diff --git a/HISTORY.md b/HISTORY.md index fe9feea8..c2380d83 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -18,6 +18,26 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene **How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`. +# Th, 10 August 2017 | v8.2.2 + +No API Changes. + +- Implement [Google reCAPTCHA](middleware/recaptcha) middleware +- Fix [kataras/golog](https://github.com/kataras/golog) prints with colors on windows server 2012 while it shouldn't because its command line tool does not support 256bit colors +- Improve the updater by a custom self-updated back-end version checker, can be disabled by: + +```go +app.Run(iris.Addr(":8080"), iris.WithoutVersionChecker) +``` +Or +```go +app.Configure(iris.WithoutVersionChecker) +``` +Or +```go +app.Configure(iris.WithConfiguration(iris.Configuration{DisableVersionChecker:true})) +``` + # Tu, 08 August 2017 | v8.2.1 diff --git a/README.md b/README.md index 3f6e17f0..e5f11bc8 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Iris is a fast, simple and efficient micro web framework for Go. It provides a b ### 📑 Table of contents * [Installation](#-installation) -* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#tu-08-august-2017--v821) +* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#th-10-august-2017--v822) * [Learn](#-learn) * [HTTP Listening](_examples/#http-listening) * [Configuration](_examples/#configuration) @@ -319,7 +319,7 @@ Thank You for your trust! ### 📌 Version -Current: **8.2.1** +Current: **8.2.2** Each new release is pushed to the master. It stays there until the next version. When a next version is released then the previous version goes to its own branch with `gopkg.in` as its import path (and its own vendor folder), in order to keep it working "for-ever". diff --git a/VERSION b/VERSION index ce44b5bd..a0c98b53 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.2.1:https://github.com/kataras/iris/blob/master/HISTORY.md#tu-08-august-2017--v821 \ No newline at end of file +8.2.2:https://github.com/kataras/iris/blob/master/HISTORY.md#th-10-august-2017--v822 \ No newline at end of file diff --git a/configuration.go b/configuration.go index 980ec9e9..989dbf02 100644 --- a/configuration.go +++ b/configuration.go @@ -136,9 +136,9 @@ var WithoutInterruptHandler = func(app *Application) { app.config.DisableInterruptHandler = true } -// WithoutVersionCheck will disable the version checker and updater. -var WithoutVersionCheck = func(app *Application) { - app.config.DisableVersionCheck = true +// WithoutVersionChecker will disable the version checker and updater. +var WithoutVersionChecker = func(app *Application) { + app.config.DisableVersionChecker = true } // WithoutPathCorrection disables the PathCorrection setting. @@ -281,10 +281,10 @@ type Configuration struct { // Defaults to false. DisableInterruptHandler bool `yaml:"DisableInterruptHandler" toml:"DisableInterruptHandler"` - // DisableVersionCheck if true then process will be not be notified for any available updates. + // DisableVersionChecker if true then process will be not be notified for any available updates. // // Defaults to false. - DisableVersionCheck bool `yaml:"DisableVersionCheck" toml:"DisableVersionCheck"` + DisableVersionChecker bool `yaml:"DisableVersionChecker" toml:"DisableVersionChecker"` // DisablePathCorrection corrects and redirects the requested path to the registered path // for example, if /home/ path is requested but no handler for this Route found, @@ -607,7 +607,7 @@ func DefaultConfiguration() Configuration { return Configuration{ DisableStartupLog: false, DisableInterruptHandler: false, - DisableVersionCheck: false, + DisableVersionChecker: false, DisablePathCorrection: false, EnablePathEscape: false, FireMethodNotAllowed: false, diff --git a/core/netutil/client.go b/core/netutil/client.go new file mode 100644 index 00000000..4fc18c49 --- /dev/null +++ b/core/netutil/client.go @@ -0,0 +1,33 @@ +package netutil + +import ( + "net" + "net/http" + "time" + + "github.com/kataras/golog" +) + +// Client returns a new http.Client using +// the "timeout" for open connection and read-write operations. +func Client(timeout time.Duration) *http.Client { + transport := http.Transport{ + Dial: func(network string, addr string) (net.Conn, error) { + conn, err := net.DialTimeout(network, addr, timeout) + if err != nil { + golog.Debugf("%v", err) + return nil, err + } + if err = conn.SetDeadline(time.Now().Add(timeout)); err != nil { + golog.Debugf("%v", err) + } + return conn, err + }, + } + + client := &http.Client{ + Transport: &transport, + } + + return client +} diff --git a/doc.go b/doc.go index 63d95dd2..ecbc9139 100644 --- a/doc.go +++ b/doc.go @@ -35,7 +35,7 @@ Source code and other details for the project are available at GitHub: Current Version -8.2.1 +8.2.2 Installation diff --git a/iris.go b/iris.go index 0e67db39..363389d9 100644 --- a/iris.go +++ b/iris.go @@ -32,7 +32,7 @@ import ( const ( // Version is the current version number of the Iris Web Framework. - Version = "8.2.1" + Version = "8.2.2" ) // HTTP status codes as registered with IANA. @@ -648,7 +648,7 @@ func (app *Application) Run(serve Runner, withOrWithout ...Configurator) error { app.Configure(withOrWithout...) app.logger.Debugf("Application: running using %d host(s)", len(app.Hosts)+1) - if !app.config.DisableVersionCheck && app.logger.Printer.IsTerminal { + if !app.config.DisableVersionChecker && app.logger.Printer.IsTerminal { go CheckVersion() } diff --git a/middleware/recaptcha/recaptcha.go b/middleware/recaptcha/recaptcha.go new file mode 100644 index 00000000..192beb55 --- /dev/null +++ b/middleware/recaptcha/recaptcha.go @@ -0,0 +1,127 @@ +package recaptcha + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/url" + "time" + + "github.com/kataras/iris/context" + "github.com/kataras/iris/core/netutil" +) + +const ( + responseFormValue = "g-recaptcha-response" + apiURL = "https://www.google.com/recaptcha/api/siteverify" +) + +// Response is the google's recaptcha response as JSON. +type Response struct { + ChallengeTS time.Time `json:"challenge_ts"` + Hostname string `json:"hostname"` + ErrorCodes []string `json:"error-codes"` + Success bool `json:"success"` +} + +var client = netutil.Client(time.Duration(10 * time.Second)) + +// New accepts the google's recaptcha secret key and returns +// a middleware that verifies the request by sending a response to the google's API(V2-latest). +// Secret key can be obtained by https://www.google.com/recaptcha. +// +// Used for communication between your site and Google. Be sure to keep it a secret. +// +// Use `SiteVerify` to verify a request inside another handler if needed. +func New(secret string) context.Handler { + return func(ctx context.Context) { + if SiteFerify(ctx, secret).Success { + ctx.Next() + } + } +} + +// SiteFerify accepts context and the secret key(https://www.google.com/recaptcha) +// and returns the google's recaptcha response, if `response.Success` is true +// then validation passed. +// +// Use `New` for middleware use instead. +func SiteFerify(ctx context.Context, secret string) (response Response) { + generatedResponseID := ctx.FormValue("g-recaptcha-response") + if generatedResponseID == "" { + response.ErrorCodes = append(response.ErrorCodes, + "form value[g-recaptcha-response] is empty") + return + } + + r, err := client.PostForm(apiURL, + url.Values{ + "secret": {secret}, + "response": {generatedResponseID}, + // optional: let's no track our users "remoteip": {ctx.RemoteAddr()}, + }, + ) + + if err != nil { + response.ErrorCodes = append(response.ErrorCodes, err.Error()) + return + } + + body, err := ioutil.ReadAll(r.Body) + r.Body.Close() + if err != nil { + response.ErrorCodes = append(response.ErrorCodes, err.Error()) + return + } + + err = json.Unmarshal(body, &response) + if err != nil { + response.ErrorCodes = append(response.ErrorCodes, err.Error()) + return + } + + return response +} + +var recaptchaForm = `
+ +
+ +
` + +// GetFormHTML can be used on pages where empty form +// is enough to verify that the client is not a "robot". +// i.e: GetFormHTML("public_key", "/contact") +// will return form tag which imports the google API script, +// with a simple submit button where redirects to the +// "postActionRelativePath". +// +// The "postActionRelativePath" MUST use the `SiteVerify` or +// followed by the `New()`'s context.Handler (with the secret key this time) +// in order to validate if the recaptcha verified. +// +// The majority of applications will use a custom form, +// this function is here for ridiculous simple use cases. +// +// Example Code: +// +// Method: "POST" | Path: "/contact" +// func postContact(ctx context.Context) { +// // [...] +// response := recaptcha.SiteFerify(ctx, recaptchaSecret) +// +// if response.Success { +// // [your action here, i.e sendEmail(...)] +// } +// +// ctx.JSON(response) +// } +// +// Method: "GET" | Path: "/contact" +// func getContact(ctx context.Context) { +// // render the recaptcha form +// ctx.HTML(recaptcha.GetFormHTML(recaptchaPublic, "/contact")) +// } +func GetFormHTML(dataSiteKey string, postActionRelativePath string) string { + return fmt.Sprintf(recaptchaForm, postActionRelativePath, dataSiteKey) +} diff --git a/version.go b/version.go index 970c4edb..b4ecdafa 100644 --- a/version.go +++ b/version.go @@ -2,21 +2,21 @@ package iris import ( "bufio" + "encoding/json" "io/ioutil" - "net" - "net/http" + "net/url" "os" "os/exec" "strings" "sync" "time" - "github.com/hashicorp/go-version" "github.com/kataras/golog" + "github.com/kataras/iris/core/netutil" ) const ( - versionURL = "https://raw.githubusercontent.com/kataras/iris/master/VERSION" + versionURL = "http://iris-go.com/version" updateCmd = "go get -u -f -v github.com/kataras/iris" ) @@ -29,28 +29,16 @@ func CheckVersion() { }) } +type versionInfo struct { + Version string `json:"version"` + ChangelogURL string `json:"changelog_url"` + UpdateAvailable bool `json:"update_available"` +} + func checkVersion() { + client := netutil.Client(time.Duration(15 * time.Second)) + r, err := client.PostForm(versionURL, url.Values{"current_version": {Version}}) - // open connection and read/write timeouts - timeout := time.Duration(10 * time.Second) - - transport := http.Transport{ - Dial: func(network string, addr string) (net.Conn, error) { - conn, err := net.DialTimeout(network, addr, timeout) - if err != nil { - golog.Debugf("%v", err) - return nil, err - } - conn.SetDeadline(time.Now().Add(timeout)) // skip error - return conn, nil - }, - } - - client := http.Client{ - Transport: &transport, - } - - r, err := client.Get(versionURL) if err != nil { golog.Debugf("%v", err) return @@ -68,53 +56,29 @@ func checkVersion() { return } - var ( - fetchedVersion = string(b) - changelogURL string - ) - - // 8.2.1:https://github.com/kataras/iris/blob/master/HISTORY.md#tu-08-august-2017--v821 - if idx := strings.IndexByte(fetchedVersion, ':'); idx > 0 { - changelogURL = fetchedVersion[idx+1:] - fetchedVersion = fetchedVersion[0:idx] - } - - latestVersion, err := version.NewVersion(fetchedVersion) - if err != nil { - golog.Debugf("while parsing latest version: %v", err) + v := new(versionInfo) + if err := json.Unmarshal(b, v); err != nil { + golog.Debugf("error while unmarshal the response body: %v", err) return } - currentVersion, err := version.NewVersion(Version) - if err != nil { - golog.Debugf("while parsing current version: %v", err) + if !v.UpdateAvailable { return } - if currentVersion.GreaterThan(latestVersion) { - golog.Debugf("current version is greater than latest version, report as bug") - return + format := "A more recent version has been found[%s > %s].\n" + + if v.ChangelogURL != "" { + format += "Release notes: %s\n" } - if currentVersion.Equal(latestVersion) { - return - } + format += "Update now?[%s]: " // currentVersion.LessThan(latestVersion) + updaterYesInput := [...]string{"y", "yes"} - var updaterYesInput = [...]string{"y", "yes"} - - text := "A more recent version has been found[%s > %s].\n" - - if changelogURL != "" { - text += "Release notes: %s\n" - } - - text += "Update now?[%s]: " - - golog.Warnf(text, - latestVersion.String(), currentVersion.String(), - changelogURL, + golog.Warnf(format, v.Version, Version, + v.ChangelogURL, updaterYesInput[0]+"/n") silent := false @@ -126,6 +90,7 @@ func checkVersion() { if !silent { if sc.Scan() { inputText := sc.Text() + for _, s := range updaterYesInput { if inputText == s { shouldUpdate = true @@ -146,6 +111,6 @@ func checkVersion() { return } - golog.Infof("Update process finished, current version: %s.\nManual restart is required to apply the changes.\n", latestVersion.String()) + golog.Infof("Update process finished.\nManual restart is required to apply the changes.\n") } }