helper

package
v0.0.0-...-2bc18d8 Latest Latest
Warning

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

Go to latest
Published: Jul 14, 2017 License: BSD-2-Clause Imports: 49 Imported by: 0

Documentation

Index

Constants

View Source
const (
	DATE_TIME_FMT = "2006-01-02 15:04:05"

	DATE_FMT = "2006-01-02"

	TIME_FMT = "15:04:05"

	DATE_TIME_FMT_CN = "2006年01月02日 15时04分05秒"

	DATE_FMT_CN = "2006年01月02日"

	TIME_FMT_CN = "15时04分05秒"
)

FMT_TYPE_NOMAL

View Source
const SecondInNano = 1000 * 1000 * 1000

Variables

View Source
var (
	SmtpHost     = "smtp.163.com"
	SmtpPort     = "25"
	MailUser     = "yougamcom@163.com" //发送邮件的邮箱
	MailPassword = "Me87ga88mRpasW"    //发送邮件邮箱的密码
	MailAdline   = "yougam.Com • 分享、探索和创造的地方."
)
View Source
var (
	RsaPublicKey = []byte(`
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZsfv1qscqYdy4vY+P4e3cAtmv
ppXQcRvrF1cB4drkv0haU24Y7m5qYtT52Kr539RdbKKdLAM6s20lWy7+5C0Dgacd
wYWd/7PeCELyEipZJL07Vro7Ate8Bfjya+wltGK9+XNUIHiumUKULW4KDx21+1NL
AUeJ6PeW+DAkmJWF6QIDAQAB
-----END PUBLIC KEY-----
`)

	RsaPrivateKey = []byte(`
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDZsfv1qscqYdy4vY+P4e3cAtmvppXQcRvrF1cB4drkv0haU24Y
7m5qYtT52Kr539RdbKKdLAM6s20lWy7+5C0DgacdwYWd/7PeCELyEipZJL07Vro7
Ate8Bfjya+wltGK9+XNUIHiumUKULW4KDx21+1NLAUeJ6PeW+DAkmJWF6QIDAQAB
AoGBAJlNxenTQj6OfCl9FMR2jlMJjtMrtQT9InQEE7m3m7bLHeC+MCJOhmNVBjaM
ZpthDORdxIZ6oCuOf6Z2+Dl35lntGFh5J7S34UP2BWzF1IyyQfySCNexGNHKT1G1
XKQtHmtc2gWWthEg+S6ciIyw2IGrrP2Rke81vYHExPrexf0hAkEA9Izb0MiYsMCB
/jemLJB0Lb3Y/B8xjGjQFFBQT7bmwBVjvZWZVpnMnXi9sWGdgUpxsCuAIROXjZ40
IRZ2C9EouwJBAOPjPvV8Sgw4vaseOqlJvSq/C/pIFx6RVznDGlc8bRg7SgTPpjHG
4G+M3mVgpCX1a/EU1mB+fhiJ2LAZ/pTtY6sCQGaW9NwIWu3DRIVGCSMm0mYh/3X9
DAcwLSJoctiODQ1Fq9rreDE5QfpJnaJdJfsIJNtX1F+L3YceeBXtW0Ynz2MCQBI8
9KP274Is5FkWkUFNKnuKUK4WKOuEXEO+LpR+vIhs7k6WQ8nGDd4/mujoJBr5mkrw
DPwqA3N5TMNDQVGv8gMCQQCaKGJgWYgvo3/milFfImbp+m7/Y3vCptarldXrYQWO
AQjxwc71ZGBFDITYvdgJM1MTqc8xQek1FXn1vfpy2c6O
-----END RSA PRIVATE KEY-----
`)

	Version = "4.0.3"

	AesKey       = "jjsdj#$827gfgh38dakljzmknghsj#2k" //32位
	AesPublicKey = "akljzmknm.ahkjkl"                 //16位

	ConfigPath = "./conf/config.conf"

	//网站信息
	Domain      = "http://www.yougam.com"
	SiteName    = "YOUGAM"
	SiteTitle   = "YOUGAM - 记录分享探索世界的有趣轨迹"
	Keywords    = "" /* 130-byte string literal not displayed */
	Description = "优姬是一个记录生命旅途的每一分精彩和感动,珍藏美好的回忆,分享创造发现的地方.."

	//验证码
	IsCaptcha = false

	//数据库设定
	DataType  = "goleveldb"
	DBConnect = "goleveldb://../yougam/data/tidb/tidb"

	//API通信常量密匙
	AesConstKey = "Ks*x(" //5位

	//七牛云存储
	BUCKET4QINIU = "yougam"
	DOMAIN4QINIU = "7mnnte.com1.z0.glb.clouddn.com"
	AKEY4QINIU   = "7hFd6CFkhjbtr74AgTlXZ7WgY4mpo4pmsTlyK37D"
	SKEY4QINIU   = "F5EmL1X44LA-ypSIX1TUdZrpwxrDwUI2WiMnRVe6"

	//本地存储根路径设定
	FileStorageDir = "../"

	//数据库表前缀设定
	DBTablePrefix = ""
)

