giDevice

package module
v0.6.2 Latest Latest
Warning

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

Go to latest
Published: Jul 24, 2022 License: MIT Imports: 26 Imported by: 4

README

Golang-iDevice

go doc go report license

much more easy to use 👉 electricbubble/gidevice-cli

Installation

go get github.com/electricbubble/gidevice
Devices
package main

import (
	giDevice "github.com/electricbubble/gidevice"
	"log"
)

func main() {
	usbmux, err := giDevice.NewUsbmux()
	if err != nil {
		log.Fatalln(err)
	}

	devices, err := usbmux.Devices()
	if err != nil {
		log.Fatal(err)
	}

	for _, dev := range devices {
		log.Println(dev.Properties().SerialNumber, dev.Properties().ProductID, dev.Properties().DeviceID)
	}
}

GetValue
package main

import (
	"encoding/json"
	"fmt"
	giDevice "github.com/electricbubble/gidevice"
	"log"
)

type DeviceDetail struct {
	DeviceName                string `json:"DeviceName,omitempty"`
	DeviceColor               string `json:"DeviceColor,omitempty"`
	DeviceClass               string `json:"DeviceClass,omitempty"`
	ProductVersion            string `json:"ProductVersion,omitempty"`
	ProductType               string `json:"ProductType,omitempty"`
	ProductName               string `json:"ProductName,omitempty"`
	ModelNumber               string `json:"ModelNumber,omitempty"`
	SerialNumber              string `json:"SerialNumber,omitempty"`
	SIMStatus                 string `json:"SIMStatus,omitempty"`
	PhoneNumber               string `json:"PhoneNumber,omitempty"`
	CPUArchitecture           string `json:"CPUArchitecture,omitempty"`
	ProtocolVersion           string `json:"ProtocolVersion,omitempty"`
	RegionInfo                string `json:"RegionInfo,omitempty"`
	TelephonyCapability       bool   `json:"TelephonyCapability,omitempty"`
	TimeZone                  string `json:"TimeZone,omitempty"`
	UniqueDeviceID            string `json:"UniqueDeviceID,omitempty"`
	WiFiAddress               string `json:"WiFiAddress,omitempty"`
	WirelessBoardSerialNumber string `json:"WirelessBoardSerialNumber,omitempty"`
	BluetoothAddress          string `json:"BluetoothAddress,omitempty"`
	BuildVersion              string `json:"BuildVersion,omitempty"`
}

func main() {
	usbmux, err := giDevice.NewUsbmux()
	if err != nil {
		log.Fatal(err)
	}

	devices, err := usbmux.Devices()
	if err != nil {
		log.Fatal(err)
	}

	if len(devices) == 0 {
		log.Fatal("No Device")
	}

	d := devices[0]

	detail, err1 := d.GetValue("", "")
	if err1 != nil {
		fmt.Errorf("get %s device detail fail : %w", d.Properties().SerialNumber, err1)
	}

	data, _ := json.Marshal(detail)
	d1 := &DeviceDetail{}
	json.Unmarshal(data, d1)
	fmt.Println(d1)
}
DeveloperDiskImage
package main

import (
	"encoding/base64"
	giDevice "github.com/electricbubble/gidevice"
	"log"
)

func main() {
	usbmux, err := giDevice.NewUsbmux()
	if err != nil {
		log.Fatal(err)
	}

	devices, err := usbmux.Devices()
	if err != nil {
		log.Fatal(err)
	}

	if len(devices) == 0 {
		log.Fatal("No Device")
	}

	d := devices[0]

	imageSignatures, err := d.Images()
	if err != nil {
		log.Fatalln(err)
	}

	for i, imgSign := range imageSignatures {
		log.Printf("[%d] %s\n", i+1, base64.StdEncoding.EncodeToString(imgSign))
	}

	dmgPath := "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/14.4/DeveloperDiskImage.dmg"
	signaturePath := "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/14.4/DeveloperDiskImage.dmg.signature"

	err = d.MountDeveloperDiskImage(dmgPath, signaturePath)
	if err != nil {
		log.Fatalln(err)
	}
}

App
package main

import (
	giDevice "github.com/electricbubble/gidevice"
	"log"
	"path/filepath"
)

