package module
v0.0.0-...-176d81e Latest Latest

This package is not in the latest version of its module.

Go to latest
Published: Mar 29, 2020 License: MIT Imports: 17 Imported by: 0


About this repository

This repository is based on hilink project by Kenneth Shaw (kenshaw) with applied Pull Requests #14 and #15, cause almost everyone needs user authorization and new features support on modern devices (i.e. Huawei E8372) nowadays. Without any code engineering, merging PRs, etc. Just by 2 one-liners commands.

Additionally some minor fixes is done and command line flags added for [hlcli] and [hinfo].

It can be used something like:

$ go get -u github.com/S1mpleTheBest/hilink
$ cd $GOPATH/src/github.com/S1mpleTheBest/hilink
$ go build -o hinfo ./cmd/hinfo/main.go
$ ./hinfo -endpoint -l admin -p 12345 -v

# or simple:

$ cd $GOPATH/src/github.com/S1mpleTheBest/hilink
$ go run main.go -endpoint -l admin -p 12345
# with expected result:
  "Classify": "wingle",
  "DeviceName": "E8372",
  "HardwareVersion": "CL1E8372HM",
  "Iccid": "--------------------",
  "Imei": "---------------",
  "Imsi": "---------------",
  "MacAddress1": "00:00:00:00:00:00",
  "MacAddress2": "",
  "Msisdn": "",
  "ProductFamily": "LTE",
  "SerialNumber": "----------------",
  "SoftwareVersion": "21.328.03.00.00",
  "WanIPAddress": "",
  "WanIPv6Address": "",
  "WebUIVersion": "",
  "supportmode": "LTE|WCDMA|GSM",
  "workmode": "LTE"
# (sensitive information changed)

March 2020 - successfully tested on:

  • Huawei E3272h
  • Huawei E3372h
  • Huawei E8372h


  • Add new API calls and methods for latest firmware/WebUI versions.
  • Perform some tests with other Huawei devices models.
  • Create methods for TTL and IMEI modifications.
  • Implement user log-out function.
  • Add information about flashing devices on mac/linux based computers.

Thanks to kenshaw, mliezun and nejtr0n.

Package hilink is a Go package for working with Huawei Hilink devices (ie, 3G/4G modems and WiFi access devices).


Install in the normal way:

go get -u github.com/S1mpleTheBest/hilink


To use the Go API, please see the full API information on GoDoc.

There is a convenient command line tool, hlcli that makes working with the API extremely easy:

# install hlcli tool
$ go get -u github.com/S1mpleTheBest/hilink/cmd/hlcli

# display available commands
$ hlcli help

# get help for a subcommand 'smslist'
$ hlcli help smslist
$ hlcli smslist --help

# get network connection information from non-standard API endpoint
$ hlcli networkinfo -endpoint

# send sms with verbose output
$ hlcli smssend -to='+62....' -msg='your message' -v

# send ussd code with verbose output
$ hlcli ussdcode -code -v


This was built for interfacing with a Huawei E3370h-153 (specifically a Megafon M150-2) device, popular in Europe and Asia. It was flashed using a custom firmware and WebUI that enables the extra features.

Here is the relevant information taken from the API using the hinfo tool:

$ cd $GOPATH/src/github.com/S1mpleTheBest/hilink
$ go build ./cmd/hinfo/ && ./hinfo
  "Classify": "hilink",
  "DeviceName": "E3370",
  "HardwareVersion": "CL2E3372HM",
  <<sensitive information omitted>>
  "Msisdn": "",
  "ProductFamily": "LTE",
  "SoftwareVersion": "",
  "WebUIVersion": "",
  "supportmode": "LTE|WCDMA|GSM",
  "workmode": "LTE"


This API is currently incomplete, as I only have one type of Hilink device to test with and because I have not attempted to do an exhaustive list of all API calls. That said, it should be fairly easy to write a new API call by following the existing code. Pull requests are greatly appreciated, and encouraged!

Because the documentation available for the E3372h-153 is so poor, especially for the version that I have (M150-2, identified by the "HardwareVersion" of CL2E3372HM), and very little is available in English, I have decided for posterity to record my notes on installing modified firmware here. These notes should also work for other Huawei devices, but make sure you are using the correct firmware for your device! Incorrectly flashing your Hilink device, or using the wrong firmware will likely brick your device.