Functions

func Aes128COMDecrypt

func Aes128COMDecrypt(crypted string, constKey string) (string, error)

func Aes128COMEncrypt

func Aes128COMEncrypt(content string, constKey string) (string, error)

func AesCBCDecrypt

func AesCBCDecrypt(crypted, key []byte) ([]byte, error)

AesCBCDecrypt AES256 解密CBC模式

func AesCBCEncrypt

func AesCBCEncrypt(origData, key []byte) ([]byte, error)

AesCBCEncrypt aes cbc encrypt AES-128,设key为 16 bytes. AES-192,设key为 24 bytes. AES-256,设key为 32 bytes. AES256 加密CBC模式

func AesCFBDecrypt

func AesCFBDecrypt(ciphertext string, privateKey string, publicKey string) (string, error)

AesCFBDecrypt AES256解密 CFB

func AesCFBEncrypt

func AesCFBEncrypt(content string, privateKey string, publicKey string) (string, error)

AesCFBEncrypt AES256加密 CFB

func AtPages

func AtPages(content string) ([]string, string)

AtPages 获取文本中 @urls 的网址集合 ###AtPages函数的作用是提取@后面的url网址,并不是提取图片,请注意!

func AtPagesGetImages

func AtPagesGetImages(content string) ([]string, string)

AtPagesGetImages 从网址的内容中提取图片

func AtUsers

func AtUsers(content string) (usrs []string)

AtUsers 获取文本中 @user 中的用户名集合

func AtWhois

func AtWhois(content string) []string

AtWhois get user by at

func Base64Encoding

func Base64Encoding(s string) string

Base64Encoding 对内容进行标准base64编码

func C2C

func C2C(fromstr string, tostr string, str string) string

轉換邏輯,翻譯過程處理函數

func CheckEmail

func CheckEmail(email string) (b bool)

CheckEmail : 检测伊妹儿是否符合格式

func CheckPassword

func CheckPassword(password string) (b bool)

CheckPassword 检测密码字符是否符合标准

func CheckUsername

func CheckUsername(username string) (b bool)

CheckUsername 检测用户名称是否符合标准

func Compare

func Compare(f, s, t string) bool

Compare 在模板中比较变量

func CompareDiff

func CompareDiff(fg1, fg2 string) int

Pha Compare

func Confidence

func Confidence(ups int64, downs int64) float64

Reddit信任度排序算法 返回数值越大则越信任度越高

func Controversy

func Controversy(ups int64, downs int64) float64

//Reddit争议排序算法 譬如1000赞成+1000反对还是会比1赞成+0反对要靠前。

func ConvertToBase64

func ConvertToBase64(in string) string

func ConvertToBase64ByPongo2

func ConvertToBase64ByPongo2(in *pongo2.Value) *pongo2.Value

func Convzh

func Convzh(str string, lang string) []byte

func CopyDir

func CopyDir(source string, dest string) (err error)

CopyDir : copy source directorie to dest

func CopyFile

func CopyFile(source string, dest string) (err error)

CopyFile : copy the source ro dest

func CreateFile

func CreateFile(dir string, name string) (string, error)

CreateFile 创建文件

func Cropword

func Cropword(in string, start int, length int, symbol string) string

func CropwordByPongo2

func CropwordByPongo2(in *pongo2.Value, start *pongo2.Value, length *pongo2.Value, symbol *pongo2.Value) *pongo2.Value