func main() {
	usbmux, err := giDevice.NewUsbmux()
	if err != nil {
		log.Fatalln(err)
	}

	devices, err := usbmux.Devices()
	if err != nil {
		log.Fatalln(err)
	}

	if len(devices) == 0 {
		log.Fatalln("No Device")
	}

	d := devices[0]

	bundleID := "com.apple.Preferences"
	pid, err := d.AppLaunch(bundleID)
	if err != nil {
		log.Fatalln(err)
	}

	err = d.AppKill(pid)
	if err != nil {
		log.Fatalln(err)
	}

	runningProcesses, err := d.AppRunningProcesses()
	if err != nil {
		log.Fatalln(err)
	}

	for _, process := range runningProcesses {
		if process.IsApplication {
			log.Printf("%4d\t%-24s\t%-36s\t%s\n", process.Pid, process.Name, filepath.Base(process.RealAppName), process.StartDate)
		}
	}
}

Screenshot
package main

import (
	giDevice "github.com/electricbubble/gidevice"
	"image"
	"image/jpeg"
	"image/png"
	"log"
	"os"
)

func main() {
	usbmux, err := giDevice.NewUsbmux()
	if err != nil {
		log.Fatalln(err)
	}

	devices, err := usbmux.Devices()
	if err != nil {
		log.Fatalln(err)
	}

	if len(devices) == 0 {
		log.Fatalln("No Device")
	}

	d := devices[0]

	raw, err := d.Screenshot()
	if err != nil {
		log.Fatalln(err)
	}

	img, format, err := image.Decode(raw)
	if err != nil {
		log.Fatalln(err)
	}
	userHomeDir, _ := os.UserHomeDir()
	file, err := os.Create(userHomeDir + "/Desktop/s1." + format)
	if err != nil {
		log.Fatalln(err)
	}
	defer func() { _ = file.Close() }()
	switch format {
	case "png":
		err = png.Encode(file, img)
	case "jpeg":
		err = jpeg.Encode(file, img, nil)
	}
	if err != nil {
		log.Fatalln(err)
	}
	log.Println(file.Name())
}

SimulateLocation
package main

import (
	giDevice "github.com/electricbubble/gidevice"
	"log"
)

func main() {
	usbmux, err := giDevice.NewUsbmux()
	if err != nil {
		log.Fatalln(err)
	}

	devices, err := usbmux.Devices()
	if err != nil {
		log.Fatalln(err)
	}

	if len(devices) == 0 {
		log.Fatalln("No Device")
	}

	d := devices[0]

	// https://api.map.baidu.com/lbsapi/getpoint/index.html
	if err = d.SimulateLocationUpdate(116.024067, 40.362639, giDevice.CoordinateSystemBD09); err != nil {
		log.Fatalln(err)
	}

	// https://developer.amap.com/tools/picker
	// https://lbs.qq.com/tool/getpoint/index.html
	// if err = d.SimulateLocationUpdate(120.116979, 30.252876, giDevice.CoordinateSystemGCJ02); err != nil {
	// 	log.Fatalln(err)
	// }

	// if err = d.SimulateLocationUpdate(121.499763, 31.239580,giDevice.CoordinateSystemWGS84); err != nil {
	// if err = d.SimulateLocationUpdate(121.499763, 31.239580); err != nil {
	// 	log.Fatalln(err)
	// }

	// err = d.SimulateLocationRecover()
	// if err != nil {
	// 	log.Fatalln(err)
	// }
}

XCTest
package main

import (
	"fmt"
	giDevice "github.com/electricbubble/gidevice"
	"log"
	"os"
	"os/signal"
)

func main() {
	usbmux, err := giDevice.NewUsbmux()
	if err != nil {
		log.Fatal(err)
	}

	devices, err := usbmux.Devices()
	if err != nil {
		log.Fatal(err)
	}

	if len(devices) == 0 {
		log.Fatal("No Device")
	}

	d := devices[0]

	out, cancel, err := d.XCTest("com.leixipaopao.WebDriverAgentRunner.xctrunner")
	if err != nil {
		log.Fatal(err)
	}

	done := make(chan os.Signal, 1)
	signal.Notify(done, os.Interrupt)

	go func() {
		for s := range out {
			fmt.Print(s)
		}
	}()

	<-done
	cancel()
	fmt.Println()
	log.Println("DONE")
}