Huawei modems with older firmware (circa 2014 and older) are commonly referred to as "stick" devices. These devices can sometimes be flashed with the newer "hilink" firmware versions, however as I only have one hardware revision available to me, I cannot definitely say which can or cannot be flashed. My rudimentary investigation leads me to believe that the vast majority of the Huawei modems on the market can be updated to a Hilink version of the firmware.

For reference, all "stick" firmware starts with "21." and all hilink firmware starts with "22." While some people have had success with getting the stick firmware to work properly with Linux, I personally have not been successful doing that. Additionally, those who have reported getting the stick firmware to work, have reported significantly lower than 4G speeds.

The primary difference between "stick" and "hilink" firmware, is that the stick firmware exposes standard serial ports to the host that work as standard modems. These also have a standard tty interface that one can access and control the internals. The "hilink" firmware instead provides a standard USB ethernet device, with its own subnett'd network and exposes a web interface for controlling the software over the ethernet device. By default, that network provides standard DHCP and is configured to issue addresses in the range of

Note that a hilink device can be put into "debug" mode which then exposes a number of standard /dev/ttyUSB* that can then be used to send AT commands. This can be done by using this API or by using the usb_modeswitch command line tool. Please see GoDoc API or the usb_modeswitch manpage.

USB Modeswitching

Occassionally when a hilink device is plugged in, the Linux kernel/udev does not recognize it, and does not properly put the device into hilink mode. The latest versions of usb_modeswitch are able to deal with this:

$ sudo usb_modeswitch -v 12d1 -p 1f01 -V 12d1 -P 14dc -J

Megafon M150-2 (aka E3372h-153, aka hw rev. CL2E3372HM)

The Huawei E3372h-153 device is a 4G LTE USB modem that works with with all international 4G frequencies. The same hardware is sold as a "stick" model and a "hilink" model -- please see the comments above. Other companies may distribute this same hardware version under other names, and will usually be labeled a E3372h-153 device. I am not sure if the -153 is specific only to Megafon or not, as I have seen others listed (but have not bought) with that model number.

If it is sold as a "stick" model (sometimes listed on the packaging as a E3372s) then it will have the older firmware, and will need to be flashed / updated. Additionally, the stock firmware is missing many useful features, and should be flashed to a modified firmware version.

Flashing a CL2E3372HM with modified firmware
  1. Write down the IMEI number listed on the modem (needed if it is running the 'stick' firmware)
  2. Boot into Windows and plug the CL2E3372HM into the system
  3. Download all the files here
  4. Extract MobileBrServ.rar and as an Administrator, execute the extracted mbbServiceSetup.exe. Follow the prompts to install (if any)
  5. Extract E3372h-607_Update_21. and as an Administrator, execute the extracted E3372h-607_Update_21. Follow the prompts. If there is an error, unplug the CL2E3372HM and plug it in again. Wait until Windows recognizes the device and try again
  6. If the existing version of the firmware is a 'stick' firmware, you will be prompted for a flash unlock code. You can use this tool to generate an unlock code. Enter the unlock code and continue following the prompts
  7. Extract E3372h-153_Update_22. and as an Administrator, execute the extracted E3372h-153_Update_22. Follow the prompts
  8. Extract Update_WEBUI_17. and as an Administrator, execute the extracted Update_WEBUI_17. Follow the prompts



Package hilink provides a Hilink WebUI client.