func DelFile

func DelFile(files []os.FileInfo, count int, fileDir string)

DelFile : 获取所有文件,如果文件达到最上限,按时间删除

func DelLostImages

func DelLostImages(oldz string, newz string)

DelLostImages delete the lost images

func DifferenceSets

func DifferenceSets(a []string, b []string) []string

DifferenceSets 差集

func Elapse

func Elapse(f func()) int64

Timing the cost of function call, unix nano was returned

func ElapseString

func ElapseString(f func()) string

Timing the cost of function call, unix nano was returned

func EncryptHash

func EncryptHash(password string, salt []byte) string

func Exist

func Exist(filename string) bool

Exist : if file exist return true

func File

func File(s string) string

func FileGetContent

func FileGetContent(file string) (string, error)

FileGetContent 获取指定文件的内容

func FileMTime

func FileMTime(file string) (int64, error)

FileMTime 返回文件的修改时间戳

func FilePutContent

func FilePutContent(file string, content string) (int, error)

FilePutContent 将指定内容写入指定文件

func FileSize

func FileSize(file string) (int64, error)

FileSize 获取文件尺寸

func Filehash

func Filehash(pathOr string, fileOr *os.File) (string, error)

Filehash get hash of file

func FilehashBlock

func FilehashBlock(path string, block int64) string

FilehashBlock 仅以区块范围作文件哈希

func FilehashNumber

func FilehashNumber(path string) (int, error)

FilehashNumber 以数字形式表达文件希哈

func FixURL

func FixURL(currentURL, url string) string

FixURL 修正url

func FixedpathByNumber

func FixedpathByNumber(n int, layer int) string

FixedpathByNumber is fixed path via number

func FixedpathByString

func FixedpathByString(s string, layer int) string

FixedpathByString is fixed path via string

func GUID

func GUID() string

GUID 生成36位GUID

func GUID32BIT

func GUID32BIT() string

GUID32BIT generator a 32bit guid

func GetBanner

func GetBanner(content string) (string, error)

GetBanner : Get one of images

func GetBannerThumbnail

func GetBannerThumbnail(content string) (string, error)

GetBannerThumbnail 从内容获取banner

func GetCurrentTimeFormat

func GetCurrentTimeFormat(format string) string

format

func GetFile

func GetFile(fileURL string, filePath string, useragent string, referer string) error

GetFile 从因特网获取网页内容作为文件

func GetImagePha

func GetImagePha(path string) (string, error)

PHA算法 获取图像指纹

func GetImages

func GetImages(content string) (imgs []string, num int)

GetImages 返回 图片url列表集合

func GetJsonCOMDecrypt

func GetJsonCOMDecrypt(status string, actionurl string, data interface{}, ckJar *cookiejar.Jar) (*simplejson.Json, error)

func GetMonthDays

func GetMonthDays(year, month int) int

GetMonthDays return days of the month/year

func GetPage

func GetPage(url string) (string, error)

GetPage 返回获得的网页内容

func GetSensitiveInfoRemovedEmail

func GetSensitiveInfoRemovedEmail(email string) string

GetSensitiveInfoRemovedEmail 获取 sensitive 信息

func GetThumbnails

func GetThumbnails(content string) (thumbnails string, thumbnailslarge string, thumbnailsmedium string, thumbnailssmall string, err error)

GetThumbnails get thumbnails from the content

func GetTimeFormat

func GetTimeFormat(second int64, format string) string

func GetTimestamp

func GetTimestamp() int64

return 1441006057 in sec

func GetTimestampInMicro

func GetTimestampInMicro() int64

微秒

func GetTimestampInMicroString

func GetTimestampInMicroString() string

微秒

func GetTimestampInMilli

func GetTimestampInMilli() int64

return 1441007112776 in millisecond

func GetTimestampInMilliString

func GetTimestampInMilliString() string

return 1441007112776 in millisecond

func GetTimestampString

func GetTimestampString() string

return 1441006057 in sec

func GetVideoAddress

func GetVideoAddress(input string) string

GetVideoAddress 调用you-get命令对输入的视频网站网址进行抽取真实视频源网址

func GraphicsProcess

func GraphicsProcess(r io.Reader, w io.Writer, width, height, quality int) error

GraphicsProcess 图像处理过程

func Gravatar

func Gravatar(email string, height int) string

Gravatar 根据用户邮箱显示Gravatar头像

func HTML2str

func HTML2str(html string) string

HTML2str 过滤html文本内容中的特殊标签或内容

func Hotness

func Hotness(ups, downs, createTime int64) float64

func Htm2Str

func Htm2Str(htm string) string

Htm2Str 将html字符内容以纯Policy方式过滤内容后返回字符串

func Htmlquote

func Htmlquote(text string) string

Htmlquote HTML编码为实体符号

func Htmlunquote

func Htmlunquote(text string) string

Htmlunquote 实体符号解释为HTML

func IntersectionSets

func IntersectionSets(fora []string, forb []string) []string

IntersectionSets 交集

func IsContainsSets

func IsContainsSets(values []string, ivalue string) bool

IsContainsSets if is contains then return true

func IsExist

func IsExist(path string) bool

IsExist 当文件或目录存在时返回真

func IsFile

func IsFile(file string) bool

IsFile 当为目录或文件不存在时返回假

func IsLeapYear

func IsLeapYear(year int) bool

IsLeapYear check whether a year is leay

func IsLocal

func IsLocal(path string) bool

IsLocal : if is local path then return true

func IsSendMail

func IsSendMail() (isSendMail bool)

func IsSpider

func IsSpider(userAgent string) bool

检查是否为搜索引擎爬虫

func Local2url

func Local2url(path string) string

Local2url is turn the local path to url

func MD5

func MD5(s string) string

MD5 对字符串进行md5哈希, 返回32位小写md5结果

func MD5(s string) string {
	h := md5.New()
	io.WriteString(h, s)
	return fmt.Sprintf("%x", h.Sum(nil))
}

func MD5to16

func MD5to16(s string) string

MD5to16 对字符串进行md5哈希, 返回16位小写md5结果

func MakeThumbnails

func MakeThumbnails(localpath string) (thumbnails string, thumbnailslarge string, thumbnailsmedium string, thumbnailssmall string, err error)

MakeThumbnails 对指定图片文件进行缩略处理

func Markdown

func Markdown(md string) template.HTML

Markdown 转换markdown为html模板对象

func Markdown2Byte

func Markdown2Byte(md string) []byte

Markdown2Byte md内容转为bytes

func Markdown2String

func Markdown2String(md string) string

Markdown2String md内容转为string

func Markdown2Text

func Markdown2Text(md string) string

Markdown2Text md内容转为text

func MarkdownByPongo2

func MarkdownByPongo2(in *pongo2.Value) *pongo2.Value

func Metric

func Metric(n int64) string

Metric 返回数字带数量级单位 千对k 百万对M 京对G

func MoveFile

func MoveFile(frompath string, topath string) error

MoveFile 移动文件到目标路径

func Nrand

func Nrand(n int64) float64

Nrand 标准正态分布随机整数,n为随机个数,从0开始

func ObjPolicy

func ObjPolicy() *bluemonday.Policy

ObjPolicy 对用户生成内容进行过滤

func OrderKey

func OrderKey(FUid, SUid int64) string

OrderKey 对key进行排序拼接

func PHA

func PHA(m image.Image) string

func PKCS5Padding

func PKCS5Padding(ciphertext []byte, blockSize int) []byte

PKCS5Padding PKCS5 补码

func PKCS5UnPadding

func PKCS5UnPadding(origData []byte) []byte

PKCS5UnPadding PKCS5 去除补码

func PKCS7Pad

func PKCS7Pad(data []byte) []byte

PKCS7Pad pads an byte array to be a multiple of 16 http://tools.ietf.org/html/rfc5652#section-6.3

func PKCS7Padding

func PKCS7Padding(data []byte) []byte

PKCS7Padding PKCS7补码

func PKCS7UnPadding

func PKCS7UnPadding(data []byte) []byte

PKCS7UnPadding 去除PKCS7补码

func PKCS7Unpad

func PKCS7Unpad(data []byte) []byte

PKCS7Unpad removes any potential PKCS7 padding added.

func Pages

func Pages(totalRecords int64, page int64, pageSize int64) (pages int64, pageOutput int64, beginNum int64, endNum int64, offset int64)

Pages : 分页计算函数

func Pagesbar

func Pagesbar(url string, keyword string, resultsMax int64, pages int64, page int64, beginNum int64, endNum int64, style int64) (output template.HTML)

Pagesbar : is for the html template

func PhaCompare

func PhaCompare(path1 string, path2 string) (int, error)

指纹比较

func PingFile

func PingFile(url string) bool

PingFile 检测远端文件i是否可用

func PostFile

func PostFile(filepath string, actionurl string, fieldname string) (*http.Response, error)

PostFile 以POST方式发送内容到 web 端

func PrintError

func PrintError()

PrintError 打印错误

func PurePolicy

func PurePolicy() *bluemonday.Policy

PurePolicy 返回纯Policy对象

func Qhot

func Qhot(Qviews, Qanswers, Qscore, Ascores, Created, ReplyTime int64) float64

func QhotAScore

func QhotAScore(ups int64, downs int64) int64

func QhotQScore

func QhotQScore(ups int64, downs int64) int64

func QhotVote

func QhotVote(ups int64, downs int64) int64

func RangeRand

func RangeRand(n int) int

RangeRand 生成规定范围内的整数 设置起始数字范围,0开始,n截止

func Rename

func Rename(file string, to string) error

Rename 重命名文件

func Resample

func Resample(m image.Image, r image.Rectangle, w, h int) image.Image

重新取样返回一个重采样本的图像切片r的副本。 返回的图像具有宽度w和高度h。 Resample returns a resampled copy of the image slice r of m. The returned image has width w and height h.

func Resize

func Resize(m image.Image, r image.Rectangle, w, h int) image.Image

调整传回的图像片的R米的缩放副本。 返回的图像具有宽度w和高度h。 Resize returns a scaled copy of the image slice r of m. The returned image has width w and height h.

func Rex

func Rex(text string, iregexp string) (b bool)

Rex 如果文本符合正则表达式则返回真

func Round

func Round(val float64, prec int) float64

Round 函数对浮点数进行四舍五入 语法 round(val,prec) 参数 val 规定要舍入的数字。 prec 规定小数点后的位数

func RsaAesReceivingPacket

func RsaAesReceivingPacket(decrypt bool, hash string, status string, content []byte, aesPublicKey string, rsaPublicKey []byte, rsaPrivateKey []byte) ([]byte, error)

RsaAesReceivingPacket 接受加密数据包

func RsaAesSendPacket

func RsaAesSendPacket(encrypt bool, status string, actionurl string, content string, aesKey string, aesPublicKey string, rsaPublicKey []byte) (*http.Response, error)

RsaAesSendPacket 发送报文 是否加密 HTTP状态 动作URL 数据内容 RSA公匙

func RsaDecrypt

func RsaDecrypt(ciphertext []byte, privateKey []byte) ([]byte, error)

RsaDecrypt RSA解密

func RsaEncrypt

func RsaEncrypt(origData []byte, publicKey []byte) ([]byte, error)

RsaEncrypt RSA加密

func S2T

func S2T(str string) string

func SHA1

func SHA1(s string) string

SHA1 对字符串进行sha1哈希, 返回42位小写sha1结果

func Score

func Score(ups int64, downs int64) int64

reddit 排序算法

func SendEmail

func SendEmail(to, subject, body, mailtype string) error

func SendMail

func SendMail(user, password, host, to, subject, body, mailtype string) error

* * user : example@example.com login smtp server user * password: xxxxx login smtp server password * host: smtp.example.com:port smtp.163.com:25 * to: example@example.com;example1@163.com;example2@sina.com.cn;... * subject:The subject of mail * body: The content of mail * mailtype: mail type html or text

func SendPacket

func SendPacket(status string, actionurl string, content string, ckJar *cookiejar.Jar) (*http.Response, error)

SendPacket 发送报文

func SetJsonCOMEncrypt

func SetJsonCOMEncrypt(status int64, msg string, data interface{}) (string, error)

func SetSuffix

func SetSuffix(content string, str string) string

SetSuffix 设置后缀

func SmcTimeSince

func SmcTimeSince(timeAt time.Time) string

SmcTimeSince is format for time

func Split

func Split(content string, str string) []string

Split 分割tags imgs等自行用特定符号组合的字符串为列表

func SplitByPongo2

func SplitByPongo2(in *pongo2.Value, splitor *pongo2.Value) *pongo2.Value

func StandardURLsPolicy

func StandardURLsPolicy() *bluemonday.Policy

StandardURLsPolicy 返回标准URL Policy对象

func Str2Ans

func Str2Ans(str string) int64

func Str2HTML

func Str2HTML(raw string) template.HTML

Str2HTML 转换html文本为html模板对象

func StrLen

func StrLen(str string) int64

func StrOne

func StrOne(str string, where int64) string

func StrPos

func StrPos(str string, sep string) int64

func String2Time

func String2Time(strtime string) (time.Time, error)

String2Time 时间字符串转换为时间类型

func String2UnixNano

func String2UnixNano(strtime string) (int64, error)

String2UnixNano 时间字符串转换为纳秒级的时间戳

func StringNewRand

func StringNewRand(len int) string

StringNewRand : gen new random string

func StringNewUUID

func StringNewUUID() string

StringNewUUID generates a new UUID based on version 4.

func StringToUTF16

func StringToUTF16(s string) []uint16

StringToUTF16 字符串转换来unit16

func Substr

func Substr(strin string, start, length int, symbol string) string

Substr 截取字符

func SymmetricDifferenceSets

func SymmetricDifferenceSets(fora []string, forb []string) []string

SymmetricDifferenceSets 对称差=并集-交集 即是 并集和交集的差集就是对称差

func T2S

func T2S(str string) string

func Tag4Video

func Tag4Video(input string) string

Tag4Video 获取视频标签对内的视频网址

func Theme

func Theme() (theme string)

func ThisDate

func ThisDate() int64

ThisDate 获取今天的开始点

func ThisHour

func ThisHour() int64

ThisHour 获取这个小时的开始点

func ThisMonth

func ThisMonth() int64

ThisMonth 获取这月的开始点

func ThisWeek

func ThisWeek() int64

ThisWeek 获取这周的开始点

func ThisYear

func ThisYear() int64

ThisYear 获取今年的开始点

func Thumbnail

func Thumbnail(mode string, inputFile string, outputFile string, outputSize string, outputAlign string, background string) error

Thumbnail 对图片进行缩略处理

func TimeSince

func TimeSince(timestamp int64) string

TimeSince : 微博时间格式化显示 timestamp,标准时间戳

func TouchFile

func TouchFile(path string)

TouchFile : create file of blank

func URL2local

func URL2local(path string) string

URL2local is turn the url to local path

func UnionSets

func UnionSets(fora []string, forb []string) []string

UnionSets 并集

func Unix2Time

func Unix2Time(s int64, timeLayout string) string

Unix2Time 转换时间戳为时间字符串 单位为秒

func Unix2TimeByPongo2

func Unix2TimeByPongo2(in *pongo2.Value, timeLayout *pongo2.Value) *pongo2.Value

func UnixNS2Time

func UnixNS2Time(ns int64, timeLayout string) string

UnixNS2Time 转换时间戳为时间字符串 单位为纳秒

func Unlink(file string) error

Unlink 删除文件

func ValidateHash

func ValidateHash(hashed string, input_password string) bool

func VerifyUserfile

func VerifyUserfile(path string, usr string) bool

VerifyUserfile verify the user file

func VideoTags

func VideoTags(in string) string

VideoTags 调用you-get命令对视频标签进行解析

func Watermark

func Watermark(watermarkFile string, inputFile string, outputFile string, outputAlign string) error

Watermark 对输入的图片文件作水印处理

func WriteFile

func WriteFile(filepath string, content string) error

WriteFile 按指定路径写入内容作为文件

func ZeroPadding

func ZeroPadding(ciphertext []byte, blockSize int) []byte

ZeroPadding 零式补码

func ZeroUnPadding

func ZeroUnPadding(origData []byte) []byte

ZeroUnPadding 零式去补码

Types

type FileRepos

type FileRepos []repository

FileRepos file operations

func (FileRepos) Len

func (r FileRepos) Len() int

func (FileRepos) Less

func (r FileRepos) Less(i, j int) bool

func (FileRepos) Swap

func (r FileRepos) Swap(i, j int)

Jump to

Keyboard shortcuts

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