Connect and Forward
package main

import (
	"fmt"
	giDevice "github.com/electricbubble/gidevice"
	"io"
	"log"
	"net"
	"os"
	"os/signal"
	"time"
	"syscall"
)

func main() {
	usbmux, err := giDevice.NewUsbmux()
	if err != nil {
		log.Fatal(err)
	}

	devices, err := usbmux.Devices()
	if err != nil {
		log.Fatal(err)
	}

	if len(devices) == 0 {
		log.Fatal("No Device")
	}

	d := devices[0]

	localPort, remotePort := 8100, 8100

	listener, err := net.Listen("tcp", fmt.Sprintf(":%d", localPort))

	go func(listener net.Listener) {
		for {
			var accept net.Conn
			if accept, err = listener.Accept(); err != nil {
				log.Println("accept:", err)
			}

			fmt.Println("accept", accept.RemoteAddr())

			rInnerConn, err := d.NewConnect(remotePort)
			if err != nil {
				log.Println(err)
				os.Exit(0)
			}

			rConn := rInnerConn.RawConn()
			_ = rConn.SetDeadline(time.Time{})

			go func(lConn net.Conn) {
				go func(lConn, rConn net.Conn) {
					if _, err := io.Copy(lConn, rConn); err != nil {
						//do sth
					}
				}(lConn, rConn)
				go func(lConn, rConn net.Conn) {
					if _, err := io.Copy(rConn, lConn); err != nil {
						//do sth
					}
				}(lConn, rConn)
			}(accept)
		}
	}(listener)

	done := make(chan os.Signal, syscall.SIGTERM)
	signal.Notify(done, os.Interrupt, os.Kill)
	<-done
}

Thanks

About
libimobiledevice/libimobiledevice A cross-platform protocol library to communicate with iOS devices
anonymous5l/iConsole iOS usbmuxd communication impl iTunes protocol
alibaba/taobao-iphone-device tidevice can be used to communicate with iPhone device

Thank you JetBrains for providing free open source licenses

Documentation

Index

Constants

View Source
const (
	CoordinateSystemWGS84 = libimobiledevice.CoordinateSystemWGS84
	CoordinateSystemBD09  = libimobiledevice.CoordinateSystemBD09
	CoordinateSystemGCJ02 = libimobiledevice.CoordinateSystemGCJ02
)
View Source
const (
	ApplicationTypeSystem   = libimobiledevice.ApplicationTypeSystem
	ApplicationTypeUser     = libimobiledevice.ApplicationTypeUser
	ApplicationTypeInternal = libimobiledevice.ApplicationTypeInternal
	ApplicationTypeAny      = libimobiledevice.ApplicationTypeAny
)
View Source
const LockdownPort = 62078

Variables

View Source
var ErrAfcStatNotExist = errors.New("afc stat: no such file or directory")

Functions

func DeviceVersion

func DeviceVersion(version ...int) int

func SetDebug

func SetDebug(debug bool, libDebug ...bool)

SetDebug sets debug mode

Types

type Afc

type Afc interface {
	DiskInfo() (diskInfo *AfcDiskInfo, err error)
	ReadDir(dirname string) (names []string, err error)
	Stat(filename string) (info *AfcFileInfo, err error)
	Open(filename string, mode AfcFileMode) (file *AfcFile, err error)
	Remove(filePath string) (err error)
	Rename(oldPath string, newPath string) (err error)
	Mkdir(path string) (err error)
	Link(oldName string, newName string, linkType AfcLinkType) (err error)
	Truncate(filePath string, size int64) (err error)
	SetFileModTime(filePath string, modTime time.Time) (err error)
	// Hash sha1 algorithm
	Hash(filePath string) ([]byte, error)
	// HashWithRange sha1 algorithm with file range
	HashWithRange(filePath string, start, end uint64) ([]byte, error)
	RemoveAll(path string) (err error)

	WriteFile(filename string, data []byte, perm AfcFileMode) (err error)
}

type AfcDiskInfo

type AfcDiskInfo struct {
	Model      string
	TotalBytes uint64
	FreeBytes  uint64
	BlockSize  uint64
}

type AfcFile

type AfcFile struct {
	// contains filtered or unexported fields
}