View Source
const (
	// DefaultURL is the default URL endpoint for the Hilink WebUI.
	DefaultURL = ""

	//DefaultHost default host header
	DefaultHost = ""

	// DefaultTimeout is the default timeout.
	DefaultTimeout = 30 * time.Second

	// TokenHeader is the header used by the WebUI for CSRF tokens.
	TokenHeader = "__RequestVerificationToken"

	//TokenHeaderLogin .
	TokenHeaderLogin = TokenHeader + "one"


View Source
var (
	// ErrBadStatusCode is the bad status code error.
	ErrBadStatusCode = errors.New("bad status code")

	// ErrInvalidResponse is the invalid response error.
	ErrInvalidResponse = errors.New("invalid response")

	// ErrInvalidError is the invalid error error.
	ErrInvalidError = errors.New("invalid error")

	// ErrInvalidValue is the invalid value error.
	ErrInvalidValue = errors.New("invalid value")

	// ErrInvalidXML is the invalid xml error.
	ErrInvalidXML = errors.New("invalid xml")

	// ErrMissingRootElement is the missing root element error.
	ErrMissingRootElement = errors.New("missing root element")

	// ErrMessageTooLong is the message too long error.
	ErrMessageTooLong = errors.New("message too long")
View Source
var ErrorCodeMessageMap = map[string]string{
	"-1":     "system not available",
	"100002": "not supported by firmware or incorrect API path",
	"100003": "unauthorized",
	"100004": "system busy",
	"100005": "unknown error",
	"100006": "invalid parameter",
	"100009": "write error",
	"103002": "unknown error",
	"103015": "unknown error",
	"108001": "invalid username",
	"108002": "invalid password",
	"108003": "user already logged in",
	"108006": "invalid username or password",
	"108007": "invalid username, password, or session timeout",
	"110024": "battery charge less than 50%",
	"111019": "no network response",
	"111020": "network timeout",
	"111022": "network not supported",
	"113018": "system busy",
	"114001": "file already exists",
	"114002": "file already exists",
	"114003": "SD card currently in use",
	"114004": "path does not exist",
	"114005": "path too long",
	"114006": "no permission for specified file or directory",
	"115001": "unknown error",
	"117001": "incorrect WiFi password",
	"117004": "incorrect WISPr password",
	"120001": "voice busy",
	"125001": "invalid token",

ErrorCodeMessageMap contains the known message strings for Hilink devices.

see: http://www.bez-kabli.pl/viewtopic.php?t=42168


func HashPw

func HashPw(text string) string

HashPw generate

func NoSessionStart

func NoSessionStart(c *Client) error

NoSessionStart is an option that prevents the automatic creation of a session with the Hilink device.

func SimpleRequestXML

func SimpleRequestXML(vals ...string) []byte

SimpleRequestXML creates an XML string from value pairs.

Unfortunately the XML parser (or whatever underyling code) included with the WebUI on Hilink devices expects parameters in a specific order. This makes packages like mxj or other map based solutions not feasible for use, as Go has random key ordering for maps.

On another note, XML sucks.

func WifiDefaultConfig

func WifiDefaultConfig() map[string]string

WifiDefaultConfig returns the default configuration of the wireless interface.

func XMLMapString

func XMLMapString(indent string, vals map[string]string) string

XMLMapString builds a string of XML string map.


type Client

type Client struct {
	// contains filtered or unexported fields

Client represents a Hilink client connection.

func NewClient

func NewClient(opts ...Option) (*Client, error)

NewClient creates a new client a Hilink device.

func (*Client) AutorunVersion

func (c *Client) AutorunVersion() (string, error)

AutorunVersion retrieves device autorun version.

func (*Client) ChangePassword

func (c *Client) ChangePassword(newPassword string) (bool, error)

ChangePassword changes the current user password

func (*Client) Connect

func (c *Client) Connect() (bool, error)

Connect connects the Hilink device to the network provider.

func (*Client) ConnectionInfo

func (c *Client) ConnectionInfo() (XMLData, error)

ConnectionInfo retrieves connection (dialup) information.

func (*Client) CradleMAC

func (c *Client) CradleMAC() (string, error)

CradleMAC retrieves cradle MAC address.

func (*Client) CradleMACSet

func (c *Client) CradleMACSet(addr string) (bool, error)

CradleMACSet sets the MAC address for the cradle.

func (*Client) CradleStatusInfo

func (c *Client) CradleStatusInfo() (XMLData, error)

CradleStatusInfo retrieves cradle status information.

func (*Client) DdnsList

func (c *Client) DdnsList() (XMLData, error)

DdnsList retrieves list of DDNS providers.

func (*Client) DeviceBackup

func (c *Client) DeviceBackup() (string, error)

DeviceBackup backups device configuration and retrieves backed up configuration data as a base64 encoded string.

func (*Client) DeviceBasicInfo

func (c *Client) DeviceBasicInfo() (XMLData, error)

DeviceBasicInfo retrieves basic device information.

func (*Client) DeviceConfig

func (c *Client) DeviceConfig() (XMLData, error)

DeviceConfig retrieves device configuration.

func (*Client) DeviceControl

func (c *Client) DeviceControl(code uint) (bool, error)

DeviceControl sends a control code to the device.

func (*Client) DeviceFeatures

func (c *Client) DeviceFeatures() (XMLData, error)

DeviceFeatures retrieves device feature information.

func (*Client) DeviceInfo

func (c *Client) DeviceInfo() (XMLData, error)

DeviceInfo retrieves general device information.

func (*Client) DeviceModeSet

func (c *Client) DeviceModeSet(mode uint) (bool, error)

DeviceModeSet sets the device mode (0-project, 1-debug).

func (*Client) DeviceReboot

func (c *Client) DeviceReboot() (bool, error)

DeviceReboot restarts the device.

func (*Client) DeviceReset

func (c *Client) DeviceReset() (bool, error)

DeviceReset resets the device configuration.

func (*Client) DeviceShutdown

func (c *Client) DeviceShutdown() (bool, error)

DeviceShutdown shuts down the device.

func (*Client) DhcpConfig

func (c *Client) DhcpConfig() (XMLData, error)

DhcpConfig retrieves DHCP configuration.

func (*Client) Disconnect

func (c *Client) Disconnect() (bool, error)

Disconnect disconnects the Hilink device from the network provider.

func (*Client) DmzConfig

func (c *Client) DmzConfig() (XMLData, error)

DmzConfig retrieves DMZ status and IP address of DMZ host.

func (*Client) DmzConfigSet

func (c *Client) DmzConfigSet(enabled bool, dmzIPAddress string) (bool, error)

DmzConfigSet enables or disables the DMZ and the DMZ IP address of the device.

func (*Client) Do

func (c *Client) Do(path string, v interface{}) (XMLData, error)

Do sends a request to the server with the provided path. If data is nil, then GET will be used as the HTTP method, otherwise POST will be used.

func (*Client) FastbootFeatures

func (c *Client) FastbootFeatures() (XMLData, error)

FastbootFeatures retrieves fastboot feature information.

func (*Client) FirewallFeatures

func (c *Client) FirewallFeatures() (XMLData, error)

FirewallFeatures retrieves firewall security feature information.

func (*Client) GlobalConfig

func (c *Client) GlobalConfig() (XMLData, error)

GlobalConfig retrieves global Hilink configuration.

func (*Client) GlobalFeatures

func (c *Client) GlobalFeatures() (XMLData, error)

GlobalFeatures retrieves global feature information.

func (*Client) Language

func (c *Client) Language() (string, error)

Language retrieves current language.

func (*Client) LanguageSet

func (c *Client) LanguageSet(lang string) (bool, error)

LanguageSet sets the language.

func (*Client) LogInfo

func (c *Client) LogInfo() (XMLData, error)

LogInfo retrieves current log setting information.

func (*Client) LogPath

func (c *Client) LogPath() (string, error)

LogPath retrieves device log path (URL).

func (*Client) LoginStatusInfo

func (c *Client) LoginStatusInfo() (XMLData, error)

LoginStatusInfo retrieves the status of user authentication.

func (*Client) ModeInfo

func (c *Client) ModeInfo() (XMLData, error)

ModeInfo retrieves network mode settings information.

func (*Client) ModeList

func (c *Client) ModeList() (XMLData, error)

ModeList retrieves available network modes.

func (*Client) ModeNetworkInfo

func (c *Client) ModeNetworkInfo() (XMLData, error)

ModeNetworkInfo retrieves current network mode information.

func (*Client) ModeSet

func (c *Client) ModeSet(netMode, netBand, lteBand string) (bool, error)

ModeSet sets the network mode.

func (*Client) MonthInfo

func (c *Client) MonthInfo() (XMLData, error)

MonthInfo retrieves the month download statistic information.

func (*Client) NatType

func (c *Client) NatType() (XMLData, error)

NatType retrieves NAT type.

func (*Client) NatTypeSet

func (c *Client) NatTypeSet(ntype uint) (bool, error)

NatTypeSet sets NAT type (values: 0, 1).

func (*Client) NetworkInfo

func (c *Client) NetworkInfo() (XMLData, error)

NetworkInfo retrieves network provider information.

func (*Client) NetworkTypes

func (c *Client) NetworkTypes() (XMLData, error)

NetworkTypes retrieves available network types.

func (*Client) NewSessionAndTokenID

func (c *Client) NewSessionAndTokenID() (string, string, error)

NewSessionAndTokenID starts a session with the server, and returns the session and token.

func (*Client) NotificationInfo

func (c *Client) NotificationInfo() (XMLData, error)

NotificationInfo retrieves notification information.

func (*Client) PCAssistantConfig

func (c *Client) PCAssistantConfig() (XMLData, error)

PCAssistantConfig retrieves PC Assistant configuration.

func (*Client) PhonebookCount

func (c *Client) PhonebookCount() (XMLData, error)

PhonebookCount retrieves count of phonebook entries per group.

func (*Client) PhonebookCreate

func (c *Client) PhonebookCreate(group uint, name, phone string, sim bool) (XMLData, error)

PhonebookCreate creates a new phonebook entry.

func (*Client) PhonebookDelete

func (c *Client) PhonebookDelete(id uint) (bool, error)

PhonebookDelete deletes a specified phonebook entry.

func (*Client) PhonebookGroupList

func (c *Client) PhonebookGroupList(page, count uint, sortByName, ascending bool) (XMLData, error)

PhonebookGroupList retrieves list of the phonebook groups.

func (*Client) PhonebookImport

func (c *Client) PhonebookImport(group uint) (XMLData, error)

PhonebookImport imports SIM contacts into specified phonebook group.

func (*Client) PhonebookList

func (c *Client) PhonebookList(group, page, count uint, sim, sortByName, ascending bool, keyword string) (XMLData, error)

PhonebookList retrieves list of phonebook entries from a specified group.

func (*Client) PinActivate

func (c *Client) PinActivate(pin string) (bool, error)

PinActivate activates a SIM PIN.

func (*Client) PinChange

func (c *Client) PinChange(pin, new string) (bool, error)

PinChange changes a SIM PIN.

func (*Client) PinDeactivate

func (c *Client) PinDeactivate(pin string) (bool, error)

PinDeactivate deactivates a SIM PIN.

func (*Client) PinEnter

func (c *Client) PinEnter(pin string) (bool, error)

PinEnter enters a SIM PIN.

func (*Client) PinEnterPuk

func (c *Client) PinEnterPuk(puk, new string) (bool, error)

PinEnterPuk enters a SIM PIN puk.

func (*Client) PinInfo

func (c *Client) PinInfo() (XMLData, error)

PinInfo retrieves SIM PIN status information.

func (*Client) PinSaveInfo

func (c *Client) PinSaveInfo() (XMLData, error)

PinSaveInfo retrieves SIM PIN save information.

func (*Client) PinSimlockInfo

func (c *Client) PinSimlockInfo() (XMLData, error)

PinSimlockInfo retrieves SIM lock information.

func (*Client) PowerFeatures

func (c *Client) PowerFeatures() (XMLData, error)

PowerFeatures retrieves power feature information.

func (*Client) ProfileInfo

func (c *Client) ProfileInfo() (XMLData, error)

ProfileInfo retrieves profile information (ie, APN).

func (*Client) PublicKey

func (c *Client) PublicKey() (string, error)

PublicKey retrieves webserver public key.

func (*Client) SetSessionAndTokenID

func (c *Client) SetSessionAndTokenID(sessionID, tokenID string) error

SetSessionAndTokenID sets the sessionID and tokenID for the Client.

func (*Client) SignalInfo

func (c *Client) SignalInfo() (XMLData, error)

SignalInfo retrieves network signal information.

func (*Client) SimInfo

func (c *Client) SimInfo() (XMLData, error)

SimInfo retrieves SIM card information.

func (*Client) SipAlg

func (c *Client) SipAlg() (XMLData, error)

SipAlg retrieves status and port of the SIP application-level gateway.

func (*Client) SipAlgSet

func (c *Client) SipAlgSet(port uint, enabled bool) (bool, error)

SipAlgSet enables/disables SIP application-level gateway and sets SIP port.

func (*Client) SmsConfig

func (c *Client) SmsConfig() (XMLData, error)

SmsConfig retrieves device SMS configuration.

func (*Client) SmsCount

func (c *Client) SmsCount() (XMLData, error)

SmsCount retrieves count of SMS per inbox type.

func (*Client) SmsDelete

func (c *Client) SmsDelete(id uint) (bool, error)

SmsDelete deletes a specified SMS.

func (*Client) SmsFeatures

func (c *Client) SmsFeatures() (XMLData, error)

SmsFeatures retrieves SMS feature information.

func (*Client) SmsList

func (c *Client) SmsList(boxType, page, count uint, sortByName, ascending, unreadPreferred bool) (XMLData, error)

SmsList retrieves list of SMS in an inbox.

func (*Client) SmsReadSet

func (c *Client) SmsReadSet(id string) (bool, error)

SmsReadSet sets the read status of a SMS.

func (*Client) SmsSend

func (c *Client) SmsSend(msg string, to ...string) (bool, error)

SmsSend sends an SMS.

func (*Client) SmsSendStatus

func (c *Client) SmsSendStatus() (XMLData, error)

SmsSendStatus retrieves SMS send status information.

func (*Client) StatusInfo

func (c *Client) StatusInfo() (XMLData, error)

StatusInfo retrieves general device status information.

func (*Client) TetheringFeatures

func (c *Client) TetheringFeatures() (XMLData, error)

TetheringFeatures retrieves USB tethering feature information.

func (*Client) TrafficClear

func (c *Client) TrafficClear() (bool, error)

TrafficClear clears the current traffic statistics.

func (*Client) TrafficInfo

func (c *Client) TrafficInfo() (XMLData, error)

TrafficInfo retrieves traffic statistic information.

func (*Client) Upnp

func (c *Client) Upnp() (XMLData, error)

Upnp retrieves the status of UPNP.

func (*Client) UpnpSet

func (c *Client) UpnpSet(enabled bool) (bool, error)

UpnpSet enables/disables UPNP.

func (*Client) UssdCode

func (c *Client) UssdCode(code string) (bool, error)

UssdCode sends a USSD code to the Hilink device.

func (*Client) UssdContent

func (c *Client) UssdContent() (string, error)

UssdContent retrieves content buffer of the active USSD session.

func (*Client) UssdRelease

func (c *Client) UssdRelease() (bool, error)

UssdRelease releases the active USSD session.

func (*Client) UssdStatus

func (c *Client) UssdStatus() (UssdState, error)

UssdStatus retrieves current USSD session status information.

func (*Client) WebUIConfig

func (c *Client) WebUIConfig() (XMLData, error)

WebUIConfig retrieves WebUI configuration.

func (*Client) WifiFeatures

func (c *Client) WifiFeatures() (XMLData, error)

WifiFeatures retrieves wifi feature information.

func (*Client) WlanConfig

func (c *Client) WlanConfig() (XMLData, error)

WlanConfig retrieves basic WLAN settings.

func (*Client) WlanDisable

func (c *Client) WlanDisable(ssid string, config map[string]string) (bool, error)

WlanDisable disables the WLAN interface that matches the given ssid.

func (*Client) WlanMonthInfo

func (c *Client) WlanMonthInfo() (XMLData, error)

WlanMonthInfo retrieves the WLAN month download statistic information.

type Option

type Option func(*Client) error

Option is an option used when creating a new Client.

func Auth

func Auth(id, pw string) Option

Auth is an option specifying the identifier and password to use. The option is ignored if id is an empty string.

func HTTPClient

func HTTPClient(client *http.Client) Option

HTTPClient is an option that allows setting the http.Client used by the Client.

func Host

func Host(rawhost string) Option

Host Set Host header to each request to prevent redirect to when modem url different from default

func Log

func Log(requestLogf, responseLogf func(string, ...interface{})) Option

Log is an option that writes all HTTP request and response data to the respective logger.

func URL

func URL(rawurl string) Option

URL is an option that handles setting the URL on the Client.

type PinType

type PinType int

PinType are the PIN types for a PIN command.

const (
	PinTypeEnter PinType = iota

PinType values.

type SmsBoxType

type SmsBoxType uint

SmsBoxType represents the different inbox types available on a hilink device.

const (
	SmsBoxTypeInbox SmsBoxType = iota + 1

SmsBoxType values.

type UssdState

type UssdState int

UssdState represents the different USSD states.

const (
	UssdStateNone UssdState = iota

UssdState values.

type XMLData

type XMLData mxj.Map

XMLData is a map of XML data to encode/decode.


Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL