diff --git a/Gopkg.lock b/Gopkg.lock index de54310a..24bd3afc 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -271,6 +271,12 @@ packages = ["."] revision = "eb3733d160e74a9c7e442f435eb3bea458e1d19f" +[[projects]] + branch = "master" + name = "github.com/Shopify/goreferrer" + packages = ["."] + revision = "aad8439df3bf67adb025382ee2e5f46a72fc9456" + [solve-meta] analyzer-name = "dep" analyzer-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 41b80165..a0baeae3 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -77,3 +77,7 @@ [[constraint]] branch = "v2" name = "gopkg.in/yaml.v2" + +[[constraint]] + branch = "master" + name = "github.com/Shopify/goreferrer" \ No newline at end of file diff --git a/README.md b/README.md index 60de9f3d..3a2d3b95 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,45 @@ func main() { id: 1234; page: 1; name: manu; message: this_is_great ``` +### Extract Referer + +```go +package main + +import ( + "github.com/kataras/iris" + "github.com/kataras/iris/context" +) + +func main() { + app := iris.New() + + app.Get("/", func(ctx context.Context) /* or iris.Context, it's the same for Go 1.9+. */ { + + // request header "referer" or url parameter "referer". + r := ctx.GetReferrer() + switch r.Type { + case context.ReferrerSearch: + ctx.Writef("Search %s: %s\n", r.Label, r.Query) + ctx.Writef("Google: %s\n", r.GoogleType) + case context.ReferrerSocial: + ctx.Writef("Social %s\n", r.Label) + case context.ReferrerIndirect: + ctx.Writef("Indirect: %s\n", r.URL) + } + }) + + app.Run(iris.Addr(":8080")) +} +``` + +How to `curl`: + +```bash +curl http://localhost:8080?referer=https://twitter.com/Xinterio/status/1023566830974251008 +curl http://localhost:8080?referer=https://www.google.com/search?q=Top+6+golang+web+frameworks&oq=Top+6+golang+web+frameworks +``` + ### Upload files - [single file upload](_examples/http_request/upload-file/main.go) diff --git a/_examples/README.md b/_examples/README.md index c281b5ff..084f1b50 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -384,6 +384,7 @@ You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [her - [Read Custom via Unmarshaler](http_request/read-custom-via-unmarshaler/main.go) - [Upload/Read File](http_request/upload-file/main.go) - [Upload multiple files with an easy way](http_request/upload-files/main.go) +- [Extract referrer from "referer" header or URL query parameter](http_request/extract-referer/main.go) **NEW** > The `context.Request()` returns the same *http.Request you already know, these examples show some places where the Context uses this object. Besides that you can use it as you did before iris. diff --git a/_examples/http_request/extract-referer/main.go b/_examples/http_request/extract-referer/main.go new file mode 100644 index 00000000..8c14e54e --- /dev/null +++ b/_examples/http_request/extract-referer/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "github.com/kataras/iris" + "github.com/kataras/iris/context" +) + +func main() { + app := iris.New() + + app.Get("/", func(ctx context.Context) /* or iris.Context, it's the same for Go 1.9+. */ { + // GetReferrer extracts and returns the information from the "Referer" header as specified + // in https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy or by the URL query parameter "referer". + r := ctx.GetReferrer() + switch r.Type { + case context.ReferrerSearch: + ctx.Writef("Search %s: %s\n", r.Label, r.Query) + ctx.Writef("Google: %s\n", r.GoogleType) + case context.ReferrerSocial: + ctx.Writef("Social %s\n", r.Label) + case context.ReferrerIndirect: + ctx.Writef("Indirect: %s\n", r.URL) + } + }) + + // http://localhost:8080?referer=https://twitter.com/Xinterio/status/1023566830974251008 + // http://localhost:8080?referer=https://www.google.com/search?q=Top+6+golang+web+frameworks&oq=Top+6+golang+web+frameworks + app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed), iris.WithoutVersionChecker) +} diff --git a/context/context.go b/context/context.go index c6923c37..624cb293 100644 --- a/context/context.go +++ b/context/context.go @@ -21,15 +21,16 @@ import ( "sync/atomic" "time" + "github.com/kataras/iris/core/errors" + "github.com/kataras/iris/core/memstore" + + "github.com/Shopify/goreferrer" "github.com/fatih/structs" formbinder "github.com/iris-contrib/formBinder" "github.com/json-iterator/go" "github.com/microcosm-cc/bluemonday" "gopkg.in/russross/blackfriday.v2" "gopkg.in/yaml.v2" - - "github.com/kataras/iris/core/errors" - "github.com/kataras/iris/core/memstore" ) type ( @@ -445,6 +446,10 @@ type Context interface { // // Keep note that this checks the "User-Agent" request header. IsMobile() bool + // GetReferrer extracts and returns the information from the "Referer" header as specified + // in https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy + // or by the URL query parameter "referer". + GetReferrer() Referrer // +------------------------------------------------------------+ // | Headers helpers | // +------------------------------------------------------------+ @@ -1687,6 +1692,84 @@ func (ctx *context) IsMobile() bool { return isMobileRegex.MatchString(s) } +type ( + // Referrer contains the extracted information from the `GetReferrer` + // + // The structure contains struct tags for JSON, form, XML, YAML and TOML. + // Look the `GetReferrer() Referrer` and `goreferrer` external package. + Referrer struct { + Type ReferrerType `json:"type" form:"referrer_type" xml:"Type" yaml:"Type" toml:"Type"` + Label string `json:"label" form:"referrer_form" xml:"Label" yaml:"Label" toml:"Label"` + URL string `json:"url" form:"referrer_url" xml:"URL" yaml:"URL" toml:"URL"` + Subdomain string `json:"subdomain" form:"referrer_subdomain" xml:"Subdomain" yaml:"Subdomain" toml:"Subdomain"` + Domain string `json:"domain" form:"referrer_domain" xml:"Domain" yaml:"Domain" toml:"Domain"` + Tld string `json:"tld" form:"referrer_tld" xml:"Tld" yaml:"Tld" toml:"Tld"` + Path string `jsonn:"path" form:"referrer_path" xml:"Path" yaml:"Path" toml:"Path"` + Query string `json:"query" form:"referrer_query" xml:"Query" yaml:"Query" toml:"GoogleType"` + GoogleType ReferrerGoogleSearchType `json:"googleType" form:"referrer_google_type" xml:"GoogleType" yaml:"GoogleType" toml:"GoogleType"` + } + + // ReferrerType is the goreferrer enum for a referrer type (indirect, direct, email, search, social). + ReferrerType int + + // ReferrerGoogleSearchType is the goreferrer enum for a google search type (organic, adwords). + ReferrerGoogleSearchType int +) + +// Contains the available values of the goreferrer enums. +const ( + ReferrerInvalid ReferrerType = iota + ReferrerIndirect + ReferrerDirect + ReferrerEmail + ReferrerSearch + ReferrerSocial + + ReferrerNotGoogleSearch ReferrerGoogleSearchType = iota + ReferrerGoogleOrganicSearch + ReferrerGoogleAdwords +) + +func (gs ReferrerGoogleSearchType) String() string { + return goreferrer.GoogleSearchType(gs).String() +} + +func (r ReferrerType) String() string { + return goreferrer.ReferrerType(r).String() +} + +// unnecessary but good to know the default values upfront. +var emptyReferrer = Referrer{Type: ReferrerInvalid, GoogleType: ReferrerNotGoogleSearch} + +// GetReferrer extracts and returns the information from the "Referer" header as specified +// in https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy +// or by the URL query parameter "referer". +func (ctx *context) GetReferrer() Referrer { + // the underline net/http follows the https://tools.ietf.org/html/rfc7231#section-5.5.2, + // so there is nothing special left to do. + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy + refURL := ctx.GetHeader("Referer") + if refURL == "" { + refURL = ctx.URLParam("referer") + } + + if ref := goreferrer.DefaultRules.Parse(refURL); ref.Type > goreferrer.Invalid { + return Referrer{ + Type: ReferrerType(ref.Type), + Label: ref.Label, + URL: ref.URL, + Subdomain: ref.Subdomain, + Domain: ref.Domain, + Tld: ref.Tld, + Path: ref.Path, + Query: ref.Query, + GoogleType: ReferrerGoogleSearchType(ref.GoogleType), + } + } + + return emptyReferrer +} + // +------------------------------------------------------------+ // | Response Headers helpers | // +------------------------------------------------------------+