func (*AfcFile) Close

func (f *AfcFile) Close() (err error)

func (*AfcFile) Lock

func (f *AfcFile) Lock(lockType AfcLockType) (err error)

func (*AfcFile) Read

func (f *AfcFile) Read(b []byte) (n int, err error)

func (*AfcFile) Seek

func (f *AfcFile) Seek(offset int64, whence int) (ret int64, err error)

func (*AfcFile) Tell

func (f *AfcFile) Tell() (n uint64, err error)

func (*AfcFile) Truncate

func (f *AfcFile) Truncate(size int64) (err error)

func (*AfcFile) Unlock

func (f *AfcFile) Unlock() (err error)

func (*AfcFile) Write

func (f *AfcFile) Write(b []byte) (n int, err error)

type AfcFileInfo

type AfcFileInfo struct {
	// contains filtered or unexported fields
}

func (*AfcFileInfo) CreationTime

func (f *AfcFileInfo) CreationTime() time.Time

func (*AfcFileInfo) IsDir

func (f *AfcFileInfo) IsDir() bool

func (*AfcFileInfo) ModTime

func (f *AfcFileInfo) ModTime() time.Time

func (*AfcFileInfo) Name

func (f *AfcFileInfo) Name() string

func (*AfcFileInfo) Size

func (f *AfcFileInfo) Size() int64

type AfcFileMode

type AfcFileMode uint32
const (
	AfcFileModeRdOnly   AfcFileMode = 0x00000001
	AfcFileModeRw       AfcFileMode = 0x00000002
	AfcFileModeWrOnly   AfcFileMode = 0x00000003
	AfcFileModeWr       AfcFileMode = 0x00000004
	AfcFileModeAppend   AfcFileMode = 0x00000005
	AfcFileModeRdAppend AfcFileMode = 0x00000006
)

type AfcLinkType

type AfcLinkType int
const (
	AfcLinkTypeHardLink AfcLinkType = 1
	AfcLinkTypeSymLink  AfcLinkType = 2
)

type AfcLockType

type AfcLockType int
const (
	AfcLockTypeSharedLock    AfcLockType = 1 | 4
	AfcLockTypeExclusiveLock AfcLockType = 2 | 4
	AfcLockTypeUnlock        AfcLockType = 8 | 4
)

type AppLaunchOption

type AppLaunchOption func(option *appLaunchOption)

func WithAppPath

func WithAppPath(appPath string) AppLaunchOption

func WithArguments

func WithArguments(arguments []interface{}) AppLaunchOption

func WithEnvironment

func WithEnvironment(environment map[string]interface{}) AppLaunchOption

func WithKillExisting

func WithKillExisting(b bool) AppLaunchOption

func WithOptions

func WithOptions(options map[string]interface{}) AppLaunchOption

type AppListOption

type AppListOption func(option *appListOption)

func WithAppsMatching

func WithAppsMatching(appsMatching map[string]interface{}) AppListOption

func WithUpdateToken

func WithUpdateToken(updateToken string) AppListOption

type Application

type Application struct {
	AppExtensionUUIDs         []string `json:"AppExtensionUUIDs,omitempty"`
	BundlePath                string   `json:"BundlePath"`
	CFBundleIdentifier        string   `json:"CFBundleIdentifier"`
	ContainerBundleIdentifier string   `json:"ContainerBundleIdentifier,omitempty"`
	ContainerBundlePath       string   `json:"ContainerBundlePath,omitempty"`
	DisplayName               string   `json:"DisplayName"`
	ExecutableName            string   `json:"ExecutableName,omitempty"`
	Placeholder               bool     `json:"Placeholder,omitempty"`
	PluginIdentifier          string   `json:"PluginIdentifier,omitempty"`
	PluginUUID                string   `json:"PluginUUID,omitempty"`
	Restricted                int      `json:"Restricted"`
	Type                      string   `json:"Type"`
	Version                   string   `json:"Version"`
}

type ApplicationType

type ApplicationType = libimobiledevice.ApplicationType

type CoordinateSystem

type CoordinateSystem = libimobiledevice.CoordinateSystem

type CrashReportMover added in v0.3.0

type CrashReportMover interface {
	Move(hostDir string, opts ...CrashReportMoverOption) (err error)
	// contains filtered or unexported methods
}

type CrashReportMoverOption added in v0.3.0

type CrashReportMoverOption func(opt *crashReportMoverOption)

func WithExtractRawCrashReport added in v0.3.0

func WithExtractRawCrashReport(b bool) CrashReportMoverOption

func WithKeepCrashReport added in v0.3.0

func WithKeepCrashReport(b bool) CrashReportMoverOption

func WithWhenMoveIsDone added in v0.3.0

func WithWhenMoveIsDone(whenDone func(filename string)) CrashReportMoverOption

type Device

type Device interface {
	Properties() DeviceProperties

	NewConnect(port int, timeout ...time.Duration) (InnerConn, error)
	ReadPairRecord() (pairRecord *PairRecord, err error)
	SavePairRecord(pairRecord *PairRecord) (err error)
	DeletePairRecord() (err error)

	QueryType() (LockdownType, error)
	GetValue(domain, key string) (v interface{}, err error)
	Pair() (pairRecord *PairRecord, err error)

	Images(imgType ...string) (imageSignatures [][]byte, err error)
	MountDeveloperDiskImage(dmgPath string, signaturePath string) (err error)

	Screenshot() (raw *bytes.Buffer, err error)

	SimulateLocationUpdate(longitude float64, latitude float64, coordinateSystem ...CoordinateSystem) (err error)
	SimulateLocationRecover() (err error)

	InstallationProxyBrowse(opts ...InstallationProxyOption) (currentList []interface{}, err error)
	InstallationProxyLookup(opts ...InstallationProxyOption) (lookupResult interface{}, err error)

	AppLaunch(bundleID string, opts ...AppLaunchOption) (pid int, err error)
	AppKill(pid int) (err error)
	AppRunningProcesses() (processes []Process, err error)
	AppList(opts ...AppListOption) (apps []Application, err error)
	DeviceInfo() (devInfo *DeviceInfo, err error)

	AfcService() (afc Afc, err error)
	AppInstall(ipaPath string) (err error)
	AppUninstall(bundleID string) (err error)

	HouseArrestService() (houseArrest HouseArrest, err error)

	Syslog() (lines <-chan string, err error)
	SyslogStop()

	PcapdService() (pcapd Pcapd, err error)
	Pcap() (packet <-chan []byte, err error)
	PcapStop()

	Reboot() error
	Shutdown() error

	MoveCrashReport(hostDir string, opts ...CrashReportMoverOption) (err error)

	XCTest(bundleID string, opts ...XCTestOption) (out <-chan string, cancel context.CancelFunc, err error)

	GetIconPNGData(bundleId string) (raw *bytes.Buffer, err error)
	GetInterfaceOrientation() (orientation OrientationState, err error)
	// contains filtered or unexported methods
}

type DeviceInfo

type DeviceInfo struct {
	Description       string `json:"_deviceDescription"`
	DisplayName       string `json:"_deviceDisplayName"`
	Identifier        string `json:"_deviceIdentifier"`
	Version           string `json:"_deviceVersion"`
	ProductType       string `json:"_productType"`
	ProductVersion    string `json:"_productVersion"`
	XRDeviceClassName string `json:"_xrdeviceClassName"`
}

type DeviceProperties

type DeviceProperties = libimobiledevice.DeviceProperties

type DiagnosticsRelay added in v0.4.0

type DiagnosticsRelay interface {
	Reboot() error
	Shutdown() error
}

type HouseArrest

type HouseArrest interface {
	Documents(bundleID string) (afc Afc, err error)
	Container(bundleID string) (afc Afc, err error)
}

type ImageMounter

type ImageMounter interface {
	Images(imgType string) (imageSignatures [][]byte, err error)
	UploadImage(imgType, dmgPath string, signatureData []byte) (err error)
	Mount(imgType, devImgPath string, signatureData []byte) (err error)

	UploadImageAndMount(imgType, devImgPath, dmgPath, signaturePath string) (err error)
}

type InnerConn

type InnerConn = libimobiledevice.InnerConn

type InstallationProxy

type InstallationProxy interface {
	Browse(opts ...InstallationProxyOption) (currentList []interface{}, err error)
	Lookup(opts ...InstallationProxyOption) (lookupResult interface{}, err error)
	Install(bundleID, packagePath string) (err error)
	Uninstall(bundleID string) (err error)
}

type InstallationProxyOption

type InstallationProxyOption func(*installationProxyOption)

func WithApplicationType

func WithApplicationType(appType ApplicationType) InstallationProxyOption

func WithBundleIDs

func WithBundleIDs(BundleIDs ...string) InstallationProxyOption

func WithMetaData

func WithMetaData(b bool) InstallationProxyOption

func WithReturnAttributes

func WithReturnAttributes(attrs ...string) InstallationProxyOption

type Instruments

type Instruments interface {
	AppLaunch(bundleID string, opts ...AppLaunchOption) (pid int, err error)
	AppKill(pid int) (err error)
	AppRunningProcesses() (processes []Process, err error)
	AppList(opts ...AppListOption) (apps []Application, err error)
	DeviceInfo() (devInfo *DeviceInfo, err error)
	// contains filtered or unexported methods
}

type Lockdown

type Lockdown interface {
	QueryType() (LockdownType, error)
	GetValue(domain, key string) (v interface{}, err error)
	SetValue(domain, key string, value interface{}) (err error)
	Pair() (pairRecord *PairRecord, err error)
	EnterRecovery() (err error)

	ImageMounterService() (imageMounter ImageMounter, err error)
	ScreenshotService() (screenshot Screenshot, err error)
	SimulateLocationService() (simulateLocation SimulateLocation, err error)
	InstallationProxyService() (installationProxy InstallationProxy, err error)
	InstrumentsService() (instruments Instruments, err error)
	TestmanagerdService() (testmanagerd Testmanagerd, err error)
	AfcService() (afc Afc, err error)
	HouseArrestService() (houseArrest HouseArrest, err error)
	SyslogRelayService() (syslogRelay SyslogRelay, err error)
	DiagnosticsRelayService() (diagnostics DiagnosticsRelay, err error)
	CrashReportMoverService() (crashReportMover CrashReportMover, err error)
	SpringBoardService() (springBoard SpringBoard, err error)
	// contains filtered or unexported methods
}

type LockdownType

type LockdownType = libimobiledevice.LockdownType

type OrientationState added in v0.6.0

type OrientationState = libimobiledevice.OrientationState

type PairRecord

type PairRecord = libimobiledevice.PairRecord

type Pcapd added in v0.3.3

type Pcapd interface {
	Packet() <-chan []byte
	Stop()
}

type Process

type Process struct {
	IsApplication bool      `json:"isApplication"`
	Name          string    `json:"name"`
	Pid           int       `json:"pid"`
	RealAppName   string    `json:"realAppName"`
	StartDate     time.Time `json:"startDate"`
}

type Screenshot

type Screenshot interface {
	Take() (raw *bytes.Buffer, err error)
	// contains filtered or unexported methods
}

type SimulateLocation

type SimulateLocation interface {
	Update(longitude float64, latitude float64, coordinateSystem ...CoordinateSystem) (err error)
	// Recover try to revert back
	Recover() (err error)
}

type SpringBoard added in v0.5.0

type SpringBoard interface {
	GetIconPNGData(bundleId string) (raw *bytes.Buffer, err error)
	GetInterfaceOrientation() (orientation OrientationState, err error)
}

type SyslogRelay added in v0.2.0

type SyslogRelay interface {
	Lines() <-chan string
	Stop()
}

type Testmanagerd

type Testmanagerd interface {
	// contains filtered or unexported methods
}

type Usbmux

type Usbmux interface {
	Devices() ([]Device, error)
	ReadBUID() (string, error)
	Listen(chan Device) (context.CancelFunc, error)
}

func NewUsbmux

func NewUsbmux() (Usbmux, error)

type XCTestManagerDaemon

type XCTestManagerDaemon interface {
	// contains filtered or unexported methods
}

type XCTestOption added in v0.3.2

type XCTestOption func(opt *xcTestOption)

func WithXCTestEnv added in v0.3.2

func WithXCTestEnv(env map[string]interface{}) XCTestOption

func WithXCTestOpt added in v0.3.2

func WithXCTestOpt(appOpt map[string]interface{}) XCTestOption

Directories

Path Synopsis
pkg
ipa

Jump to

Keyboard shortcuts

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