update vendored deps and clean up makefile

This commit is contained in:
Ian Kent 2017-04-16 19:47:34 +01:00
parent a6aee3ed1b
commit 286e8f43f1
No known key found for this signature in database
GPG key ID: CE5AC689AF520A48
59 changed files with 1881 additions and 646 deletions

View file

@ -1,6 +1,6 @@
VERSION=0.2.1 VERSION=0.2.1
all: deps fmt combined all: fmt combined
combined: combined:
go install . go install .
@ -11,24 +11,6 @@ release: tag release-deps dockerhub
fmt: fmt:
go fmt ./... go fmt ./...
deps:
go get github.com/mailhog/MailHog-Server
go get github.com/mailhog/MailHog-UI
go get github.com/mailhog/mhsendmail
cd ../MailHog-UI; make bindata
go get github.com/mailhog/http
go get github.com/ian-kent/go-log/log
go get github.com/ian-kent/envconf
go get github.com/ian-kent/goose
go get github.com/ian-kent/linkio
go get github.com/jteeuwen/go-bindata/...
go get gopkg.in/mgo.v2
# added to fix travis issues
go get golang.org/x/crypto/bcrypt
test-deps:
go get github.com/smartystreets/goconvey
release-deps: release-deps:
go get github.com/mitchellh/gox go get github.com/mitchellh/gox
@ -50,4 +32,4 @@ tag:
cd ../smtp; git tag -a -m 'v${VERSION}' v${VERSION} && git push origin v${VERSION} cd ../smtp; git tag -a -m 'v${VERSION}' v${VERSION} && git push origin v${VERSION}
cd ../storage; git tag -a -m 'v${VERSION}' v${VERSION} && git push origin v${VERSION} cd ../storage; git tag -a -m 'v${VERSION}' v${VERSION} && git push origin v${VERSION}
.PNONY: all combined release fmt deps test-deps release-deps pull tag .PNONY: all combined release fmt release-deps pull tag

View file

@ -4,4 +4,7 @@ context
gorilla/context is a general purpose registry for global request variables. gorilla/context is a general purpose registry for global request variables.
> Note: gorilla/context, having been born well before `context.Context` existed, does not play well
> with the shallow copying of the request that [`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext) (added to net/http Go 1.7 onwards) performs. You should either use *just* gorilla/context, or moving forward, the new `http.Request.Context()`.
Read the full documentation here: http://www.gorillatoolkit.org/pkg/context Read the full documentation here: http://www.gorillatoolkit.org/pkg/context

View file

@ -5,6 +5,12 @@
/* /*
Package context stores values shared during a request lifetime. Package context stores values shared during a request lifetime.
Note: gorilla/context, having been born well before `context.Context` existed,
does not play well > with the shallow copying of the request that
[`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext)
(added to net/http Go 1.7 onwards) performs. You should either use *just*
gorilla/context, or moving forward, the new `http.Request.Context()`.
For example, a router can set variables extracted from the URL and later For example, a router can set variables extracted from the URL and later
application handlers can access those values, or it can be used to store application handlers can access those values, or it can be used to store
sessions values to be saved at the end of a request. There are several sessions values to be saved at the end of a request. There are several

View file

@ -1,19 +1,43 @@
mux gorilla/mux
=== ===
[![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux) [![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux)
[![Build Status](https://travis-ci.org/gorilla/mux.svg?branch=master)](https://travis-ci.org/gorilla/mux) [![Build Status](https://travis-ci.org/gorilla/mux.svg?branch=master)](https://travis-ci.org/gorilla/mux)
![Gorilla Logo](http://www.gorillatoolkit.org/static/images/gorilla-icon-64.png)
http://www.gorillatoolkit.org/pkg/mux http://www.gorillatoolkit.org/pkg/mux
Package `gorilla/mux` implements a request router and dispatcher. Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to
their respective handler.
The name mux stands for "HTTP request multiplexer". Like the standard `http.ServeMux`, `mux.Router` matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are: The name mux stands for "HTTP request multiplexer". Like the standard `http.ServeMux`, `mux.Router` matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are:
* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.
* Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers. * Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.
* URL hosts and paths can have variables with an optional regular expression. * URL hosts and paths can have variables with an optional regular expression.
* Registered URLs can be built, or "reversed", which helps maintaining references to resources. * Registered URLs can be built, or "reversed", which helps maintaining references to resources.
* Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching. * Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching.
* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.
---
* [Install](#install)
* [Examples](#examples)
* [Matching Routes](#matching-routes)
* [Static Files](#static-files)
* [Registered URLs](#registered-urls)
* [Full Example](#full-example)
---
## Install
With a [correctly configured](https://golang.org/doc/install#testing) Go toolchain:
```sh
go get -u github.com/gorilla/mux
```
## Examples
Let's start registering a couple of URL paths and handlers: Let's start registering a couple of URL paths and handlers:
@ -47,6 +71,8 @@ category := vars["category"]
And this is all you need to know about the basic usage. More advanced options are explained below. And this is all you need to know about the basic usage. More advanced options are explained below.
### Matching Routes
Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables: Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables:
```go ```go
@ -118,7 +144,7 @@ Then register routes in the subrouter:
```go ```go
s.HandleFunc("/products/", ProductsHandler) s.HandleFunc("/products/", ProductsHandler)
s.HandleFunc("/products/{key}", ProductHandler) s.HandleFunc("/products/{key}", ProductHandler)
s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) s.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
``` ```
The three URL paths we registered above will only be tested if the domain is `www.example.com`, because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route. The three URL paths we registered above will only be tested if the domain is `www.example.com`, because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route.
@ -138,6 +164,37 @@ s.HandleFunc("/{key}/", ProductHandler)
s.HandleFunc("/{key}/details", ProductDetailsHandler) s.HandleFunc("/{key}/details", ProductDetailsHandler)
``` ```
### Static Files
Note that the path provided to `PathPrefix()` represents a "wildcard": calling
`PathPrefix("/static/").Handler(...)` means that the handler will be passed any
request that matches "/static/*". This makes it easy to serve static files with mux:
```go
func main() {
var dir string
flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
flag.Parse()
r := mux.NewRouter()
// This will serve files under http://localhost:8000/static/<filename>
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
srv := &http.Server{
Handler: r,
Addr: "127.0.0.1:8000",
// Good practice: enforce timeouts for servers you create!
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}
```
### Registered URLs
Now let's see how to build registered URLs. Now let's see how to build registered URLs.
Routes can be named. All routes that define a name can have their URLs built, or "reversed". We define a name calling `Name()` on a route. For example: Routes can be named. All routes that define a name can have their URLs built, or "reversed". We define a name calling `Name()` on a route. For example:
@ -219,7 +276,7 @@ package main
import ( import (
"net/http" "net/http"
"log"
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
@ -233,7 +290,7 @@ func main() {
r.HandleFunc("/", YourHandler) r.HandleFunc("/", YourHandler)
// Bind to a port and pass our router in // Bind to a port and pass our router in
http.ListenAndServe(":8000", r) log.Fatal(http.ListenAndServe(":8000", r))
} }
``` ```

26
vendor/github.com/gorilla/mux/context_gorilla.go generated vendored Normal file
View file

@ -0,0 +1,26 @@
// +build !go1.7
package mux
import (
"net/http"
"github.com/gorilla/context"
)
func contextGet(r *http.Request, key interface{}) interface{} {
return context.Get(r, key)
}
func contextSet(r *http.Request, key, val interface{}) *http.Request {
if val == nil {
return r
}
context.Set(r, key, val)
return r
}
func contextClear(r *http.Request) {
context.Clear(r)
}

24
vendor/github.com/gorilla/mux/context_native.go generated vendored Normal file
View file

@ -0,0 +1,24 @@
// +build go1.7
package mux
import (
"context"
"net/http"
)
func contextGet(r *http.Request, key interface{}) interface{} {
return r.Context().Value(key)
}
func contextSet(r *http.Request, key, val interface{}) *http.Request {
if val == nil {
return r
}
return r.WithContext(context.WithValue(r.Context(), key, val))
}
func contextClear(r *http.Request) {
return
}

25
vendor/github.com/gorilla/mux/doc.go generated vendored
View file

@ -136,6 +136,31 @@ the inner routes use it as base for their paths:
// "/products/{key}/details" // "/products/{key}/details"
s.HandleFunc("/{key}/details", ProductDetailsHandler) s.HandleFunc("/{key}/details", ProductDetailsHandler)
Note that the path provided to PathPrefix() represents a "wildcard": calling
PathPrefix("/static/").Handler(...) means that the handler will be passed any
request that matches "/static/*". This makes it easy to serve static files with mux:
func main() {
var dir string
flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
flag.Parse()
r := mux.NewRouter()
// This will serve files under http://localhost:8000/static/<filename>
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
srv := &http.Server{
Handler: r,
Addr: "127.0.0.1:8000",
// Good practice: enforce timeouts for servers you create!
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}
Now let's see how to build registered URLs. Now let's see how to build registered URLs.
Routes can be named. All routes that define a name can have their URLs built, Routes can be named. All routes that define a name can have their URLs built,

79
vendor/github.com/gorilla/mux/mux.go generated vendored
View file

@ -10,8 +10,7 @@ import (
"net/http" "net/http"
"path" "path"
"regexp" "regexp"
"strings"
"github.com/gorilla/context"
) )
// NewRouter returns a new router instance. // NewRouter returns a new router instance.
@ -50,8 +49,12 @@ type Router struct {
strictSlash bool strictSlash bool
// See Router.SkipClean(). This defines the flag for new routes. // See Router.SkipClean(). This defines the flag for new routes.
skipClean bool skipClean bool
// If true, do not clear the request context after handling the request // If true, do not clear the request context after handling the request.
// This has no effect when go1.7+ is used, since the context is stored
// on the request itself.
KeepContext bool KeepContext bool
// see Router.UseEncodedPath(). This defines a flag for all routes.
useEncodedPath bool
} }
// Match matches registered routes against the request. // Match matches registered routes against the request.
@ -76,8 +79,12 @@ func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
// mux.Vars(request). // mux.Vars(request).
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if !r.skipClean { if !r.skipClean {
path := req.URL.Path
if r.useEncodedPath {
path = getPath(req)
}
// Clean path to canonical form and redirect. // Clean path to canonical form and redirect.
if p := cleanPath(req.URL.Path); p != req.URL.Path { if p := cleanPath(path); p != path {
// Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query. // Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
// This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue: // This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue:
@ -95,14 +102,14 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var handler http.Handler var handler http.Handler
if r.Match(req, &match) { if r.Match(req, &match) {
handler = match.Handler handler = match.Handler
setVars(req, match.Vars) req = setVars(req, match.Vars)
setCurrentRoute(req, match.Route) req = setCurrentRoute(req, match.Route)
} }
if handler == nil { if handler == nil {
handler = http.NotFoundHandler() handler = http.NotFoundHandler()
} }
if !r.KeepContext { if !r.KeepContext {
defer context.Clear(req) defer contextClear(req)
} }
handler.ServeHTTP(w, req) handler.ServeHTTP(w, req)
} }
@ -150,6 +157,21 @@ func (r *Router) SkipClean(value bool) *Router {
return r return r
} }
// UseEncodedPath tells the router to match the encoded original path
// to the routes.
// For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to".
// This behavior has the drawback of needing to match routes against
// r.RequestURI instead of r.URL.Path. Any modifications (such as http.StripPrefix)
// to r.URL.Path will not affect routing when this flag is on and thus may
// induce unintended behavior.
//
// If not called, the router will match the unencoded path to the routes.
// For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to"
func (r *Router) UseEncodedPath() *Router {
r.useEncodedPath = true
return r
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// parentRoute // parentRoute
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -187,7 +209,7 @@ func (r *Router) buildVars(m map[string]string) map[string]string {
// NewRoute registers an empty route. // NewRoute registers an empty route.
func (r *Router) NewRoute() *Route { func (r *Router) NewRoute() *Route {
route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean} route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean, useEncodedPath: r.useEncodedPath}
r.routes = append(r.routes, route) r.routes = append(r.routes, route)
return route return route
} }
@ -285,6 +307,9 @@ func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
if err == SkipRouter { if err == SkipRouter {
continue continue
} }
if err != nil {
return err
}
for _, sr := range t.matchers { for _, sr := range t.matchers {
if h, ok := sr.(*Router); ok { if h, ok := sr.(*Router); ok {
err := h.walk(walkFn, ancestors) err := h.walk(walkFn, ancestors)
@ -325,7 +350,7 @@ const (
// Vars returns the route variables for the current request, if any. // Vars returns the route variables for the current request, if any.
func Vars(r *http.Request) map[string]string { func Vars(r *http.Request) map[string]string {
if rv := context.Get(r, varsKey); rv != nil { if rv := contextGet(r, varsKey); rv != nil {
return rv.(map[string]string) return rv.(map[string]string)
} }
return nil return nil
@ -337,28 +362,46 @@ func Vars(r *http.Request) map[string]string {
// after the handler returns, unless the KeepContext option is set on the // after the handler returns, unless the KeepContext option is set on the
// Router. // Router.
func CurrentRoute(r *http.Request) *Route { func CurrentRoute(r *http.Request) *Route {
if rv := context.Get(r, routeKey); rv != nil { if rv := contextGet(r, routeKey); rv != nil {
return rv.(*Route) return rv.(*Route)
} }
return nil return nil
} }
func setVars(r *http.Request, val interface{}) { func setVars(r *http.Request, val interface{}) *http.Request {
if val != nil { return contextSet(r, varsKey, val)
context.Set(r, varsKey, val)
}
} }
func setCurrentRoute(r *http.Request, val interface{}) { func setCurrentRoute(r *http.Request, val interface{}) *http.Request {
if val != nil { return contextSet(r, routeKey, val)
context.Set(r, routeKey, val)
}
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Helpers // Helpers
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// getPath returns the escaped path if possible; doing what URL.EscapedPath()
// which was added in go1.5 does
func getPath(req *http.Request) string {
if req.RequestURI != "" {
// Extract the path from RequestURI (which is escaped unlike URL.Path)
// as detailed here as detailed in https://golang.org/pkg/net/url/#URL
// for < 1.5 server side workaround
// http://localhost/path/here?v=1 -> /path/here
path := req.RequestURI
path = strings.TrimPrefix(path, req.URL.Scheme+`://`)
path = strings.TrimPrefix(path, req.URL.Host)
if i := strings.LastIndex(path, "?"); i > -1 {
path = path[:i]
}
if i := strings.LastIndex(path, "#"); i > -1 {
path = path[:i]
}
return path
}
return req.URL.Path
}
// cleanPath returns the canonical path for p, eliminating . and .. elements. // cleanPath returns the canonical path for p, eliminating . and .. elements.
// Borrowed from the net/http package. // Borrowed from the net/http package.
func cleanPath(p string) string { func cleanPath(p string) string {

View file

@ -24,7 +24,7 @@ import (
// Previously we accepted only Python-like identifiers for variable // Previously we accepted only Python-like identifiers for variable
// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that // names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that
// name and pattern can't be empty, and names can't contain a colon. // name and pattern can't be empty, and names can't contain a colon.
func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash bool) (*routeRegexp, error) { func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash, useEncodedPath bool) (*routeRegexp, error) {
// Check if it is well-formed. // Check if it is well-formed.
idxs, errBraces := braceIndices(tpl) idxs, errBraces := braceIndices(tpl)
if errBraces != nil { if errBraces != nil {
@ -115,6 +115,7 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash
matchHost: matchHost, matchHost: matchHost,
matchQuery: matchQuery, matchQuery: matchQuery,
strictSlash: strictSlash, strictSlash: strictSlash,
useEncodedPath: useEncodedPath,
regexp: reg, regexp: reg,
reverse: reverse.String(), reverse: reverse.String(),
varsN: varsN, varsN: varsN,
@ -133,6 +134,9 @@ type routeRegexp struct {
matchQuery bool matchQuery bool
// The strictSlash value defined on the route, but disabled if PathPrefix was used. // The strictSlash value defined on the route, but disabled if PathPrefix was used.
strictSlash bool strictSlash bool
// Determines whether to use encoded path from getPath function or unencoded
// req.URL.Path for path matching
useEncodedPath bool
// Expanded regexp. // Expanded regexp.
regexp *regexp.Regexp regexp *regexp.Regexp
// Reverse template. // Reverse template.
@ -149,8 +153,11 @@ func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
if r.matchQuery { if r.matchQuery {
return r.matchQueryString(req) return r.matchQueryString(req)
} }
path := req.URL.Path
return r.regexp.MatchString(req.URL.Path) if r.useEncodedPath {
path = getPath(req)
}
return r.regexp.MatchString(path)
} }
return r.regexp.MatchString(getHost(req)) return r.regexp.MatchString(getHost(req))
@ -253,14 +260,18 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route)
extractVars(host, matches, v.host.varsN, m.Vars) extractVars(host, matches, v.host.varsN, m.Vars)
} }
} }
path := req.URL.Path
if r.useEncodedPath {
path = getPath(req)
}
// Store path variables. // Store path variables.
if v.path != nil { if v.path != nil {
matches := v.path.regexp.FindStringSubmatchIndex(req.URL.Path) matches := v.path.regexp.FindStringSubmatchIndex(path)
if len(matches) > 0 { if len(matches) > 0 {
extractVars(req.URL.Path, matches, v.path.varsN, m.Vars) extractVars(path, matches, v.path.varsN, m.Vars)
// Check if we should redirect. // Check if we should redirect.
if v.path.strictSlash { if v.path.strictSlash {
p1 := strings.HasSuffix(req.URL.Path, "/") p1 := strings.HasSuffix(path, "/")
p2 := strings.HasSuffix(v.path.template, "/") p2 := strings.HasSuffix(v.path.template, "/")
if p1 != p2 { if p1 != p2 {
u, _ := url.Parse(req.URL.String()) u, _ := url.Parse(req.URL.String())
@ -299,14 +310,7 @@ func getHost(r *http.Request) string {
} }
func extractVars(input string, matches []int, names []string, output map[string]string) { func extractVars(input string, matches []int, names []string, output map[string]string) {
matchesCount := 0 for i, name := range names {
prevEnd := -1 output[name] = input[matches[2*i+2]:matches[2*i+3]]
for i := 2; i < len(matches) && matchesCount < len(names); i += 2 {
if prevEnd < matches[i+1] {
value := input[matches[i]:matches[i+1]]
output[names[matchesCount]] = value
prevEnd = matches[i+1]
matchesCount++
}
} }
} }

View file

@ -29,6 +29,8 @@ type Route struct {
// If true, when the path pattern is "/path//to", accessing "/path//to" // If true, when the path pattern is "/path//to", accessing "/path//to"
// will not redirect // will not redirect
skipClean bool skipClean bool
// If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
useEncodedPath bool
// If true, this route never matches: it is only used to build URLs. // If true, this route never matches: it is only used to build URLs.
buildOnly bool buildOnly bool
// The name used to build URLs. // The name used to build URLs.
@ -158,7 +160,7 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery
tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
} }
} }
rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash) rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash, r.useEncodedPath)
if err != nil { if err != nil {
return err return err
} }

View file

@ -3,6 +3,9 @@
Gorilla WebSocket is a [Go](http://golang.org/) implementation of the Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol.
[![Build Status](https://travis-ci.org/gorilla/websocket.svg?branch=master)](https://travis-ci.org/gorilla/websocket)
[![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket)
### Documentation ### Documentation
* [API Reference](http://godoc.org/github.com/gorilla/websocket) * [API Reference](http://godoc.org/github.com/gorilla/websocket)
@ -43,7 +46,7 @@ subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.2">pings</a> and receive <a href="https://tools.ietf.org/html/rfc6455#section-5.5.3">pongs</a></td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td>No</td></tr> <tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.2">pings</a> and receive <a href="https://tools.ietf.org/html/rfc6455#section-5.5.3">pongs</a></td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td>No</td></tr>
<tr><td>Get the <a href="https://tools.ietf.org/html/rfc6455#section-5.6">type</a> of a received data message</td><td>Yes</td><td>Yes, see note 2</td></tr> <tr><td>Get the <a href="https://tools.ietf.org/html/rfc6455#section-5.6">type</a> of a received data message</td><td>Yes</td><td>Yes, see note 2</td></tr>
<tr><td colspan="3">Other Features</tr></td> <tr><td colspan="3">Other Features</tr></td>
<tr><td>Limit size of received message</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.SetReadLimit">Yes</a></td><td><a href="https://code.google.com/p/go/issues/detail?id=5082">No</a></td></tr> <tr><td><a href="https://tools.ietf.org/html/rfc7692">Compression Extensions</a></td><td>Experimental</td><td>No</td></tr>
<tr><td>Read message using io.Reader</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextReader">Yes</a></td><td>No, see note 3</td></tr> <tr><td>Read message using io.Reader</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextReader">Yes</a></td><td>No, see note 3</td></tr>
<tr><td>Write message using io.WriteCloser</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextWriter">Yes</a></td><td>No, see note 3</td></tr> <tr><td>Write message using io.WriteCloser</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextWriter">Yes</a></td><td>No, see note 3</td></tr>
</table> </table>

View file

@ -23,6 +23,8 @@ import (
// invalid. // invalid.
var ErrBadHandshake = errors.New("websocket: bad handshake") var ErrBadHandshake = errors.New("websocket: bad handshake")
var errInvalidCompression = errors.New("websocket: invalid compression negotiation")
// NewClient creates a new client connection using the given net connection. // NewClient creates a new client connection using the given net connection.
// The URL u specifies the host and request URI. Use requestHeader to specify // The URL u specifies the host and request URI. Use requestHeader to specify
// the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies // the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies
@ -64,12 +66,24 @@ type Dialer struct {
// HandshakeTimeout specifies the duration for the handshake to complete. // HandshakeTimeout specifies the duration for the handshake to complete.
HandshakeTimeout time.Duration HandshakeTimeout time.Duration
// Input and output buffer sizes. If the buffer size is zero, then a // ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
// default value of 4096 is used. // size is zero, then a useful default size is used. The I/O buffer sizes
// do not limit the size of the messages that can be sent or received.
ReadBufferSize, WriteBufferSize int ReadBufferSize, WriteBufferSize int
// Subprotocols specifies the client's requested subprotocols. // Subprotocols specifies the client's requested subprotocols.
Subprotocols []string Subprotocols []string
// EnableCompression specifies if the client should attempt to negotiate
// per message compression (RFC 7692). Setting this value to true does not
// guarantee that compression will be supported. Currently only "no context
// takeover" modes are supported.
EnableCompression bool
// Jar specifies the cookie jar.
// If Jar is nil, cookies are not sent in requests and ignored
// in responses.
Jar http.CookieJar
} }
var errMalformedURL = errors.New("malformed ws or wss URL") var errMalformedURL = errors.New("malformed ws or wss URL")
@ -83,7 +97,6 @@ func parseURL(s string) (*url.URL, error) {
// //
// ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ] // ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
// wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ] // wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
var u url.URL var u url.URL
switch { switch {
case strings.HasPrefix(s, "ws://"): case strings.HasPrefix(s, "ws://"):
@ -193,6 +206,13 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
Host: u.Host, Host: u.Host,
} }
// Set the cookies present in the cookie jar of the dialer
if d.Jar != nil {
for _, cookie := range d.Jar.Cookies(u) {
req.AddCookie(cookie)
}
}
// Set the request headers using the capitalization for names and values in // Set the request headers using the capitalization for names and values in
// RFC examples. Although the capitalization shouldn't matter, there are // RFC examples. Although the capitalization shouldn't matter, there are
// servers that depend on it. The Header.Set method is not used because the // servers that depend on it. The Header.Set method is not used because the
@ -214,6 +234,7 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
k == "Connection" || k == "Connection" ||
k == "Sec-Websocket-Key" || k == "Sec-Websocket-Key" ||
k == "Sec-Websocket-Version" || k == "Sec-Websocket-Version" ||
k == "Sec-Websocket-Extensions" ||
(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0): (k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
return nil, nil, errors.New("websocket: duplicate header not allowed: " + k) return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
default: default:
@ -221,6 +242,10 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
} }
} }
if d.EnableCompression {
req.Header.Set("Sec-Websocket-Extensions", "permessage-deflate; server_no_context_takeover; client_no_context_takeover")
}
hostPort, hostNoPort := hostPortNoPort(u) hostPort, hostNoPort := hostPortNoPort(u)
var proxyURL *url.URL var proxyURL *url.URL
@ -324,6 +349,13 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
if d.Jar != nil {
if rc := resp.Cookies(); len(rc) > 0 {
d.Jar.SetCookies(u, rc)
}
}
if resp.StatusCode != 101 || if resp.StatusCode != 101 ||
!strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") || !strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") ||
!strings.EqualFold(resp.Header.Get("Connection"), "upgrade") || !strings.EqualFold(resp.Header.Get("Connection"), "upgrade") ||
@ -337,6 +369,20 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
return nil, resp, ErrBadHandshake return nil, resp, ErrBadHandshake
} }
for _, ext := range parseExtensions(resp.Header) {
if ext[""] != "permessage-deflate" {
continue
}
_, snct := ext["server_no_context_takeover"]
_, cnct := ext["client_no_context_takeover"]
if !snct || !cnct {
return nil, resp, errInvalidCompression
}
conn.newCompressionWriter = compressNoContextTakeover
conn.newDecompressionReader = decompressNoContextTakeover
break
}
resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{})) resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol") conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")
@ -344,32 +390,3 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
netConn = nil // to avoid close in defer. netConn = nil // to avoid close in defer.
return conn, resp, nil return conn, resp, nil
} }
// cloneTLSConfig clones all public fields except the fields
// SessionTicketsDisabled and SessionTicketKey. This avoids copying the
// sync.Mutex in the sync.Once and makes it safe to call cloneTLSConfig on a
// config in active use.
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
if cfg == nil {
return &tls.Config{}
}
return &tls.Config{
Rand: cfg.Rand,
Time: cfg.Time,
Certificates: cfg.Certificates,
NameToCertificate: cfg.NameToCertificate,
GetCertificate: cfg.GetCertificate,
RootCAs: cfg.RootCAs,
NextProtos: cfg.NextProtos,
ServerName: cfg.ServerName,
ClientAuth: cfg.ClientAuth,
ClientCAs: cfg.ClientCAs,
InsecureSkipVerify: cfg.InsecureSkipVerify,
CipherSuites: cfg.CipherSuites,
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
ClientSessionCache: cfg.ClientSessionCache,
MinVersion: cfg.MinVersion,
MaxVersion: cfg.MaxVersion,
CurvePreferences: cfg.CurvePreferences,
}
}

16
vendor/github.com/gorilla/websocket/client_clone.go generated vendored Normal file
View file

@ -0,0 +1,16 @@
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.8
package websocket
import "crypto/tls"
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
if cfg == nil {
return &tls.Config{}
}
return cfg.Clone()
}

View file

@ -0,0 +1,38 @@
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.8
package websocket
import "crypto/tls"
// cloneTLSConfig clones all public fields except the fields
// SessionTicketsDisabled and SessionTicketKey. This avoids copying the
// sync.Mutex in the sync.Once and makes it safe to call cloneTLSConfig on a
// config in active use.
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
if cfg == nil {
return &tls.Config{}
}
return &tls.Config{
Rand: cfg.Rand,
Time: cfg.Time,
Certificates: cfg.Certificates,
NameToCertificate: cfg.NameToCertificate,
GetCertificate: cfg.GetCertificate,
RootCAs: cfg.RootCAs,
NextProtos: cfg.NextProtos,
ServerName: cfg.ServerName,
ClientAuth: cfg.ClientAuth,
ClientCAs: cfg.ClientCAs,
InsecureSkipVerify: cfg.InsecureSkipVerify,
CipherSuites: cfg.CipherSuites,
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
ClientSessionCache: cfg.ClientSessionCache,
MinVersion: cfg.MinVersion,
MaxVersion: cfg.MaxVersion,
CurvePreferences: cfg.CurvePreferences,
}
}

View file

@ -1,4 +1,4 @@
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. // Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -9,22 +9,48 @@ import (
"errors" "errors"
"io" "io"
"strings" "strings"
"sync"
) )
func decompressNoContextTakeover(r io.Reader) io.Reader { const (
minCompressionLevel = -2 // flate.HuffmanOnly not defined in Go < 1.6
maxCompressionLevel = flate.BestCompression
defaultCompressionLevel = 1
)
var (
flateWriterPools [maxCompressionLevel - minCompressionLevel + 1]sync.Pool
flateReaderPool = sync.Pool{New: func() interface{} {
return flate.NewReader(nil)
}}
)
func decompressNoContextTakeover(r io.Reader) io.ReadCloser {
const tail = const tail =
// Add four bytes as specified in RFC // Add four bytes as specified in RFC
"\x00\x00\xff\xff" + "\x00\x00\xff\xff" +
// Add final block to squelch unexpected EOF error from flate reader. // Add final block to squelch unexpected EOF error from flate reader.
"\x01\x00\x00\xff\xff" "\x01\x00\x00\xff\xff"
return flate.NewReader(io.MultiReader(r, strings.NewReader(tail))) fr, _ := flateReaderPool.Get().(io.ReadCloser)
fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil)
return &flateReadWrapper{fr}
} }
func compressNoContextTakeover(w io.WriteCloser) (io.WriteCloser, error) { func isValidCompressionLevel(level int) bool {
return minCompressionLevel <= level && level <= maxCompressionLevel
}
func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteCloser {
p := &flateWriterPools[level-minCompressionLevel]
tw := &truncWriter{w: w} tw := &truncWriter{w: w}
fw, err := flate.NewWriter(tw, 3) fw, _ := p.Get().(*flate.Writer)
return &flateWrapper{fw: fw, tw: tw}, err if fw == nil {
fw, _ = flate.NewWriter(tw, level)
} else {
fw.Reset(tw)
}
return &flateWriteWrapper{fw: fw, tw: tw, p: p}
} }
// truncWriter is an io.Writer that writes all but the last four bytes of the // truncWriter is an io.Writer that writes all but the last four bytes of the
@ -63,17 +89,26 @@ func (w *truncWriter) Write(p []byte) (int, error) {
return n + nn, err return n + nn, err
} }
type flateWrapper struct { type flateWriteWrapper struct {
fw *flate.Writer fw *flate.Writer
tw *truncWriter tw *truncWriter
p *sync.Pool
} }
func (w *flateWrapper) Write(p []byte) (int, error) { func (w *flateWriteWrapper) Write(p []byte) (int, error) {
if w.fw == nil {
return 0, errWriteClosed
}
return w.fw.Write(p) return w.fw.Write(p)
} }
func (w *flateWrapper) Close() error { func (w *flateWriteWrapper) Close() error {
if w.fw == nil {
return errWriteClosed
}
err1 := w.fw.Flush() err1 := w.fw.Flush()
w.p.Put(w.fw)
w.fw = nil
if w.tw.p != [4]byte{0, 0, 0xff, 0xff} { if w.tw.p != [4]byte{0, 0, 0xff, 0xff} {
return errors.New("websocket: internal error, unexpected bytes at end of flate stream") return errors.New("websocket: internal error, unexpected bytes at end of flate stream")
} }
@ -83,3 +118,31 @@ func (w *flateWrapper) Close() error {
} }
return err2 return err2
} }
type flateReadWrapper struct {
fr io.ReadCloser
}
func (r *flateReadWrapper) Read(p []byte) (int, error) {
if r.fr == nil {
return 0, io.ErrClosedPipe
}
n, err := r.fr.Read(p)
if err == io.EOF {
// Preemptively place the reader back in the pool. This helps with
// scenarios where the application does not call NextReader() soon after
// this final read.
r.Close()
}
return n, err
}
func (r *flateReadWrapper) Close() error {
if r.fr == nil {
return io.ErrClosedPipe
}
err := r.fr.Close()
flateReaderPool.Put(r.fr)
r.fr = nil
return err
}

View file

@ -13,6 +13,7 @@ import (
"math/rand" "math/rand"
"net" "net"
"strconv" "strconv"
"sync"
"time" "time"
"unicode/utf8" "unicode/utf8"
) )
@ -180,6 +181,11 @@ var (
errInvalidControlFrame = errors.New("websocket: invalid control frame") errInvalidControlFrame = errors.New("websocket: invalid control frame")
) )
func newMaskKey() [4]byte {
n := rand.Uint32()
return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)}
}
func hideTempErr(err error) error { func hideTempErr(err error) error {
if e, ok := err.(net.Error); ok && e.Temporary() { if e, ok := err.(net.Error); ok && e.Temporary() {
err = &netError{msg: e.Error(), timeout: e.Timeout()} err = &netError{msg: e.Error(), timeout: e.Timeout()}
@ -218,42 +224,28 @@ func isValidReceivedCloseCode(code int) bool {
return validReceivedCloseCodes[code] || (code >= 3000 && code <= 4999) return validReceivedCloseCodes[code] || (code >= 3000 && code <= 4999)
} }
func maskBytes(key [4]byte, pos int, b []byte) int { // The Conn type represents a WebSocket connection.
for i := range b {
b[i] ^= key[pos&3]
pos++
}
return pos & 3
}
func newMaskKey() [4]byte {
n := rand.Uint32()
return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)}
}
// Conn represents a WebSocket connection.
type Conn struct { type Conn struct {
conn net.Conn conn net.Conn
isServer bool isServer bool
subprotocol string subprotocol string
// Write fields // Write fields
mu chan bool // used as mutex to protect write to conn and closeSent mu chan bool // used as mutex to protect write to conn
closeSent bool // whether close message was sent
writeErr error
writeBuf []byte // frame is constructed in this buffer. writeBuf []byte // frame is constructed in this buffer.
writePos int // end of data in writeBuf.
writeFrameType int // type of the current frame.
writeDeadline time.Time writeDeadline time.Time
messageWriter *messageWriter // the current low-level message writer
writer io.WriteCloser // the current writer returned to the application writer io.WriteCloser // the current writer returned to the application
isWriting bool // for best-effort concurrent write detection isWriting bool // for best-effort concurrent write detection
writeErrMu sync.Mutex
writeErr error
enableWriteCompression bool enableWriteCompression bool
writeCompress bool // whether next call to flushFrame should set RSV1 compressionLevel int
newCompressionWriter func(io.WriteCloser) (io.WriteCloser, error) newCompressionWriter func(io.WriteCloser, int) io.WriteCloser
// Read fields // Read fields
reader io.ReadCloser // the current reader returned to the application
readErr error readErr error
br *bufio.Reader br *bufio.Reader
readRemaining int64 // bytes remaining in current frame. readRemaining int64 // bytes remaining in current frame.
@ -264,38 +256,83 @@ type Conn struct {
readMaskKey [4]byte readMaskKey [4]byte
handlePong func(string) error handlePong func(string) error
handlePing func(string) error handlePing func(string) error
handleClose func(int, string) error
readErrCount int readErrCount int
messageReader *messageReader // the current low-level reader messageReader *messageReader // the current low-level reader
readDecompress bool // whether last read frame had RSV1 set readDecompress bool // whether last read frame had RSV1 set
newDecompressionReader func(io.Reader) io.Reader newDecompressionReader func(io.Reader) io.ReadCloser
} }
func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int) *Conn { func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int) *Conn {
return newConnBRW(conn, isServer, readBufferSize, writeBufferSize, nil)
}
type writeHook struct {
p []byte
}
func (wh *writeHook) Write(p []byte) (int, error) {
wh.p = p
return len(p), nil
}
func newConnBRW(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int, brw *bufio.ReadWriter) *Conn {
mu := make(chan bool, 1) mu := make(chan bool, 1)
mu <- true mu <- true
var br *bufio.Reader
if readBufferSize == 0 && brw != nil && brw.Reader != nil {
// Reuse the supplied bufio.Reader if the buffer has a useful size.
// This code assumes that peek on a reader returns
// bufio.Reader.buf[:0].
brw.Reader.Reset(conn)
if p, err := brw.Reader.Peek(0); err == nil && cap(p) >= 256 {
br = brw.Reader
}
}
if br == nil {
if readBufferSize == 0 { if readBufferSize == 0 {
readBufferSize = defaultReadBufferSize readBufferSize = defaultReadBufferSize
} }
if readBufferSize < maxControlFramePayloadSize { if readBufferSize < maxControlFramePayloadSize {
readBufferSize = maxControlFramePayloadSize readBufferSize = maxControlFramePayloadSize
} }
br = bufio.NewReaderSize(conn, readBufferSize)
}
var writeBuf []byte
if writeBufferSize == 0 && brw != nil && brw.Writer != nil {
// Use the bufio.Writer's buffer if the buffer has a useful size. This
// code assumes that bufio.Writer.buf[:1] is passed to the
// bufio.Writer's underlying writer.
var wh writeHook
brw.Writer.Reset(&wh)
brw.Writer.WriteByte(0)
brw.Flush()
if cap(wh.p) >= maxFrameHeaderSize+256 {
writeBuf = wh.p[:cap(wh.p)]
}
}
if writeBuf == nil {
if writeBufferSize == 0 { if writeBufferSize == 0 {
writeBufferSize = defaultWriteBufferSize writeBufferSize = defaultWriteBufferSize
} }
writeBuf = make([]byte, writeBufferSize+maxFrameHeaderSize)
}
c := &Conn{ c := &Conn{
isServer: isServer, isServer: isServer,
br: bufio.NewReaderSize(conn, readBufferSize), br: br,
conn: conn, conn: conn,
mu: mu, mu: mu,
readFinal: true, readFinal: true,
writeBuf: make([]byte, writeBufferSize+maxFrameHeaderSize), writeBuf: writeBuf,
writeFrameType: noFrame,
writePos: maxFrameHeaderSize,
enableWriteCompression: true, enableWriteCompression: true,
compressionLevel: defaultCompressionLevel,
} }
c.SetCloseHandler(nil)
c.SetPingHandler(nil) c.SetPingHandler(nil)
c.SetPongHandler(nil) c.SetPongHandler(nil)
return c return c
@ -323,29 +360,40 @@ func (c *Conn) RemoteAddr() net.Addr {
// Write methods // Write methods
func (c *Conn) writeFatal(err error) error {
err = hideTempErr(err)
c.writeErrMu.Lock()
if c.writeErr == nil {
c.writeErr = err
}
c.writeErrMu.Unlock()
return err
}
func (c *Conn) write(frameType int, deadline time.Time, bufs ...[]byte) error { func (c *Conn) write(frameType int, deadline time.Time, bufs ...[]byte) error {
<-c.mu <-c.mu
defer func() { c.mu <- true }() defer func() { c.mu <- true }()
if c.closeSent { c.writeErrMu.Lock()
return ErrCloseSent err := c.writeErr
} else if frameType == CloseMessage { c.writeErrMu.Unlock()
c.closeSent = true if err != nil {
return err
} }
c.conn.SetWriteDeadline(deadline) c.conn.SetWriteDeadline(deadline)
for _, buf := range bufs { for _, buf := range bufs {
if len(buf) > 0 { if len(buf) > 0 {
n, err := c.conn.Write(buf) _, err := c.conn.Write(buf)
if n != len(buf) {
// Close on partial write.
c.conn.Close()
}
if err != nil { if err != nil {
return err return c.writeFatal(err)
} }
} }
} }
if frameType == CloseMessage {
c.writeFatal(ErrCloseSent)
}
return nil return nil
} }
@ -394,18 +442,41 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er
} }
defer func() { c.mu <- true }() defer func() { c.mu <- true }()
if c.closeSent { c.writeErrMu.Lock()
return ErrCloseSent err := c.writeErr
} else if messageType == CloseMessage { c.writeErrMu.Unlock()
c.closeSent = true if err != nil {
return err
} }
c.conn.SetWriteDeadline(deadline) c.conn.SetWriteDeadline(deadline)
n, err := c.conn.Write(buf) _, err = c.conn.Write(buf)
if n != 0 && n != len(buf) { if err != nil {
c.conn.Close() return c.writeFatal(err)
} }
return hideTempErr(err) if messageType == CloseMessage {
c.writeFatal(ErrCloseSent)
}
return err
}
func (c *Conn) prepWrite(messageType int) error {
// Close previous writer if not already closed by the application. It's
// probably better to return an error in this situation, but we cannot
// change this without breaking existing applications.
if c.writer != nil {
c.writer.Close()
c.writer = nil
}
if !isControl(messageType) && !isData(messageType) {
return errBadWriteOpCode
}
c.writeErrMu.Lock()
err := c.writeErr
c.writeErrMu.Unlock()
return err
} }
// NextWriter returns a writer for the next message to send. The writer's Close // NextWriter returns a writer for the next message to send. The writer's Close
@ -414,64 +485,60 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er
// There can be at most one open writer on a connection. NextWriter closes the // There can be at most one open writer on a connection. NextWriter closes the
// previous writer if the application has not already done so. // previous writer if the application has not already done so.
func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) { func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
if c.writeErr != nil { if err := c.prepWrite(messageType); err != nil {
return nil, c.writeErr
}
// Close previous writer if not already closed by the application. It's
// probably better to return an error in this situation, but we cannot
// change this without breaking existing applications.
if c.writer != nil {
err := c.writer.Close()
if err != nil {
return nil, err return nil, err
} }
mw := &messageWriter{
c: c,
frameType: messageType,
pos: maxFrameHeaderSize,
} }
c.writer = mw
if !isControl(messageType) && !isData(messageType) {
return nil, errBadWriteOpCode
}
c.writeFrameType = messageType
c.messageWriter = &messageWriter{c}
var w io.WriteCloser = c.messageWriter
if c.newCompressionWriter != nil && c.enableWriteCompression && isData(messageType) { if c.newCompressionWriter != nil && c.enableWriteCompression && isData(messageType) {
c.writeCompress = true w := c.newCompressionWriter(c.writer, c.compressionLevel)
var err error mw.compress = true
w, err = c.newCompressionWriter(w) c.writer = w
if err != nil {
c.writer.Close()
return nil, err
}
} }
return c.writer, nil
}
return w, nil type messageWriter struct {
c *Conn
compress bool // whether next call to flushFrame should set RSV1
pos int // end of data in writeBuf.
frameType int // type of the current frame.
err error
}
func (w *messageWriter) fatal(err error) error {
if w.err != nil {
w.err = err
w.c.writer = nil
}
return err
} }
// flushFrame writes buffered data and extra as a frame to the network. The // flushFrame writes buffered data and extra as a frame to the network. The
// final argument indicates that this is the last frame in the message. // final argument indicates that this is the last frame in the message.
func (c *Conn) flushFrame(final bool, extra []byte) error { func (w *messageWriter) flushFrame(final bool, extra []byte) error {
length := c.writePos - maxFrameHeaderSize + len(extra) c := w.c
length := w.pos - maxFrameHeaderSize + len(extra)
// Check for invalid control frames. // Check for invalid control frames.
if isControl(c.writeFrameType) && if isControl(w.frameType) &&
(!final || length > maxControlFramePayloadSize) { (!final || length > maxControlFramePayloadSize) {
c.messageWriter = nil return w.fatal(errInvalidControlFrame)
c.writer = nil
c.writeFrameType = noFrame
c.writePos = maxFrameHeaderSize
return errInvalidControlFrame
} }
b0 := byte(c.writeFrameType) b0 := byte(w.frameType)
if final { if final {
b0 |= finalBit b0 |= finalBit
} }
if c.writeCompress { if w.compress {
b0 |= rsv1Bit b0 |= rsv1Bit
} }
c.writeCompress = false w.compress = false
b1 := byte(0) b1 := byte(0)
if !c.isServer { if !c.isServer {
@ -504,10 +571,9 @@ func (c *Conn) flushFrame(final bool, extra []byte) error {
if !c.isServer { if !c.isServer {
key := newMaskKey() key := newMaskKey()
copy(c.writeBuf[maxFrameHeaderSize-4:], key[:]) copy(c.writeBuf[maxFrameHeaderSize-4:], key[:])
maskBytes(key, 0, c.writeBuf[maxFrameHeaderSize:c.writePos]) maskBytes(key, 0, c.writeBuf[maxFrameHeaderSize:w.pos])
if len(extra) > 0 { if len(extra) > 0 {
c.writeErr = errors.New("websocket: internal error, extra used in client mode") return c.writeFatal(errors.New("websocket: internal error, extra used in client mode"))
return c.writeErr
} }
} }
@ -520,44 +586,35 @@ func (c *Conn) flushFrame(final bool, extra []byte) error {
} }
c.isWriting = true c.isWriting = true
c.writeErr = c.write(c.writeFrameType, c.writeDeadline, c.writeBuf[framePos:c.writePos], extra) err := c.write(w.frameType, c.writeDeadline, c.writeBuf[framePos:w.pos], extra)
if !c.isWriting { if !c.isWriting {
panic("concurrent write to websocket connection") panic("concurrent write to websocket connection")
} }
c.isWriting = false c.isWriting = false
// Setup for next frame. if err != nil {
c.writePos = maxFrameHeaderSize return w.fatal(err)
c.writeFrameType = continuationFrame }
if final { if final {
c.messageWriter = nil
c.writer = nil c.writer = nil
c.writeFrameType = noFrame return nil
} }
return c.writeErr
}
type messageWriter struct{ c *Conn } // Setup for next frame.
w.pos = maxFrameHeaderSize
func (w *messageWriter) err() error { w.frameType = continuationFrame
c := w.c
if c.messageWriter != w {
return errWriteClosed
}
if c.writeErr != nil {
return c.writeErr
}
return nil return nil
} }
func (w *messageWriter) ncopy(max int) (int, error) { func (w *messageWriter) ncopy(max int) (int, error) {
n := len(w.c.writeBuf) - w.c.writePos n := len(w.c.writeBuf) - w.pos
if n <= 0 { if n <= 0 {
if err := w.c.flushFrame(false, nil); err != nil { if err := w.flushFrame(false, nil); err != nil {
return 0, err return 0, err
} }
n = len(w.c.writeBuf) - w.c.writePos n = len(w.c.writeBuf) - w.pos
} }
if n > max { if n > max {
n = max n = max
@ -566,13 +623,13 @@ func (w *messageWriter) ncopy(max int) (int, error) {
} }
func (w *messageWriter) Write(p []byte) (int, error) { func (w *messageWriter) Write(p []byte) (int, error) {
if err := w.err(); err != nil { if w.err != nil {
return 0, err return 0, w.err
} }
if len(p) > 2*len(w.c.writeBuf) && w.c.isServer { if len(p) > 2*len(w.c.writeBuf) && w.c.isServer {
// Don't buffer large messages. // Don't buffer large messages.
err := w.c.flushFrame(false, p) err := w.flushFrame(false, p)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -585,16 +642,16 @@ func (w *messageWriter) Write(p []byte) (int, error) {
if err != nil { if err != nil {
return 0, err return 0, err
} }
copy(w.c.writeBuf[w.c.writePos:], p[:n]) copy(w.c.writeBuf[w.pos:], p[:n])
w.c.writePos += n w.pos += n
p = p[n:] p = p[n:]
} }
return nn, nil return nn, nil
} }
func (w *messageWriter) WriteString(p string) (int, error) { func (w *messageWriter) WriteString(p string) (int, error) {
if err := w.err(); err != nil { if w.err != nil {
return 0, err return 0, w.err
} }
nn := len(p) nn := len(p)
@ -603,27 +660,27 @@ func (w *messageWriter) WriteString(p string) (int, error) {
if err != nil { if err != nil {
return 0, err return 0, err
} }
copy(w.c.writeBuf[w.c.writePos:], p[:n]) copy(w.c.writeBuf[w.pos:], p[:n])
w.c.writePos += n w.pos += n
p = p[n:] p = p[n:]
} }
return nn, nil return nn, nil
} }
func (w *messageWriter) ReadFrom(r io.Reader) (nn int64, err error) { func (w *messageWriter) ReadFrom(r io.Reader) (nn int64, err error) {
if err := w.err(); err != nil { if w.err != nil {
return 0, err return 0, w.err
} }
for { for {
if w.c.writePos == len(w.c.writeBuf) { if w.pos == len(w.c.writeBuf) {
err = w.c.flushFrame(false, nil) err = w.flushFrame(false, nil)
if err != nil { if err != nil {
break break
} }
} }
var n int var n int
n, err = r.Read(w.c.writeBuf[w.c.writePos:]) n, err = r.Read(w.c.writeBuf[w.pos:])
w.c.writePos += n w.pos += n
nn += int64(n) nn += int64(n)
if err != nil { if err != nil {
if err == io.EOF { if err == io.EOF {
@ -636,25 +693,57 @@ func (w *messageWriter) ReadFrom(r io.Reader) (nn int64, err error) {
} }
func (w *messageWriter) Close() error { func (w *messageWriter) Close() error {
if err := w.err(); err != nil { if w.err != nil {
return w.err
}
if err := w.flushFrame(true, nil); err != nil {
return err return err
} }
return w.c.flushFrame(true, nil) w.err = errWriteClosed
return nil
}
// WritePreparedMessage writes prepared message into connection.
func (c *Conn) WritePreparedMessage(pm *PreparedMessage) error {
frameType, frameData, err := pm.frame(prepareKey{
isServer: c.isServer,
compress: c.newCompressionWriter != nil && c.enableWriteCompression && isData(pm.messageType),
compressionLevel: c.compressionLevel,
})
if err != nil {
return err
}
if c.isWriting {
panic("concurrent write to websocket connection")
}
c.isWriting = true
err = c.write(frameType, c.writeDeadline, frameData, nil)
if !c.isWriting {
panic("concurrent write to websocket connection")
}
c.isWriting = false
return err
} }
// WriteMessage is a helper method for getting a writer using NextWriter, // WriteMessage is a helper method for getting a writer using NextWriter,
// writing the message and closing the writer. // writing the message and closing the writer.
func (c *Conn) WriteMessage(messageType int, data []byte) error { func (c *Conn) WriteMessage(messageType int, data []byte) error {
w, err := c.NextWriter(messageType)
if err != nil { if c.isServer && (c.newCompressionWriter == nil || !c.enableWriteCompression) {
// Fast path with no allocations and single frame.
if err := c.prepWrite(messageType); err != nil {
return err return err
} }
if _, ok := w.(*messageWriter); ok && c.isServer { mw := messageWriter{c: c, frameType: messageType, pos: maxFrameHeaderSize}
// Optimize write as a single frame. n := copy(c.writeBuf[mw.pos:], data)
n := copy(c.writeBuf[c.writePos:], data) mw.pos += n
c.writePos += n
data = data[n:] data = data[n:]
err = c.flushFrame(true, data) return mw.flushFrame(true, data)
}
w, err := c.NextWriter(messageType)
if err != nil {
return err return err
} }
if _, err = w.Write(data); err != nil { if _, err = w.Write(data); err != nil {
@ -799,11 +888,9 @@ func (c *Conn) advanceFrame() (int, error) {
return noFrame, err return noFrame, err
} }
case CloseMessage: case CloseMessage:
echoMessage := []byte{}
closeCode := CloseNoStatusReceived closeCode := CloseNoStatusReceived
closeText := "" closeText := ""
if len(payload) >= 2 { if len(payload) >= 2 {
echoMessage = payload[:2]
closeCode = int(binary.BigEndian.Uint16(payload)) closeCode = int(binary.BigEndian.Uint16(payload))
if !isValidReceivedCloseCode(closeCode) { if !isValidReceivedCloseCode(closeCode) {
return noFrame, c.handleProtocolError("invalid close code") return noFrame, c.handleProtocolError("invalid close code")
@ -813,7 +900,9 @@ func (c *Conn) advanceFrame() (int, error) {
return noFrame, c.handleProtocolError("invalid utf8 payload in close frame") return noFrame, c.handleProtocolError("invalid utf8 payload in close frame")
} }
} }
c.WriteControl(CloseMessage, echoMessage, time.Now().Add(writeWait)) if err := c.handleClose(closeCode, closeText); err != nil {
return noFrame, err
}
return noFrame, &CloseError{Code: closeCode, Text: closeText} return noFrame, &CloseError{Code: closeCode, Text: closeText}
} }
@ -836,6 +925,11 @@ func (c *Conn) handleProtocolError(message string) error {
// permanent. Once this method returns a non-nil error, all subsequent calls to // permanent. Once this method returns a non-nil error, all subsequent calls to
// this method return the same error. // this method return the same error.
func (c *Conn) NextReader() (messageType int, r io.Reader, err error) { func (c *Conn) NextReader() (messageType int, r io.Reader, err error) {
// Close previous reader, only relevant for decompression.
if c.reader != nil {
c.reader.Close()
c.reader = nil
}
c.messageReader = nil c.messageReader = nil
c.readLength = 0 c.readLength = 0
@ -848,11 +942,11 @@ func (c *Conn) NextReader() (messageType int, r io.Reader, err error) {
} }
if frameType == TextMessage || frameType == BinaryMessage { if frameType == TextMessage || frameType == BinaryMessage {
c.messageReader = &messageReader{c} c.messageReader = &messageReader{c}
var r io.Reader = c.messageReader c.reader = c.messageReader
if c.readDecompress { if c.readDecompress {
r = c.newDecompressionReader(r) c.reader = c.newDecompressionReader(c.reader)
} }
return frameType, r, nil return frameType, c.reader, nil
} }
} }
@ -914,6 +1008,10 @@ func (r *messageReader) Read(b []byte) (int, error) {
return 0, err return 0, err
} }
func (r *messageReader) Close() error {
return nil
}
// ReadMessage is a helper method for getting a reader using NextReader and // ReadMessage is a helper method for getting a reader using NextReader and
// reading from that reader to a buffer. // reading from that reader to a buffer.
func (c *Conn) ReadMessage() (messageType int, p []byte, err error) { func (c *Conn) ReadMessage() (messageType int, p []byte, err error) {
@ -941,6 +1039,38 @@ func (c *Conn) SetReadLimit(limit int64) {
c.readLimit = limit c.readLimit = limit
} }
// CloseHandler returns the current close handler
func (c *Conn) CloseHandler() func(code int, text string) error {
return c.handleClose
}
// SetCloseHandler sets the handler for close messages received from the peer.
// The code argument to h is the received close code or CloseNoStatusReceived
// if the close message is empty. The default close handler sends a close frame
// back to the peer.
//
// The application must read the connection to process close messages as
// described in the section on Control Frames above.
//
// The connection read methods return a CloseError when a close frame is
// received. Most applications should handle close messages as part of their
// normal error handling. Applications should only set a close handler when the
// application must perform some action before sending a close frame back to
// the peer.
func (c *Conn) SetCloseHandler(h func(code int, text string) error) {
if h == nil {
h = func(code int, text string) error {
message := []byte{}
if code != CloseNoStatusReceived {
message = FormatCloseMessage(code, "")
}
c.WriteControl(CloseMessage, message, time.Now().Add(writeWait))
return nil
}
}
c.handleClose = h
}
// PingHandler returns the current ping handler // PingHandler returns the current ping handler
func (c *Conn) PingHandler() func(appData string) error { func (c *Conn) PingHandler() func(appData string) error {
return c.handlePing return c.handlePing
@ -949,6 +1079,9 @@ func (c *Conn) PingHandler() func(appData string) error {
// SetPingHandler sets the handler for ping messages received from the peer. // SetPingHandler sets the handler for ping messages received from the peer.
// The appData argument to h is the PING frame application data. The default // The appData argument to h is the PING frame application data. The default
// ping handler sends a pong to the peer. // ping handler sends a pong to the peer.
//
// The application must read the connection to process ping messages as
// described in the section on Control Frames above.
func (c *Conn) SetPingHandler(h func(appData string) error) { func (c *Conn) SetPingHandler(h func(appData string) error) {
if h == nil { if h == nil {
h = func(message string) error { h = func(message string) error {
@ -972,6 +1105,9 @@ func (c *Conn) PongHandler() func(appData string) error {
// SetPongHandler sets the handler for pong messages received from the peer. // SetPongHandler sets the handler for pong messages received from the peer.
// The appData argument to h is the PONG frame application data. The default // The appData argument to h is the PONG frame application data. The default
// pong handler does nothing. // pong handler does nothing.
//
// The application must read the connection to process ping messages as
// described in the section on Control Frames above.
func (c *Conn) SetPongHandler(h func(appData string) error) { func (c *Conn) SetPongHandler(h func(appData string) error) {
if h == nil { if h == nil {
h = func(string) error { return nil } h = func(string) error { return nil }
@ -985,6 +1121,25 @@ func (c *Conn) UnderlyingConn() net.Conn {
return c.conn return c.conn
} }
// EnableWriteCompression enables and disables write compression of
// subsequent text and binary messages. This function is a noop if
// compression was not negotiated with the peer.
func (c *Conn) EnableWriteCompression(enable bool) {
c.enableWriteCompression = enable
}
// SetCompressionLevel sets the flate compression level for subsequent text and
// binary messages. This function is a noop if compression was not negotiated
// with the peer. See the compress/flate package for a description of
// compression levels.
func (c *Conn) SetCompressionLevel(level int) error {
if !isValidCompressionLevel(level) {
return errors.New("websocket: invalid compression level")
}
c.compressionLevel = level
return nil
}
// FormatCloseMessage formats closeCode and text as a WebSocket close message. // FormatCloseMessage formats closeCode and text as a WebSocket close message.
func FormatCloseMessage(closeCode int, text string) []byte { func FormatCloseMessage(closeCode int, text string) []byte {
buf := make([]byte, 2+len(text)) buf := make([]byte, 2+len(text))

View file

@ -118,9 +118,10 @@
// //
// Applications are responsible for ensuring that no more than one goroutine // Applications are responsible for ensuring that no more than one goroutine
// calls the write methods (NextWriter, SetWriteDeadline, WriteMessage, // calls the write methods (NextWriter, SetWriteDeadline, WriteMessage,
// WriteJSON) concurrently and that no more than one goroutine calls the read // WriteJSON, EnableWriteCompression, SetCompressionLevel) concurrently and
// methods (NextReader, SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, // that no more than one goroutine calls the read methods (NextReader,
// SetPingHandler) concurrently. // SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler)
// concurrently.
// //
// The Close and WriteControl methods can be called concurrently with all other // The Close and WriteControl methods can be called concurrently with all other
// methods. // methods.
@ -149,4 +150,31 @@
// The deprecated Upgrade function does not enforce an origin policy. It's the // The deprecated Upgrade function does not enforce an origin policy. It's the
// application's responsibility to check the Origin header before calling // application's responsibility to check the Origin header before calling
// Upgrade. // Upgrade.
//
// Compression EXPERIMENTAL
//
// Per message compression extensions (RFC 7692) are experimentally supported
// by this package in a limited capacity. Setting the EnableCompression option
// to true in Dialer or Upgrader will attempt to negotiate per message deflate
// support.
//
// var upgrader = websocket.Upgrader{
// EnableCompression: true,
// }
//
// If compression was successfully negotiated with the connection's peer, any
// message received in compressed form will be automatically decompressed.
// All Read methods will return uncompressed bytes.
//
// Per message compression of messages written to a connection can be enabled
// or disabled by calling the corresponding Conn method:
//
// conn.EnableWriteCompression(false)
//
// Currently this package does not support compression with "context takeover".
// This means that messages must be compressed and decompressed in isolation,
// without retaining sliding window or dictionary state across messages. For
// more details refer to RFC 7692.
//
// Use of compression is experimental and may result in decreased performance.
package websocket package websocket

55
vendor/github.com/gorilla/websocket/mask.go generated vendored Normal file
View file

@ -0,0 +1,55 @@
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in the
// LICENSE file.
// +build !appengine
package websocket
import "unsafe"
const wordSize = int(unsafe.Sizeof(uintptr(0)))
func maskBytes(key [4]byte, pos int, b []byte) int {
// Mask one byte at a time for small buffers.
if len(b) < 2*wordSize {
for i := range b {
b[i] ^= key[pos&3]
pos++
}
return pos & 3
}
// Mask one byte at a time to word boundary.
if n := int(uintptr(unsafe.Pointer(&b[0]))) % wordSize; n != 0 {
n = wordSize - n
for i := range b[:n] {
b[i] ^= key[pos&3]
pos++
}
b = b[n:]
}
// Create aligned word size key.
var k [wordSize]byte
for i := range k {
k[i] = key[(pos+i)&3]
}
kw := *(*uintptr)(unsafe.Pointer(&k))
// Mask one word at a time.
n := (len(b) / wordSize) * wordSize
for i := 0; i < n; i += wordSize {
*(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(i))) ^= kw
}
// Mask one byte at a time for remaining bytes.
b = b[n:]
for i := range b {
b[i] ^= key[pos&3]
pos++
}
return pos & 3
}

15
vendor/github.com/gorilla/websocket/mask_safe.go generated vendored Normal file
View file

@ -0,0 +1,15 @@
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in the
// LICENSE file.
// +build appengine
package websocket
func maskBytes(key [4]byte, pos int, b []byte) int {
for i := range b {
b[i] ^= key[pos&3]
pos++
}
return pos & 3
}

103
vendor/github.com/gorilla/websocket/prepared.go generated vendored Normal file
View file

@ -0,0 +1,103 @@
// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket
import (
"bytes"
"net"
"sync"
"time"
)
// PreparedMessage caches on the wire representations of a message payload.
// Use PreparedMessage to efficiently send a message payload to multiple
// connections. PreparedMessage is especially useful when compression is used
// because the CPU and memory expensive compression operation can be executed
// once for a given set of compression options.
type PreparedMessage struct {
messageType int
data []byte
err error
mu sync.Mutex
frames map[prepareKey]*preparedFrame
}
// prepareKey defines a unique set of options to cache prepared frames in PreparedMessage.
type prepareKey struct {
isServer bool
compress bool
compressionLevel int
}
// preparedFrame contains data in wire representation.
type preparedFrame struct {
once sync.Once
data []byte
}
// NewPreparedMessage returns an initialized PreparedMessage. You can then send
// it to connection using WritePreparedMessage method. Valid wire
// representation will be calculated lazily only once for a set of current
// connection options.
func NewPreparedMessage(messageType int, data []byte) (*PreparedMessage, error) {
pm := &PreparedMessage{
messageType: messageType,
frames: make(map[prepareKey]*preparedFrame),
data: data,
}
// Prepare a plain server frame.
_, frameData, err := pm.frame(prepareKey{isServer: true, compress: false})
if err != nil {
return nil, err
}
// To protect against caller modifying the data argument, remember the data
// copied to the plain server frame.
pm.data = frameData[len(frameData)-len(data):]
return pm, nil
}
func (pm *PreparedMessage) frame(key prepareKey) (int, []byte, error) {
pm.mu.Lock()
frame, ok := pm.frames[key]
if !ok {
frame = &preparedFrame{}
pm.frames[key] = frame
}
pm.mu.Unlock()
var err error
frame.once.Do(func() {
// Prepare a frame using a 'fake' connection.
// TODO: Refactor code in conn.go to allow more direct construction of
// the frame.
mu := make(chan bool, 1)
mu <- true
var nc prepareConn
c := &Conn{
conn: &nc,
mu: mu,
isServer: key.isServer,
compressionLevel: key.compressionLevel,
enableWriteCompression: true,
writeBuf: make([]byte, defaultWriteBufferSize+maxFrameHeaderSize),
}
if key.compress {
c.newCompressionWriter = compressNoContextTakeover
}
err = c.WriteMessage(pm.messageType, pm.data)
frame.data = nc.buf.Bytes()
})
return pm.messageType, frame.data, err
}
type prepareConn struct {
buf bytes.Buffer
net.Conn
}
func (pc *prepareConn) Write(p []byte) (int, error) { return pc.buf.Write(p) }
func (pc *prepareConn) SetWriteDeadline(t time.Time) error { return nil }

View file

@ -28,8 +28,9 @@ type Upgrader struct {
HandshakeTimeout time.Duration HandshakeTimeout time.Duration
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer // ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
// size is zero, then a default value of 4096 is used. The I/O buffer sizes // size is zero, then buffers allocated by the HTTP server are used. The
// do not limit the size of the messages that can be sent or received. // I/O buffer sizes do not limit the size of the messages that can be sent
// or received.
ReadBufferSize, WriteBufferSize int ReadBufferSize, WriteBufferSize int
// Subprotocols specifies the server's supported protocols in order of // Subprotocols specifies the server's supported protocols in order of
@ -46,6 +47,12 @@ type Upgrader struct {
// CheckOrigin is nil, the host in the Origin header must not be set or // CheckOrigin is nil, the host in the Origin header must not be set or
// must match the host of the request. // must match the host of the request.
CheckOrigin func(r *http.Request) bool CheckOrigin func(r *http.Request) bool
// EnableCompression specify if the server should attempt to negotiate per
// message compression (RFC 7692). Setting this value to true does not
// guarantee that compression will be supported. Currently only "no context
// takeover" modes are supported.
EnableCompression bool
} }
func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status int, reason string) (*Conn, error) { func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status int, reason string) (*Conn, error) {
@ -98,18 +105,23 @@ func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header
// response. // response.
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) { func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) {
if r.Method != "GET" { if r.Method != "GET" {
return u.returnError(w, r, http.StatusMethodNotAllowed, "websocket: method not GET") return u.returnError(w, r, http.StatusMethodNotAllowed, "websocket: not a websocket handshake: request method is not GET")
} }
if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") {
return u.returnError(w, r, http.StatusBadRequest, "websocket: version != 13") if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok {
return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-Websocket-Extensions' headers are unsupported")
} }
if !tokenListContainsValue(r.Header, "Connection", "upgrade") { if !tokenListContainsValue(r.Header, "Connection", "upgrade") {
return u.returnError(w, r, http.StatusBadRequest, "websocket: could not find connection header with token 'upgrade'") return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'upgrade' token not found in 'Connection' header")
} }
if !tokenListContainsValue(r.Header, "Upgrade", "websocket") { if !tokenListContainsValue(r.Header, "Upgrade", "websocket") {
return u.returnError(w, r, http.StatusBadRequest, "websocket: could not find upgrade header with token 'websocket'") return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'websocket' token not found in 'Upgrade' header")
}
if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") {
return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header")
} }
checkOrigin := u.CheckOrigin checkOrigin := u.CheckOrigin
@ -117,19 +129,30 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
checkOrigin = checkSameOrigin checkOrigin = checkSameOrigin
} }
if !checkOrigin(r) { if !checkOrigin(r) {
return u.returnError(w, r, http.StatusForbidden, "websocket: origin not allowed") return u.returnError(w, r, http.StatusForbidden, "websocket: 'Origin' header value not allowed")
} }
challengeKey := r.Header.Get("Sec-Websocket-Key") challengeKey := r.Header.Get("Sec-Websocket-Key")
if challengeKey == "" { if challengeKey == "" {
return u.returnError(w, r, http.StatusBadRequest, "websocket: key missing or blank") return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-Websocket-Key' header is missing or blank")
} }
subprotocol := u.selectSubprotocol(r, responseHeader) subprotocol := u.selectSubprotocol(r, responseHeader)
// Negotiate PMCE
var compress bool
if u.EnableCompression {
for _, ext := range parseExtensions(r.Header) {
if ext[""] != "permessage-deflate" {
continue
}
compress = true
break
}
}
var ( var (
netConn net.Conn netConn net.Conn
br *bufio.Reader
err error err error
) )
@ -137,21 +160,25 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
if !ok { if !ok {
return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker") return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker")
} }
var rw *bufio.ReadWriter var brw *bufio.ReadWriter
netConn, rw, err = h.Hijack() netConn, brw, err = h.Hijack()
if err != nil { if err != nil {
return u.returnError(w, r, http.StatusInternalServerError, err.Error()) return u.returnError(w, r, http.StatusInternalServerError, err.Error())
} }
br = rw.Reader
if br.Buffered() > 0 { if brw.Reader.Buffered() > 0 {
netConn.Close() netConn.Close()
return nil, errors.New("websocket: client sent data before handshake is complete") return nil, errors.New("websocket: client sent data before handshake is complete")
} }
c := newConn(netConn, true, u.ReadBufferSize, u.WriteBufferSize) c := newConnBRW(netConn, true, u.ReadBufferSize, u.WriteBufferSize, brw)
c.subprotocol = subprotocol c.subprotocol = subprotocol
if compress {
c.newCompressionWriter = compressNoContextTakeover
c.newDecompressionReader = decompressNoContextTakeover
}
p := c.writeBuf[:0] p := c.writeBuf[:0]
p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...) p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...)
p = append(p, computeAcceptKey(challengeKey)...) p = append(p, computeAcceptKey(challengeKey)...)
@ -161,6 +188,9 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
p = append(p, c.subprotocol...) p = append(p, c.subprotocol...)
p = append(p, "\r\n"...) p = append(p, "\r\n"...)
} }
if compress {
p = append(p, "Sec-Websocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...)
}
for k, vs := range responseHeader { for k, vs := range responseHeader {
if k == "Sec-Websocket-Protocol" { if k == "Sec-Websocket-Protocol" {
continue continue

View file

@ -0,0 +1 @@
Yet another test

View file

@ -0,0 +1,2 @@
Test message
Another test

File diff suppressed because one or more lines are too long

View file

@ -38,17 +38,22 @@ func Go() {
fromAddr = os.Getenv("MH_SENDMAIL_FROM") fromAddr = os.Getenv("MH_SENDMAIL_FROM")
} }
var verbose bool
// override defaults from cli flags // override defaults from cli flags
flag.StringVar(&smtpAddr, "smtp-addr", smtpAddr, "SMTP server address") flag.StringVar(&smtpAddr, "smtp-addr", smtpAddr, "SMTP server address")
flag.StringVarP(&fromAddr, "from", "f", fromAddr, "SMTP sender") flag.StringVarP(&fromAddr, "from", "f", fromAddr, "SMTP sender")
flag.BoolP("long-i", "i", true, "Ignored. This flag exists for sendmail compatibility.") flag.BoolP("long-i", "i", true, "Ignored. This flag exists for sendmail compatibility.")
flag.BoolP("long-t", "t", true, "Ignored. This flag exists for sendmail compatibility.") flag.BoolP("long-t", "t", true, "Ignored. This flag exists for sendmail compatibility.")
flag.BoolVarP(&verbose, "verbose", "v", false, "Verbose mode (sends debug output to stderr)")
flag.Parse() flag.Parse()
// allow recipient to be passed as an argument // allow recipient to be passed as an argument
recip = flag.Args() recip = flag.Args()
if verbose {
fmt.Fprintln(os.Stderr, smtpAddr, fromAddr) fmt.Fprintln(os.Stderr, smtpAddr, fromAddr)
}
body, err := ioutil.ReadAll(os.Stdin) body, err := ioutil.ReadAll(os.Stdin)
if err != nil { if err != nil {

View file

@ -2,6 +2,7 @@ package storage
import ( import (
"strings" "strings"
"sync"
"github.com/mailhog/data" "github.com/mailhog/data"
) )
@ -10,6 +11,7 @@ import (
type InMemory struct { type InMemory struct {
MessageIDIndex map[string]int MessageIDIndex map[string]int
Messages []*data.Message Messages []*data.Message
mu sync.Mutex
} }
// CreateInMemory creates a new in memory storage backend // CreateInMemory creates a new in memory storage backend
@ -22,6 +24,8 @@ func CreateInMemory() *InMemory {
// Store stores a message and returns its storage ID // Store stores a message and returns its storage ID
func (memory *InMemory) Store(m *data.Message) (string, error) { func (memory *InMemory) Store(m *data.Message) (string, error) {
memory.mu.Lock()
defer memory.mu.Unlock()
memory.Messages = append(memory.Messages, m) memory.Messages = append(memory.Messages, m)
memory.MessageIDIndex[string(m.ID)] = len(memory.Messages) - 1 memory.MessageIDIndex[string(m.ID)] = len(memory.Messages) - 1
return string(m.ID), nil return string(m.ID), nil
@ -156,6 +160,8 @@ func (memory *InMemory) List(start int, limit int) (*data.Messages, error) {
// DeleteOne deletes an individual message by storage ID // DeleteOne deletes an individual message by storage ID
func (memory *InMemory) DeleteOne(id string) error { func (memory *InMemory) DeleteOne(id string) error {
memory.mu.Lock()
defer memory.mu.Unlock()
index := memory.MessageIDIndex[id] index := memory.MessageIDIndex[id]
delete(memory.MessageIDIndex, id) delete(memory.MessageIDIndex, id)
for k, v := range memory.MessageIDIndex { for k, v := range memory.MessageIDIndex {
@ -169,6 +175,8 @@ func (memory *InMemory) DeleteOne(id string) error {
// DeleteAll deletes all in memory messages // DeleteAll deletes all in memory messages
func (memory *InMemory) DeleteAll() error { func (memory *InMemory) DeleteAll() error {
memory.mu.Lock()
defer memory.mu.Unlock()
memory.Messages = make([]*data.Message, 0) memory.Messages = make([]*data.Message, 0)
memory.MessageIDIndex = make(map[string]int) memory.MessageIDIndex = make(map[string]int)
return nil return nil

View file

@ -1,4 +1,6 @@
[![Build Status](https://travis-ci.org/spf13/pflag.svg?branch=master)](https://travis-ci.org/spf13/pflag) [![Build Status](https://travis-ci.org/spf13/pflag.svg?branch=master)](https://travis-ci.org/spf13/pflag)
[![Go Report Card](https://goreportcard.com/badge/github.com/spf13/pflag)](https://goreportcard.com/report/github.com/spf13/pflag)
[![GoDoc](https://godoc.org/github.com/spf13/pflag?status.svg)](https://godoc.org/github.com/spf13/pflag)
## Description ## Description
@ -106,9 +108,9 @@ that give one-letter shorthands for flags. You can use these by appending
var ip = flag.IntP("flagname", "f", 1234, "help message") var ip = flag.IntP("flagname", "f", 1234, "help message")
var flagvar bool var flagvar bool
func init() { func init() {
flag.BoolVarP("boolname", "b", true, "help message") flag.BoolVarP(&flagvar, "boolname", "b", true, "help message")
} }
flag.VarP(&flagVar, "varname", "v", 1234, "help message") flag.VarP(&flagVal, "varname", "v", "help message")
``` ```
Shorthand letters can be used with single dashes on the command line. Shorthand letters can be used with single dashes on the command line.
@ -244,6 +246,25 @@ It is possible to mark a flag as hidden, meaning it will still function as norma
flags.MarkHidden("secretFlag") flags.MarkHidden("secretFlag")
``` ```
## Supporting Go flags when using pflag
In order to support flags defined using Go's `flag` package, they must be added to the `pflag` flagset. This is usually necessary
to support flags defined by third-party dependencies (e.g. `golang/glog`).
**Example**: You want to add the Go flags to the `CommandLine` flagset
```go
import (
goflag "flag"
flag "github.com/spf13/pflag"
)
var ip *int = flag.Int("flagname", 1234, "help message for flagname")
func main() {
flag.CommandLine.AddGoFlagSet(goflag.CommandLine)
flag.Parse()
}
```
## More info ## More info
You can see the full reference documentation of the pflag package You can see the full reference documentation of the pflag package

View file

@ -1,9 +1,6 @@
package pflag package pflag
import ( import "strconv"
"fmt"
"strconv"
)
// optional interface to indicate boolean flags that can be // optional interface to indicate boolean flags that can be
// supplied without "=value" text // supplied without "=value" text
@ -30,7 +27,7 @@ func (b *boolValue) Type() string {
return "bool" return "bool"
} }
func (b *boolValue) String() string { return fmt.Sprintf("%v", *b) } func (b *boolValue) String() string { return strconv.FormatBool(bool(*b)) }
func (b *boolValue) IsBoolFlag() bool { return true } func (b *boolValue) IsBoolFlag() bool { return true }

147
vendor/github.com/spf13/pflag/bool_slice.go generated vendored Normal file
View file

@ -0,0 +1,147 @@
package pflag
import (
"io"
"strconv"
"strings"
)
// -- boolSlice Value
type boolSliceValue struct {
value *[]bool
changed bool
}
func newBoolSliceValue(val []bool, p *[]bool) *boolSliceValue {
bsv := new(boolSliceValue)
bsv.value = p
*bsv.value = val
return bsv
}
// Set converts, and assigns, the comma-separated boolean argument string representation as the []bool value of this flag.
// If Set is called on a flag that already has a []bool assigned, the newly converted values will be appended.
func (s *boolSliceValue) Set(val string) error {
// remove all quote characters
rmQuote := strings.NewReplacer(`"`, "", `'`, "", "`", "")
// read flag arguments with CSV parser
boolStrSlice, err := readAsCSV(rmQuote.Replace(val))
if err != nil && err != io.EOF {
return err
}
// parse boolean values into slice
out := make([]bool, 0, len(boolStrSlice))
for _, boolStr := range boolStrSlice {
b, err := strconv.ParseBool(strings.TrimSpace(boolStr))
if err != nil {
return err
}
out = append(out, b)
}
if !s.changed {
*s.value = out
} else {
*s.value = append(*s.value, out...)
}
s.changed = true
return nil
}
// Type returns a string that uniquely represents this flag's type.
func (s *boolSliceValue) Type() string {
return "boolSlice"
}
// String defines a "native" format for this boolean slice flag value.
func (s *boolSliceValue) String() string {
boolStrSlice := make([]string, len(*s.value))
for i, b := range *s.value {
boolStrSlice[i] = strconv.FormatBool(b)
}
out, _ := writeAsCSV(boolStrSlice)
return "[" + out + "]"
}
func boolSliceConv(val string) (interface{}, error) {
val = strings.Trim(val, "[]")
// Empty string would cause a slice with one (empty) entry
if len(val) == 0 {
return []bool{}, nil
}
ss := strings.Split(val, ",")
out := make([]bool, len(ss))
for i, t := range ss {
var err error
out[i], err = strconv.ParseBool(t)
if err != nil {
return nil, err
}
}
return out, nil
}
// GetBoolSlice returns the []bool value of a flag with the given name.
func (f *FlagSet) GetBoolSlice(name string) ([]bool, error) {
val, err := f.getFlagType(name, "boolSlice", boolSliceConv)
if err != nil {
return []bool{}, err
}
return val.([]bool), nil
}
// BoolSliceVar defines a boolSlice flag with specified name, default value, and usage string.
// The argument p points to a []bool variable in which to store the value of the flag.
func (f *FlagSet) BoolSliceVar(p *[]bool, name string, value []bool, usage string) {
f.VarP(newBoolSliceValue(value, p), name, "", usage)
}
// BoolSliceVarP is like BoolSliceVar, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) BoolSliceVarP(p *[]bool, name, shorthand string, value []bool, usage string) {
f.VarP(newBoolSliceValue(value, p), name, shorthand, usage)
}
// BoolSliceVar defines a []bool flag with specified name, default value, and usage string.
// The argument p points to a []bool variable in which to store the value of the flag.
func BoolSliceVar(p *[]bool, name string, value []bool, usage string) {
CommandLine.VarP(newBoolSliceValue(value, p), name, "", usage)
}
// BoolSliceVarP is like BoolSliceVar, but accepts a shorthand letter that can be used after a single dash.
func BoolSliceVarP(p *[]bool, name, shorthand string, value []bool, usage string) {
CommandLine.VarP(newBoolSliceValue(value, p), name, shorthand, usage)
}
// BoolSlice defines a []bool flag with specified name, default value, and usage string.
// The return value is the address of a []bool variable that stores the value of the flag.
func (f *FlagSet) BoolSlice(name string, value []bool, usage string) *[]bool {
p := []bool{}
f.BoolSliceVarP(&p, name, "", value, usage)
return &p
}
// BoolSliceP is like BoolSlice, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) BoolSliceP(name, shorthand string, value []bool, usage string) *[]bool {
p := []bool{}
f.BoolSliceVarP(&p, name, shorthand, value, usage)
return &p
}
// BoolSlice defines a []bool flag with specified name, default value, and usage string.
// The return value is the address of a []bool variable that stores the value of the flag.
func BoolSlice(name string, value []bool, usage string) *[]bool {
return CommandLine.BoolSliceP(name, "", value, usage)
}
// BoolSliceP is like BoolSlice, but accepts a shorthand letter that can be used after a single dash.
func BoolSliceP(name, shorthand string, value []bool, usage string) *[]bool {
return CommandLine.BoolSliceP(name, shorthand, value, usage)
}

View file

@ -1,9 +1,6 @@
package pflag package pflag
import ( import "strconv"
"fmt"
"strconv"
)
// -- count Value // -- count Value
type countValue int type countValue int
@ -28,7 +25,7 @@ func (i *countValue) Type() string {
return "count" return "count"
} }
func (i *countValue) String() string { return fmt.Sprintf("%v", *i) } func (i *countValue) String() string { return strconv.Itoa(int(*i)) }
func countConv(sval string) (interface{}, error) { func countConv(sval string) (interface{}, error) {
i, err := strconv.Atoi(sval) i, err := strconv.Atoi(sval)

197
vendor/github.com/spf13/pflag/flag.go generated vendored
View file

@ -416,13 +416,28 @@ func Set(name, value string) error {
// otherwise, the default values of all defined flags in the set. // otherwise, the default values of all defined flags in the set.
func (f *FlagSet) PrintDefaults() { func (f *FlagSet) PrintDefaults() {
usages := f.FlagUsages() usages := f.FlagUsages()
fmt.Fprintf(f.out(), "%s", usages) fmt.Fprint(f.out(), usages)
} }
// isZeroValue guesses whether the string represents the zero // defaultIsZeroValue returns true if the default value for this flag represents
// value for a flag. It is not accurate but in practice works OK. // a zero value.
func isZeroValue(value string) bool { func (f *Flag) defaultIsZeroValue() bool {
switch value { switch f.Value.(type) {
case boolFlag:
return f.DefValue == "false"
case *durationValue:
// Beginning in Go 1.7, duration zero values are "0s"
return f.DefValue == "0" || f.DefValue == "0s"
case *intValue, *int8Value, *int32Value, *int64Value, *uintValue, *uint8Value, *uint16Value, *uint32Value, *uint64Value, *countValue, *float32Value, *float64Value:
return f.DefValue == "0"
case *stringValue:
return f.DefValue == ""
case *ipValue, *ipMaskValue, *ipNetValue:
return f.DefValue == "<nil>"
case *intSliceValue, *stringSliceValue, *stringArrayValue:
return f.DefValue == "[]"
default:
switch f.Value.String() {
case "false": case "false":
return true return true
case "<nil>": case "<nil>":
@ -433,6 +448,7 @@ func isZeroValue(value string) bool {
return true return true
} }
return false return false
}
} }
// UnquoteUsage extracts a back-quoted name from the usage // UnquoteUsage extracts a back-quoted name from the usage
@ -455,28 +471,92 @@ func UnquoteUsage(flag *Flag) (name string, usage string) {
break // Only one back quote; use type name. break // Only one back quote; use type name.
} }
} }
// No explicit name, so use type if we can find one.
name = "value" name = flag.Value.Type()
switch flag.Value.(type) { switch name {
case boolFlag: case "bool":
name = "" name = ""
case *durationValue: case "float64":
name = "duration"
case *float64Value:
name = "float" name = "float"
case *intValue, *int64Value: case "int64":
name = "int" name = "int"
case *stringValue: case "uint64":
name = "string"
case *uintValue, *uint64Value:
name = "uint" name = "uint"
} }
return return
} }
// FlagUsages Returns a string containing the usage information for all flags in // Splits the string `s` on whitespace into an initial substring up to
// the FlagSet // `i` runes in length and the remainder. Will go `slop` over `i` if
func (f *FlagSet) FlagUsages() string { // that encompasses the entire string (which allows the caller to
// avoid short orphan words on the final line).
func wrapN(i, slop int, s string) (string, string) {
if i+slop > len(s) {
return s, ""
}
w := strings.LastIndexAny(s[:i], " \t")
if w <= 0 {
return s, ""
}
return s[:w], s[w+1:]
}
// Wraps the string `s` to a maximum width `w` with leading indent
// `i`. The first line is not indented (this is assumed to be done by
// caller). Pass `w` == 0 to do no wrapping
func wrap(i, w int, s string) string {
if w == 0 {
return s
}
// space between indent i and end of line width w into which
// we should wrap the text.
wrap := w - i
var r, l string
// Not enough space for sensible wrapping. Wrap as a block on
// the next line instead.
if wrap < 24 {
i = 16
wrap = w - i
r += "\n" + strings.Repeat(" ", i)
}
// If still not enough space then don't even try to wrap.
if wrap < 24 {
return s
}
// Try to avoid short orphan words on the final line, by
// allowing wrapN to go a bit over if that would fit in the
// remainder of the line.
slop := 5
wrap = wrap - slop
// Handle first line, which is indented by the caller (or the
// special case above)
l, s = wrapN(wrap, slop, s)
r = r + l
// Now wrap the rest
for s != "" {
var t string
t, s = wrapN(wrap, slop, s)
r = r + "\n" + strings.Repeat(" ", i) + t
}
return r
}
// FlagUsagesWrapped returns a string containing the usage information
// for all flags in the FlagSet. Wrapped to `cols` columns (0 for no
// wrapping)
func (f *FlagSet) FlagUsagesWrapped(cols int) string {
x := new(bytes.Buffer) x := new(bytes.Buffer)
lines := make([]string, 0, len(f.formal)) lines := make([]string, 0, len(f.formal))
@ -501,7 +581,7 @@ func (f *FlagSet) FlagUsages() string {
if len(flag.NoOptDefVal) > 0 { if len(flag.NoOptDefVal) > 0 {
switch flag.Value.Type() { switch flag.Value.Type() {
case "string": case "string":
line += fmt.Sprintf("[=%q]", flag.NoOptDefVal) line += fmt.Sprintf("[=\"%s\"]", flag.NoOptDefVal)
case "bool": case "bool":
if flag.NoOptDefVal != "true" { if flag.NoOptDefVal != "true" {
line += fmt.Sprintf("[=%s]", flag.NoOptDefVal) line += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
@ -519,9 +599,9 @@ func (f *FlagSet) FlagUsages() string {
} }
line += usage line += usage
if !isZeroValue(flag.DefValue) { if !flag.defaultIsZeroValue() {
if flag.Value.Type() == "string" { if flag.Value.Type() == "string" {
line += fmt.Sprintf(" (default %q)", flag.DefValue) line += fmt.Sprintf(" (default \"%s\")", flag.DefValue)
} else { } else {
line += fmt.Sprintf(" (default %s)", flag.DefValue) line += fmt.Sprintf(" (default %s)", flag.DefValue)
} }
@ -533,12 +613,19 @@ func (f *FlagSet) FlagUsages() string {
for _, line := range lines { for _, line := range lines {
sidx := strings.Index(line, "\x00") sidx := strings.Index(line, "\x00")
spacing := strings.Repeat(" ", maxlen-sidx) spacing := strings.Repeat(" ", maxlen-sidx)
fmt.Fprintln(x, line[:sidx], spacing, line[sidx+1:]) // maxlen + 2 comes from + 1 for the \x00 and + 1 for the (deliberate) off-by-one in maxlen-sidx
fmt.Fprintln(x, line[:sidx], spacing, wrap(maxlen+2, cols, line[sidx+1:]))
} }
return x.String() return x.String()
} }
// FlagUsages returns a string containing the usage information for all flags in
// the FlagSet
func (f *FlagSet) FlagUsages() string {
return f.FlagUsagesWrapped(0)
}
// PrintDefaults prints to standard error the default values of all defined command-line flags. // PrintDefaults prints to standard error the default values of all defined command-line flags.
func PrintDefaults() { func PrintDefaults() {
CommandLine.PrintDefaults() CommandLine.PrintDefaults()
@ -622,7 +709,7 @@ func (f *FlagSet) VarPF(value Value, name, shorthand, usage string) *Flag {
// VarP is like Var, but accepts a shorthand letter that can be used after a single dash. // VarP is like Var, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) VarP(value Value, name, shorthand, usage string) { func (f *FlagSet) VarP(value Value, name, shorthand, usage string) {
_ = f.VarPF(value, name, shorthand, usage) f.VarPF(value, name, shorthand, usage)
} }
// AddFlag will add the flag to the FlagSet // AddFlag will add the flag to the FlagSet
@ -739,7 +826,7 @@ func containsShorthand(arg, shorthand string) bool {
return strings.Contains(arg, shorthand) return strings.Contains(arg, shorthand)
} }
func (f *FlagSet) parseLongArg(s string, args []string) (a []string, err error) { func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []string, err error) {
a = args a = args
name := s[2:] name := s[2:]
if len(name) == 0 || name[0] == '-' || name[0] == '=' { if len(name) == 0 || name[0] == '-' || name[0] == '=' {
@ -773,11 +860,11 @@ func (f *FlagSet) parseLongArg(s string, args []string) (a []string, err error)
err = f.failf("flag needs an argument: %s", s) err = f.failf("flag needs an argument: %s", s)
return return
} }
err = f.setFlag(flag, value, s) err = fn(flag, value, s)
return return
} }
func (f *FlagSet) parseSingleShortArg(shorthands string, args []string) (outShorts string, outArgs []string, err error) { func (f *FlagSet) parseSingleShortArg(shorthands string, args []string, fn parseFunc) (outShorts string, outArgs []string, err error) {
if strings.HasPrefix(shorthands, "test.") { if strings.HasPrefix(shorthands, "test.") {
return return
} }
@ -812,16 +899,16 @@ func (f *FlagSet) parseSingleShortArg(shorthands string, args []string) (outShor
err = f.failf("flag needs an argument: %q in -%s", c, shorthands) err = f.failf("flag needs an argument: %q in -%s", c, shorthands)
return return
} }
err = f.setFlag(flag, value, shorthands) err = fn(flag, value, shorthands)
return return
} }
func (f *FlagSet) parseShortArg(s string, args []string) (a []string, err error) { func (f *FlagSet) parseShortArg(s string, args []string, fn parseFunc) (a []string, err error) {
a = args a = args
shorthands := s[1:] shorthands := s[1:]
for len(shorthands) > 0 { for len(shorthands) > 0 {
shorthands, a, err = f.parseSingleShortArg(shorthands, args) shorthands, a, err = f.parseSingleShortArg(shorthands, args, fn)
if err != nil { if err != nil {
return return
} }
@ -830,7 +917,7 @@ func (f *FlagSet) parseShortArg(s string, args []string) (a []string, err error)
return return
} }
func (f *FlagSet) parseArgs(args []string) (err error) { func (f *FlagSet) parseArgs(args []string, fn parseFunc) (err error) {
for len(args) > 0 { for len(args) > 0 {
s := args[0] s := args[0]
args = args[1:] args = args[1:]
@ -850,9 +937,9 @@ func (f *FlagSet) parseArgs(args []string) (err error) {
f.args = append(f.args, args...) f.args = append(f.args, args...)
break break
} }
args, err = f.parseLongArg(s, args) args, err = f.parseLongArg(s, args, fn)
} else { } else {
args, err = f.parseShortArg(s, args) args, err = f.parseShortArg(s, args, fn)
} }
if err != nil { if err != nil {
return return
@ -868,7 +955,41 @@ func (f *FlagSet) parseArgs(args []string) (err error) {
func (f *FlagSet) Parse(arguments []string) error { func (f *FlagSet) Parse(arguments []string) error {
f.parsed = true f.parsed = true
f.args = make([]string, 0, len(arguments)) f.args = make([]string, 0, len(arguments))
err := f.parseArgs(arguments)
assign := func(flag *Flag, value, origArg string) error {
return f.setFlag(flag, value, origArg)
}
err := f.parseArgs(arguments, assign)
if err != nil {
switch f.errorHandling {
case ContinueOnError:
return err
case ExitOnError:
os.Exit(2)
case PanicOnError:
panic(err)
}
}
return nil
}
type parseFunc func(flag *Flag, value, origArg string) error
// ParseAll parses flag definitions from the argument list, which should not
// include the command name. The arguments for fn are flag and value. Must be
// called after all flags in the FlagSet are defined and before flags are
// accessed by the program. The return value will be ErrHelp if -help was set
// but not defined.
func (f *FlagSet) ParseAll(arguments []string, fn func(flag *Flag, value string) error) error {
f.parsed = true
f.args = make([]string, 0, len(arguments))
assign := func(flag *Flag, value, origArg string) error {
return fn(flag, value)
}
err := f.parseArgs(arguments, assign)
if err != nil { if err != nil {
switch f.errorHandling { switch f.errorHandling {
case ContinueOnError: case ContinueOnError:
@ -894,6 +1015,14 @@ func Parse() {
CommandLine.Parse(os.Args[1:]) CommandLine.Parse(os.Args[1:])
} }
// ParseAll parses the command-line flags from os.Args[1:] and called fn for each.
// The arguments for fn are flag and value. Must be called after all flags are
// defined and before flags are accessed by the program.
func ParseAll(fn func(flag *Flag, value string) error) {
// Ignore errors; CommandLine is set for ExitOnError.
CommandLine.ParseAll(os.Args[1:], fn)
}
// SetInterspersed sets whether to support interspersed option/non-option arguments. // SetInterspersed sets whether to support interspersed option/non-option arguments.
func SetInterspersed(interspersed bool) { func SetInterspersed(interspersed bool) {
CommandLine.SetInterspersed(interspersed) CommandLine.SetInterspersed(interspersed)

View file

@ -1,9 +1,6 @@
package pflag package pflag
import ( import "strconv"
"fmt"
"strconv"
)
// -- float32 Value // -- float32 Value
type float32Value float32 type float32Value float32
@ -23,7 +20,7 @@ func (f *float32Value) Type() string {
return "float32" return "float32"
} }
func (f *float32Value) String() string { return fmt.Sprintf("%v", *f) } func (f *float32Value) String() string { return strconv.FormatFloat(float64(*f), 'g', -1, 32) }
func float32Conv(sval string) (interface{}, error) { func float32Conv(sval string) (interface{}, error) {
v, err := strconv.ParseFloat(sval, 32) v, err := strconv.ParseFloat(sval, 32)

View file

@ -1,9 +1,6 @@
package pflag package pflag
import ( import "strconv"
"fmt"
"strconv"
)
// -- float64 Value // -- float64 Value
type float64Value float64 type float64Value float64
@ -23,7 +20,7 @@ func (f *float64Value) Type() string {
return "float64" return "float64"
} }
func (f *float64Value) String() string { return fmt.Sprintf("%v", *f) } func (f *float64Value) String() string { return strconv.FormatFloat(float64(*f), 'g', -1, 64) }
func float64Conv(sval string) (interface{}, error) { func float64Conv(sval string) (interface{}, error) {
return strconv.ParseFloat(sval, 64) return strconv.ParseFloat(sval, 64)

View file

@ -6,13 +6,10 @@ package pflag
import ( import (
goflag "flag" goflag "flag"
"fmt"
"reflect" "reflect"
"strings" "strings"
) )
var _ = fmt.Print
// flagValueWrapper implements pflag.Value around a flag.Value. The main // flagValueWrapper implements pflag.Value around a flag.Value. The main
// difference here is the addition of the Type method that returns a string // difference here is the addition of the Type method that returns a string
// name of the type. As this is generally unknown, we approximate that with // name of the type. As this is generally unknown, we approximate that with

View file

@ -1,9 +1,6 @@
package pflag package pflag
import ( import "strconv"
"fmt"
"strconv"
)
// -- int Value // -- int Value
type intValue int type intValue int
@ -23,7 +20,7 @@ func (i *intValue) Type() string {
return "int" return "int"
} }
func (i *intValue) String() string { return fmt.Sprintf("%v", *i) } func (i *intValue) String() string { return strconv.Itoa(int(*i)) }
func intConv(sval string) (interface{}, error) { func intConv(sval string) (interface{}, error) {
return strconv.Atoi(sval) return strconv.Atoi(sval)

View file

@ -1,9 +1,6 @@
package pflag package pflag
import ( import "strconv"
"fmt"
"strconv"
)
// -- int32 Value // -- int32 Value
type int32Value int32 type int32Value int32
@ -23,7 +20,7 @@ func (i *int32Value) Type() string {
return "int32" return "int32"
} }
func (i *int32Value) String() string { return fmt.Sprintf("%v", *i) } func (i *int32Value) String() string { return strconv.FormatInt(int64(*i), 10) }
func int32Conv(sval string) (interface{}, error) { func int32Conv(sval string) (interface{}, error) {
v, err := strconv.ParseInt(sval, 0, 32) v, err := strconv.ParseInt(sval, 0, 32)

View file

@ -1,9 +1,6 @@
package pflag package pflag
import ( import "strconv"
"fmt"
"strconv"
)
// -- int64 Value // -- int64 Value
type int64Value int64 type int64Value int64
@ -23,7 +20,7 @@ func (i *int64Value) Type() string {
return "int64" return "int64"
} }
func (i *int64Value) String() string { return fmt.Sprintf("%v", *i) } func (i *int64Value) String() string { return strconv.FormatInt(int64(*i), 10) }
func int64Conv(sval string) (interface{}, error) { func int64Conv(sval string) (interface{}, error) {
return strconv.ParseInt(sval, 0, 64) return strconv.ParseInt(sval, 0, 64)

View file

@ -1,9 +1,6 @@
package pflag package pflag
import ( import "strconv"
"fmt"
"strconv"
)
// -- int8 Value // -- int8 Value
type int8Value int8 type int8Value int8
@ -23,7 +20,7 @@ func (i *int8Value) Type() string {
return "int8" return "int8"
} }
func (i *int8Value) String() string { return fmt.Sprintf("%v", *i) } func (i *int8Value) String() string { return strconv.FormatInt(int64(*i), 10) }
func int8Conv(sval string) (interface{}, error) { func int8Conv(sval string) (interface{}, error) {
v, err := strconv.ParseInt(sval, 0, 8) v, err := strconv.ParseInt(sval, 0, 8)

View file

@ -6,8 +6,6 @@ import (
"strings" "strings"
) )
var _ = strings.TrimSpace
// -- net.IP value // -- net.IP value
type ipValue net.IP type ipValue net.IP

148
vendor/github.com/spf13/pflag/ip_slice.go generated vendored Normal file
View file

@ -0,0 +1,148 @@
package pflag
import (
"fmt"
"io"
"net"
"strings"
)
// -- ipSlice Value
type ipSliceValue struct {
value *[]net.IP
changed bool
}
func newIPSliceValue(val []net.IP, p *[]net.IP) *ipSliceValue {
ipsv := new(ipSliceValue)
ipsv.value = p
*ipsv.value = val
return ipsv
}
// Set converts, and assigns, the comma-separated IP argument string representation as the []net.IP value of this flag.
// If Set is called on a flag that already has a []net.IP assigned, the newly converted values will be appended.
func (s *ipSliceValue) Set(val string) error {
// remove all quote characters
rmQuote := strings.NewReplacer(`"`, "", `'`, "", "`", "")
// read flag arguments with CSV parser
ipStrSlice, err := readAsCSV(rmQuote.Replace(val))
if err != nil && err != io.EOF {
return err
}
// parse ip values into slice
out := make([]net.IP, 0, len(ipStrSlice))
for _, ipStr := range ipStrSlice {
ip := net.ParseIP(strings.TrimSpace(ipStr))
if ip == nil {
return fmt.Errorf("invalid string being converted to IP address: %s", ipStr)
}
out = append(out, ip)
}
if !s.changed {
*s.value = out
} else {
*s.value = append(*s.value, out...)
}
s.changed = true
return nil
}
// Type returns a string that uniquely represents this flag's type.
func (s *ipSliceValue) Type() string {
return "ipSlice"
}
// String defines a "native" format for this net.IP slice flag value.
func (s *ipSliceValue) String() string {
ipStrSlice := make([]string, len(*s.value))
for i, ip := range *s.value {
ipStrSlice[i] = ip.String()
}
out, _ := writeAsCSV(ipStrSlice)
return "[" + out + "]"
}
func ipSliceConv(val string) (interface{}, error) {
val = strings.Trim(val, "[]")
// Emtpy string would cause a slice with one (empty) entry
if len(val) == 0 {
return []net.IP{}, nil
}
ss := strings.Split(val, ",")
out := make([]net.IP, len(ss))
for i, sval := range ss {
ip := net.ParseIP(strings.TrimSpace(sval))
if ip == nil {
return nil, fmt.Errorf("invalid string being converted to IP address: %s", sval)
}
out[i] = ip
}
return out, nil
}
// GetIPSlice returns the []net.IP value of a flag with the given name
func (f *FlagSet) GetIPSlice(name string) ([]net.IP, error) {
val, err := f.getFlagType(name, "ipSlice", ipSliceConv)
if err != nil {
return []net.IP{}, err
}
return val.([]net.IP), nil
}
// IPSliceVar defines a ipSlice flag with specified name, default value, and usage string.
// The argument p points to a []net.IP variable in which to store the value of the flag.
func (f *FlagSet) IPSliceVar(p *[]net.IP, name string, value []net.IP, usage string) {
f.VarP(newIPSliceValue(value, p), name, "", usage)
}
// IPSliceVarP is like IPSliceVar, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) IPSliceVarP(p *[]net.IP, name, shorthand string, value []net.IP, usage string) {
f.VarP(newIPSliceValue(value, p), name, shorthand, usage)
}
// IPSliceVar defines a []net.IP flag with specified name, default value, and usage string.
// The argument p points to a []net.IP variable in which to store the value of the flag.
func IPSliceVar(p *[]net.IP, name string, value []net.IP, usage string) {
CommandLine.VarP(newIPSliceValue(value, p), name, "", usage)
}
// IPSliceVarP is like IPSliceVar, but accepts a shorthand letter that can be used after a single dash.
func IPSliceVarP(p *[]net.IP, name, shorthand string, value []net.IP, usage string) {
CommandLine.VarP(newIPSliceValue(value, p), name, shorthand, usage)
}
// IPSlice defines a []net.IP flag with specified name, default value, and usage string.
// The return value is the address of a []net.IP variable that stores the value of that flag.
func (f *FlagSet) IPSlice(name string, value []net.IP, usage string) *[]net.IP {
p := []net.IP{}
f.IPSliceVarP(&p, name, "", value, usage)
return &p
}
// IPSliceP is like IPSlice, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) IPSliceP(name, shorthand string, value []net.IP, usage string) *[]net.IP {
p := []net.IP{}
f.IPSliceVarP(&p, name, shorthand, value, usage)
return &p
}
// IPSlice defines a []net.IP flag with specified name, default value, and usage string.
// The return value is the address of a []net.IP variable that stores the value of the flag.
func IPSlice(name string, value []net.IP, usage string) *[]net.IP {
return CommandLine.IPSliceP(name, "", value, usage)
}
// IPSliceP is like IPSlice, but accepts a shorthand letter that can be used after a single dash.
func IPSliceP(name, shorthand string, value []net.IP, usage string) *[]net.IP {
return CommandLine.IPSliceP(name, shorthand, value, usage)
}

View file

@ -27,8 +27,6 @@ func (*ipNetValue) Type() string {
return "ipNet" return "ipNet"
} }
var _ = strings.TrimSpace
func newIPNetValue(val net.IPNet, p *net.IPNet) *ipNetValue { func newIPNetValue(val net.IPNet, p *net.IPNet) *ipNetValue {
*p = val *p = val
return (*ipNetValue)(p) return (*ipNetValue)(p)

View file

@ -1,7 +1,5 @@
package pflag package pflag
import "fmt"
// -- string Value // -- string Value
type stringValue string type stringValue string
@ -18,7 +16,7 @@ func (s *stringValue) Type() string {
return "string" return "string"
} }
func (s *stringValue) String() string { return fmt.Sprintf("%s", *s) } func (s *stringValue) String() string { return string(*s) }
func stringConv(sval string) (interface{}, error) { func stringConv(sval string) (interface{}, error) {
return sval, nil return sval, nil

103
vendor/github.com/spf13/pflag/string_array.go generated vendored Normal file
View file

@ -0,0 +1,103 @@
package pflag
// -- stringArray Value
type stringArrayValue struct {
value *[]string
changed bool
}
func newStringArrayValue(val []string, p *[]string) *stringArrayValue {
ssv := new(stringArrayValue)
ssv.value = p
*ssv.value = val
return ssv
}
func (s *stringArrayValue) Set(val string) error {
if !s.changed {
*s.value = []string{val}
s.changed = true
} else {
*s.value = append(*s.value, val)
}
return nil
}
func (s *stringArrayValue) Type() string {
return "stringArray"
}
func (s *stringArrayValue) String() string {
str, _ := writeAsCSV(*s.value)
return "[" + str + "]"
}
func stringArrayConv(sval string) (interface{}, error) {
sval = sval[1 : len(sval)-1]
// An empty string would cause a array with one (empty) string
if len(sval) == 0 {
return []string{}, nil
}
return readAsCSV(sval)
}
// GetStringArray return the []string value of a flag with the given name
func (f *FlagSet) GetStringArray(name string) ([]string, error) {
val, err := f.getFlagType(name, "stringArray", stringArrayConv)
if err != nil {
return []string{}, err
}
return val.([]string), nil
}
// StringArrayVar defines a string flag with specified name, default value, and usage string.
// The argument p points to a []string variable in which to store the values of the multiple flags.
// The value of each argument will not try to be separated by comma
func (f *FlagSet) StringArrayVar(p *[]string, name string, value []string, usage string) {
f.VarP(newStringArrayValue(value, p), name, "", usage)
}
// StringArrayVarP is like StringArrayVar, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) StringArrayVarP(p *[]string, name, shorthand string, value []string, usage string) {
f.VarP(newStringArrayValue(value, p), name, shorthand, usage)
}
// StringArrayVar defines a string flag with specified name, default value, and usage string.
// The argument p points to a []string variable in which to store the value of the flag.
// The value of each argument will not try to be separated by comma
func StringArrayVar(p *[]string, name string, value []string, usage string) {
CommandLine.VarP(newStringArrayValue(value, p), name, "", usage)
}
// StringArrayVarP is like StringArrayVar, but accepts a shorthand letter that can be used after a single dash.
func StringArrayVarP(p *[]string, name, shorthand string, value []string, usage string) {
CommandLine.VarP(newStringArrayValue(value, p), name, shorthand, usage)
}
// StringArray defines a string flag with specified name, default value, and usage string.
// The return value is the address of a []string variable that stores the value of the flag.
// The value of each argument will not try to be separated by comma
func (f *FlagSet) StringArray(name string, value []string, usage string) *[]string {
p := []string{}
f.StringArrayVarP(&p, name, "", value, usage)
return &p
}
// StringArrayP is like StringArray, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) StringArrayP(name, shorthand string, value []string, usage string) *[]string {
p := []string{}
f.StringArrayVarP(&p, name, shorthand, value, usage)
return &p
}
// StringArray defines a string flag with specified name, default value, and usage string.
// The return value is the address of a []string variable that stores the value of the flag.
// The value of each argument will not try to be separated by comma
func StringArray(name string, value []string, usage string) *[]string {
return CommandLine.StringArrayP(name, "", value, usage)
}
// StringArrayP is like StringArray, but accepts a shorthand letter that can be used after a single dash.
func StringArrayP(name, shorthand string, value []string, usage string) *[]string {
return CommandLine.StringArrayP(name, shorthand, value, usage)
}

View file

@ -1,13 +1,11 @@
package pflag package pflag
import ( import (
"bytes"
"encoding/csv" "encoding/csv"
"fmt"
"strings" "strings"
) )
var _ = fmt.Fprint
// -- stringSlice Value // -- stringSlice Value
type stringSliceValue struct { type stringSliceValue struct {
value *[]string value *[]string
@ -21,10 +19,28 @@ func newStringSliceValue(val []string, p *[]string) *stringSliceValue {
return ssv return ssv
} }
func (s *stringSliceValue) Set(val string) error { func readAsCSV(val string) ([]string, error) {
if val == "" {
return []string{}, nil
}
stringReader := strings.NewReader(val) stringReader := strings.NewReader(val)
csvReader := csv.NewReader(stringReader) csvReader := csv.NewReader(stringReader)
v, err := csvReader.Read() return csvReader.Read()
}
func writeAsCSV(vals []string) (string, error) {
b := &bytes.Buffer{}
w := csv.NewWriter(b)
err := w.Write(vals)
if err != nil {
return "", err
}
w.Flush()
return strings.TrimSuffix(b.String(), "\n"), nil
}
func (s *stringSliceValue) Set(val string) error {
v, err := readAsCSV(val)
if err != nil { if err != nil {
return err return err
} }
@ -41,16 +57,18 @@ func (s *stringSliceValue) Type() string {
return "stringSlice" return "stringSlice"
} }
func (s *stringSliceValue) String() string { return "[" + strings.Join(*s.value, ",") + "]" } func (s *stringSliceValue) String() string {
str, _ := writeAsCSV(*s.value)
return "[" + str + "]"
}
func stringSliceConv(sval string) (interface{}, error) { func stringSliceConv(sval string) (interface{}, error) {
sval = strings.Trim(sval, "[]") sval = sval[1 : len(sval)-1]
// An empty string would cause a slice with one (empty) string // An empty string would cause a slice with one (empty) string
if len(sval) == 0 { if len(sval) == 0 {
return []string{}, nil return []string{}, nil
} }
v := strings.Split(sval, ",") return readAsCSV(sval)
return v, nil
} }
// GetStringSlice return the []string value of a flag with the given name // GetStringSlice return the []string value of a flag with the given name

View file

@ -1,9 +1,6 @@
package pflag package pflag
import ( import "strconv"
"fmt"
"strconv"
)
// -- uint Value // -- uint Value
type uintValue uint type uintValue uint
@ -23,7 +20,7 @@ func (i *uintValue) Type() string {
return "uint" return "uint"
} }
func (i *uintValue) String() string { return fmt.Sprintf("%v", *i) } func (i *uintValue) String() string { return strconv.FormatUint(uint64(*i), 10) }
func uintConv(sval string) (interface{}, error) { func uintConv(sval string) (interface{}, error) {
v, err := strconv.ParseUint(sval, 0, 0) v, err := strconv.ParseUint(sval, 0, 0)

View file

@ -1,9 +1,6 @@
package pflag package pflag
import ( import "strconv"
"fmt"
"strconv"
)
// -- uint16 value // -- uint16 value
type uint16Value uint16 type uint16Value uint16
@ -12,7 +9,7 @@ func newUint16Value(val uint16, p *uint16) *uint16Value {
*p = val *p = val
return (*uint16Value)(p) return (*uint16Value)(p)
} }
func (i *uint16Value) String() string { return fmt.Sprintf("%d", *i) }
func (i *uint16Value) Set(s string) error { func (i *uint16Value) Set(s string) error {
v, err := strconv.ParseUint(s, 0, 16) v, err := strconv.ParseUint(s, 0, 16)
*i = uint16Value(v) *i = uint16Value(v)
@ -23,6 +20,8 @@ func (i *uint16Value) Type() string {
return "uint16" return "uint16"
} }
func (i *uint16Value) String() string { return strconv.FormatUint(uint64(*i), 10) }
func uint16Conv(sval string) (interface{}, error) { func uint16Conv(sval string) (interface{}, error) {
v, err := strconv.ParseUint(sval, 0, 16) v, err := strconv.ParseUint(sval, 0, 16)
if err != nil { if err != nil {

View file

@ -1,18 +1,15 @@
package pflag package pflag
import ( import "strconv"
"fmt"
"strconv"
)
// -- uint16 value // -- uint32 value
type uint32Value uint32 type uint32Value uint32
func newUint32Value(val uint32, p *uint32) *uint32Value { func newUint32Value(val uint32, p *uint32) *uint32Value {
*p = val *p = val
return (*uint32Value)(p) return (*uint32Value)(p)
} }
func (i *uint32Value) String() string { return fmt.Sprintf("%d", *i) }
func (i *uint32Value) Set(s string) error { func (i *uint32Value) Set(s string) error {
v, err := strconv.ParseUint(s, 0, 32) v, err := strconv.ParseUint(s, 0, 32)
*i = uint32Value(v) *i = uint32Value(v)
@ -23,6 +20,8 @@ func (i *uint32Value) Type() string {
return "uint32" return "uint32"
} }
func (i *uint32Value) String() string { return strconv.FormatUint(uint64(*i), 10) }
func uint32Conv(sval string) (interface{}, error) { func uint32Conv(sval string) (interface{}, error) {
v, err := strconv.ParseUint(sval, 0, 32) v, err := strconv.ParseUint(sval, 0, 32)
if err != nil { if err != nil {

View file

@ -1,9 +1,6 @@
package pflag package pflag
import ( import "strconv"
"fmt"
"strconv"
)
// -- uint64 Value // -- uint64 Value
type uint64Value uint64 type uint64Value uint64
@ -23,7 +20,7 @@ func (i *uint64Value) Type() string {
return "uint64" return "uint64"
} }
func (i *uint64Value) String() string { return fmt.Sprintf("%v", *i) } func (i *uint64Value) String() string { return strconv.FormatUint(uint64(*i), 10) }
func uint64Conv(sval string) (interface{}, error) { func uint64Conv(sval string) (interface{}, error) {
v, err := strconv.ParseUint(sval, 0, 64) v, err := strconv.ParseUint(sval, 0, 64)

View file

@ -1,9 +1,6 @@
package pflag package pflag
import ( import "strconv"
"fmt"
"strconv"
)
// -- uint8 Value // -- uint8 Value
type uint8Value uint8 type uint8Value uint8
@ -23,7 +20,7 @@ func (i *uint8Value) Type() string {
return "uint8" return "uint8"
} }
func (i *uint8Value) String() string { return fmt.Sprintf("%v", *i) } func (i *uint8Value) String() string { return strconv.FormatUint(uint64(*i), 10) }
func uint8Conv(sval string) (interface{}, error) { func uint8Conv(sval string) (interface{}, error) {
v, err := strconv.ParseUint(sval, 0, 8) v, err := strconv.ParseUint(sval, 0, 8)

126
vendor/github.com/spf13/pflag/uint_slice.go generated vendored Normal file
View file

@ -0,0 +1,126 @@
package pflag
import (
"fmt"
"strconv"
"strings"
)
// -- uintSlice Value
type uintSliceValue struct {
value *[]uint
changed bool
}
func newUintSliceValue(val []uint, p *[]uint) *uintSliceValue {
uisv := new(uintSliceValue)
uisv.value = p
*uisv.value = val
return uisv
}
func (s *uintSliceValue) Set(val string) error {
ss := strings.Split(val, ",")
out := make([]uint, len(ss))
for i, d := range ss {
u, err := strconv.ParseUint(d, 10, 0)
if err != nil {
return err
}
out[i] = uint(u)
}
if !s.changed {
*s.value = out
} else {
*s.value = append(*s.value, out...)
}
s.changed = true
return nil
}
func (s *uintSliceValue) Type() string {
return "uintSlice"
}
func (s *uintSliceValue) String() string {
out := make([]string, len(*s.value))
for i, d := range *s.value {
out[i] = fmt.Sprintf("%d", d)
}
return "[" + strings.Join(out, ",") + "]"
}
func uintSliceConv(val string) (interface{}, error) {
val = strings.Trim(val, "[]")
// Empty string would cause a slice with one (empty) entry
if len(val) == 0 {
return []uint{}, nil
}
ss := strings.Split(val, ",")
out := make([]uint, len(ss))
for i, d := range ss {
u, err := strconv.ParseUint(d, 10, 0)
if err != nil {
return nil, err
}
out[i] = uint(u)
}
return out, nil
}
// GetUintSlice returns the []uint value of a flag with the given name.
func (f *FlagSet) GetUintSlice(name string) ([]uint, error) {
val, err := f.getFlagType(name, "uintSlice", uintSliceConv)
if err != nil {
return []uint{}, err
}
return val.([]uint), nil
}
// UintSliceVar defines a uintSlice flag with specified name, default value, and usage string.
// The argument p points to a []uint variable in which to store the value of the flag.
func (f *FlagSet) UintSliceVar(p *[]uint, name string, value []uint, usage string) {
f.VarP(newUintSliceValue(value, p), name, "", usage)
}
// UintSliceVarP is like UintSliceVar, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) UintSliceVarP(p *[]uint, name, shorthand string, value []uint, usage string) {
f.VarP(newUintSliceValue(value, p), name, shorthand, usage)
}
// UintSliceVar defines a uint[] flag with specified name, default value, and usage string.
// The argument p points to a uint[] variable in which to store the value of the flag.
func UintSliceVar(p *[]uint, name string, value []uint, usage string) {
CommandLine.VarP(newUintSliceValue(value, p), name, "", usage)
}
// UintSliceVarP is like the UintSliceVar, but accepts a shorthand letter that can be used after a single dash.
func UintSliceVarP(p *[]uint, name, shorthand string, value []uint, usage string) {
CommandLine.VarP(newUintSliceValue(value, p), name, shorthand, usage)
}
// UintSlice defines a []uint flag with specified name, default value, and usage string.
// The return value is the address of a []uint variable that stores the value of the flag.
func (f *FlagSet) UintSlice(name string, value []uint, usage string) *[]uint {
p := []uint{}
f.UintSliceVarP(&p, name, "", value, usage)
return &p
}
// UintSliceP is like UintSlice, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) UintSliceP(name, shorthand string, value []uint, usage string) *[]uint {
p := []uint{}
f.UintSliceVarP(&p, name, shorthand, value, usage)
return &p
}
// UintSlice defines a []uint flag with specified name, default value, and usage string.
// The return value is the address of a []uint variable that stores the value of the flag.
func UintSlice(name string, value []uint, usage string) *[]uint {
return CommandLine.UintSliceP(name, "", value, usage)
}
// UintSliceP is like UintSlice, but accepts a shorthand letter that can be used after a single dash.
func UintSliceP(name, shorthand string, value []uint, usage string) *[]uint {
return CommandLine.UintSliceP(name, shorthand, value, usage)
}

4
vendor/gopkg.in/mgo.v2/Makefile generated vendored
View file

@ -1,5 +1,5 @@
startdb: startdb:
@harness/setup.sh start @testdb/setup.sh start
stopdb: stopdb:
@harness/setup.sh stop @testdb/setup.sh stop

5
vendor/gopkg.in/mgo.v2/cluster.go generated vendored
View file

@ -588,10 +588,7 @@ func (cluster *mongoCluster) AcquireSocket(mode Mode, slaveOk bool, syncTimeout
mastersLen := cluster.masters.Len() mastersLen := cluster.masters.Len()
slavesLen := cluster.servers.Len() - mastersLen slavesLen := cluster.servers.Len() - mastersLen
debugf("Cluster has %d known masters and %d known slaves.", mastersLen, slavesLen) debugf("Cluster has %d known masters and %d known slaves.", mastersLen, slavesLen)
if mastersLen > 0 && !(slaveOk && mode == Secondary) || slavesLen > 0 && slaveOk { if !(slaveOk && mode == Secondary) && mastersLen > 0 || slaveOk && slavesLen > 0 {
break
}
if mastersLen > 0 && mode == Secondary && cluster.masters.HasMongos() {
break break
} }
if started.IsZero() { if started.IsZero() {

2
vendor/gopkg.in/mgo.v2/gridfs.go generated vendored
View file

@ -359,7 +359,7 @@ func (file *GridFile) assertMode(mode gfsFileMode) {
// SetChunkSize sets size of saved chunks. Once the file is written to, it // SetChunkSize sets size of saved chunks. Once the file is written to, it
// will be split in blocks of that size and each block saved into an // will be split in blocks of that size and each block saved into an
// independent chunk document. The default chunk size is 255kb. // independent chunk document. The default chunk size is 256kb.
// //
// It is a runtime error to call this function once the file has started // It is a runtime error to call this function once the file has started
// being written to. // being written to.

View file

@ -10,18 +10,14 @@ SECURITY_STATUS SEC_ENTRY sspi_acquire_credentials_handle(CredHandle *cred_handl
auth_identity.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI; auth_identity.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
auth_identity.User = (LPSTR) username; auth_identity.User = (LPSTR) username;
auth_identity.UserLength = strlen(username); auth_identity.UserLength = strlen(username);
auth_identity.Password = NULL;
auth_identity.PasswordLength = 0;
if(password){
auth_identity.Password = (LPSTR) password; auth_identity.Password = (LPSTR) password;
auth_identity.PasswordLength = strlen(password); auth_identity.PasswordLength = strlen(password);
}
auth_identity.Domain = (LPSTR) domain; auth_identity.Domain = (LPSTR) domain;
auth_identity.DomainLength = strlen(domain); auth_identity.DomainLength = strlen(domain);
return call_sspi_acquire_credentials_handle(NULL, SSPI_PACKAGE_NAME, SECPKG_CRED_OUTBOUND, NULL, &auth_identity, NULL, NULL, cred_handle, &ignored); return call_sspi_acquire_credentials_handle(NULL, SSPI_PACKAGE_NAME, SECPKG_CRED_OUTBOUND, NULL, &auth_identity, NULL, NULL, cred_handle, &ignored);
} }
int sspi_step(CredHandle *cred_handle, int has_context, CtxtHandle *context, PVOID buffer, ULONG buffer_length, PVOID *out_buffer, ULONG *out_buffer_length, char *target) int sspi_step(CredHandle *cred_handle, int has_context, CtxtHandle *context, PVOID *buffer, ULONG *buffer_length, char *target)
{ {
SecBufferDesc inbuf; SecBufferDesc inbuf;
SecBuffer in_bufs[1]; SecBuffer in_bufs[1];
@ -34,8 +30,8 @@ int sspi_step(CredHandle *cred_handle, int has_context, CtxtHandle *context, PVO
inbuf.ulVersion = SECBUFFER_VERSION; inbuf.ulVersion = SECBUFFER_VERSION;
inbuf.cBuffers = 1; inbuf.cBuffers = 1;
inbuf.pBuffers = in_bufs; inbuf.pBuffers = in_bufs;
in_bufs[0].pvBuffer = buffer; in_bufs[0].pvBuffer = *buffer;
in_bufs[0].cbBuffer = buffer_length; in_bufs[0].cbBuffer = *buffer_length;
in_bufs[0].BufferType = SECBUFFER_TOKEN; in_bufs[0].BufferType = SECBUFFER_TOKEN;
} }
@ -61,9 +57,9 @@ int sspi_step(CredHandle *cred_handle, int has_context, CtxtHandle *context, PVO
&context_attr, &context_attr,
NULL); NULL);
*out_buffer = malloc(out_bufs[0].cbBuffer); *buffer = malloc(out_bufs[0].cbBuffer);
*out_buffer_length = out_bufs[0].cbBuffer; *buffer_length = out_bufs[0].cbBuffer;
memcpy(*out_buffer, out_bufs[0].pvBuffer, *out_buffer_length); memcpy(*buffer, out_bufs[0].pvBuffer, *buffer_length);
return ret; return ret;
} }

View file

@ -101,8 +101,6 @@ func (ss *saslSession) Step(serverData []byte) (clientData []byte, done bool, er
} }
var buffer C.PVOID var buffer C.PVOID
var bufferLength C.ULONG var bufferLength C.ULONG
var outBuffer C.PVOID
var outBufferLength C.ULONG
if len(serverData) > 0 { if len(serverData) > 0 {
buffer = (C.PVOID)(unsafe.Pointer(&serverData[0])) buffer = (C.PVOID)(unsafe.Pointer(&serverData[0]))
bufferLength = C.ULONG(len(serverData)) bufferLength = C.ULONG(len(serverData))
@ -110,20 +108,20 @@ func (ss *saslSession) Step(serverData []byte) (clientData []byte, done bool, er
var status C.int var status C.int
if ss.authComplete { if ss.authComplete {
// Step 3: last bit of magic to use the correct server credentials // Step 3: last bit of magic to use the correct server credentials
status = C.sspi_send_client_authz_id(&ss.context, &outBuffer, &outBufferLength, ss.cstr(ss.userPlusRealm)) status = C.sspi_send_client_authz_id(&ss.context, &buffer, &bufferLength, ss.cstr(ss.userPlusRealm))
} else { } else {
// Step 1 + Step 2: set up security context with the server and TGT // Step 1 + Step 2: set up security context with the server and TGT
status = C.sspi_step(&ss.credHandle, ss.hasContext, &ss.context, buffer, bufferLength, &outBuffer, &outBufferLength, ss.cstr(ss.target)) status = C.sspi_step(&ss.credHandle, ss.hasContext, &ss.context, &buffer, &bufferLength, ss.cstr(ss.target))
} }
if outBuffer != C.PVOID(nil) { if buffer != C.PVOID(nil) {
defer C.free(unsafe.Pointer(outBuffer)) defer C.free(unsafe.Pointer(buffer))
} }
if status != C.SEC_E_OK && status != C.SEC_I_CONTINUE_NEEDED { if status != C.SEC_E_OK && status != C.SEC_I_CONTINUE_NEEDED {
ss.errored = true ss.errored = true
return nil, false, ss.handleSSPIErrorCode(status) return nil, false, ss.handleSSPIErrorCode(status)
} }
clientData = C.GoBytes(unsafe.Pointer(outBuffer), C.int(outBufferLength)) clientData = C.GoBytes(unsafe.Pointer(buffer), C.int(bufferLength))
if status == C.SEC_E_OK { if status == C.SEC_E_OK {
ss.authComplete = true ss.authComplete = true
return clientData, true, nil return clientData, true, nil

View file

@ -3,5 +3,5 @@
#include "sspi_windows.h" #include "sspi_windows.h"
SECURITY_STATUS SEC_ENTRY sspi_acquire_credentials_handle(CredHandle* cred_handle, char* username, char* password, char* domain); SECURITY_STATUS SEC_ENTRY sspi_acquire_credentials_handle(CredHandle* cred_handle, char* username, char* password, char* domain);
int sspi_step(CredHandle* cred_handle, int has_context, CtxtHandle* context, PVOID buffer, ULONG buffer_length, PVOID* out_buffer, ULONG* out_buffer_length, char* target); int sspi_step(CredHandle* cred_handle, int has_context, CtxtHandle* context, PVOID* buffer, ULONG* buffer_length, char* target);
int sspi_send_client_authz_id(CtxtHandle* context, PVOID* buffer, ULONG* buffer_length, char* user_plus_realm); int sspi_send_client_authz_id(CtxtHandle* context, PVOID* buffer, ULONG* buffer_length, char* user_plus_realm);

11
vendor/gopkg.in/mgo.v2/server.go generated vendored
View file

@ -402,15 +402,6 @@ func (servers *mongoServers) Empty() bool {
return len(servers.slice) == 0 return len(servers.slice) == 0
} }
func (servers *mongoServers) HasMongos() bool {
for _, s := range servers.slice {
if s.Info().Mongos {
return true
}
}
return false
}
// BestFit returns the best guess of what would be the most interesting // BestFit returns the best guess of what would be the most interesting
// server to perform operations on at this point in time. // server to perform operations on at this point in time.
func (servers *mongoServers) BestFit(mode Mode, serverTags []bson.D) *mongoServer { func (servers *mongoServers) BestFit(mode Mode, serverTags []bson.D) *mongoServer {
@ -430,8 +421,6 @@ func (servers *mongoServers) BestFit(mode Mode, serverTags []bson.D) *mongoServe
switch { switch {
case serverTags != nil && !next.info.Mongos && !next.hasTags(serverTags): case serverTags != nil && !next.info.Mongos && !next.hasTags(serverTags):
// Must have requested tags. // Must have requested tags.
case mode == Secondary && next.info.Master && !next.info.Mongos:
// Must be a secondary or mongos.
case next.info.Master != best.info.Master && mode != Nearest: case next.info.Master != best.info.Master && mode != Nearest:
// Prefer slaves, unless the mode is PrimaryPreferred. // Prefer slaves, unless the mode is PrimaryPreferred.
swap = (mode == PrimaryPreferred) != best.info.Master swap = (mode == PrimaryPreferred) != best.info.Master

134
vendor/gopkg.in/mgo.v2/session.go generated vendored
View file

@ -146,10 +146,7 @@ var (
ErrCursor = errors.New("invalid cursor") ErrCursor = errors.New("invalid cursor")
) )
const ( const defaultPrefetch = 0.25
defaultPrefetch = 0.25
maxUpsertRetries = 5
)
// Dial establishes a new session to the cluster identified by the given seed // Dial establishes a new session to the cluster identified by the given seed
// server(s). The session will enable communication with all of the servers in // server(s). The session will enable communication with all of the servers in
@ -1011,8 +1008,6 @@ type indexSpec struct {
DefaultLanguage string "default_language,omitempty" DefaultLanguage string "default_language,omitempty"
LanguageOverride string "language_override,omitempty" LanguageOverride string "language_override,omitempty"
TextIndexVersion int "textIndexVersion,omitempty" TextIndexVersion int "textIndexVersion,omitempty"
Collation *Collation "collation,omitempty"
} }
type Index struct { type Index struct {
@ -1051,54 +1046,6 @@ type Index struct {
// from the weighted sum of the frequency for each of the indexed fields in // from the weighted sum of the frequency for each of the indexed fields in
// that document. The default field weight is 1. // that document. The default field weight is 1.
Weights map[string]int Weights map[string]int
// Collation defines the collation to use for the index.
Collation *Collation
}
type Collation struct {
// Locale defines the collation locale.
Locale string `bson:"locale"`
// CaseLevel defines whether to turn case sensitivity on at strength 1 or 2.
CaseLevel bool `bson:"caseLevel,omitempty"`
// CaseFirst may be set to "upper" or "lower" to define whether
// to have uppercase or lowercase items first. Default is "off".
CaseFirst string `bson:"caseFirst,omitempty"`
// Strength defines the priority of comparison properties, as follows:
//
// 1 (primary) - Strongest level, denote difference between base characters
// 2 (secondary) - Accents in characters are considered secondary differences
// 3 (tertiary) - Upper and lower case differences in characters are
// distinguished at the tertiary level
// 4 (quaternary) - When punctuation is ignored at level 1-3, an additional
// level can be used to distinguish words with and without
// punctuation. Should only be used if ignoring punctuation
// is required or when processing Japanese text.
// 5 (identical) - When all other levels are equal, the identical level is
// used as a tiebreaker. The Unicode code point values of
// the NFD form of each string are compared at this level,
// just in case there is no difference at levels 1-4
//
// Strength defaults to 3.
Strength int `bson:"strength,omitempty"`
// NumericOrdering defines whether to order numbers based on numerical
// order and not collation order.
NumericOrdering bool `bson:"numericOrdering,omitempty"`
// Alternate controls whether spaces and punctuation are considered base characters.
// May be set to "non-ignorable" (spaces and punctuation considered base characters)
// or "shifted" (spaces and punctuation not considered base characters, and only
// distinguished at strength > 3). Defaults to "non-ignorable".
Alternate string `bson:"alternate,omitempty"`
// Backwards defines whether to have secondary differences considered in reverse order,
// as done in the French language.
Backwards bool `bson:"backwards,omitempty"`
} }
// mgo.v3: Drop Minf and Maxf and transform Min and Max to floats. // mgo.v3: Drop Minf and Maxf and transform Min and Max to floats.
@ -1292,7 +1239,6 @@ func (c *Collection) EnsureIndex(index Index) error {
Weights: keyInfo.weights, Weights: keyInfo.weights,
DefaultLanguage: index.DefaultLanguage, DefaultLanguage: index.DefaultLanguage,
LanguageOverride: index.LanguageOverride, LanguageOverride: index.LanguageOverride,
Collation: index.Collation,
} }
if spec.Min == 0 && spec.Max == 0 { if spec.Min == 0 && spec.Max == 0 {
@ -1507,7 +1453,6 @@ func indexFromSpec(spec indexSpec) Index {
DefaultLanguage: spec.DefaultLanguage, DefaultLanguage: spec.DefaultLanguage,
LanguageOverride: spec.LanguageOverride, LanguageOverride: spec.LanguageOverride,
ExpireAfter: time.Duration(spec.ExpireAfter) * time.Second, ExpireAfter: time.Duration(spec.ExpireAfter) * time.Second,
Collation: spec.Collation,
} }
if float64(int(spec.Min)) == spec.Min && float64(int(spec.Max)) == spec.Max { if float64(int(spec.Min)) == spec.Min && float64(int(spec.Max)) == spec.Max {
index.Min = int(spec.Min) index.Min = int(spec.Min)
@ -1637,8 +1582,6 @@ func (s *Session) Refresh() {
// SetMode changes the consistency mode for the session. // SetMode changes the consistency mode for the session.
// //
// The default mode is Strong.
//
// In the Strong consistency mode reads and writes will always be made to // In the Strong consistency mode reads and writes will always be made to
// the primary server using a unique connection so that reads and writes are // the primary server using a unique connection so that reads and writes are
// fully consistent, ordered, and observing the most up-to-date data. // fully consistent, ordered, and observing the most up-to-date data.
@ -1712,8 +1655,6 @@ func (s *Session) SetSyncTimeout(d time.Duration) {
// SetSocketTimeout sets the amount of time to wait for a non-responding // SetSocketTimeout sets the amount of time to wait for a non-responding
// socket to the database before it is forcefully closed. // socket to the database before it is forcefully closed.
//
// The default timeout is 1 minute.
func (s *Session) SetSocketTimeout(d time.Duration) { func (s *Session) SetSocketTimeout(d time.Duration) {
s.m.Lock() s.m.Lock()
s.sockTimeout = d s.sockTimeout = d
@ -1844,9 +1785,6 @@ func (s *Session) Safe() (safe *Safe) {
// will be followed by a getLastError command with the specified parameters, // will be followed by a getLastError command with the specified parameters,
// to ensure the request was correctly processed. // to ensure the request was correctly processed.
// //
// The default is &Safe{}, meaning check for errors and use the default
// behavior for all fields.
//
// The safe.W parameter determines how many servers should confirm a write // The safe.W parameter determines how many servers should confirm a write
// before the operation is considered successful. If set to 0 or 1, the // before the operation is considered successful. If set to 0 or 1, the
// command will return as soon as the primary is done with the request. // command will return as soon as the primary is done with the request.
@ -2395,6 +2333,7 @@ type queryError struct {
Assertion string Assertion string
Code int Code int
AssertionCode int "assertionCode" AssertionCode int "assertionCode"
LastError *LastError "lastErrorObject"
} }
type QueryError struct { type QueryError struct {
@ -2539,15 +2478,7 @@ func (c *Collection) Upsert(selector interface{}, update interface{}) (info *Cha
Flags: 1, Flags: 1,
Upsert: true, Upsert: true,
} }
var lerr *LastError lerr, err := c.writeOp(&op, true)
for i := 0; i < maxUpsertRetries; i++ {
lerr, err = c.writeOp(&op, true)
// Retry duplicate key errors on upserts.
// https://docs.mongodb.com/v3.2/reference/method/db.collection.update/#use-unique-indexes
if !IsDup(err) {
break
}
}
if err == nil && lerr != nil { if err == nil && lerr != nil {
info = &ChangeInfo{} info = &ChangeInfo{}
if lerr.UpdatedExisting { if lerr.UpdatedExisting {
@ -3052,6 +2983,9 @@ func checkQueryError(fullname string, d []byte) error {
Error: Error:
result := &queryError{} result := &queryError{}
bson.Unmarshal(d, result) bson.Unmarshal(d, result)
if result.LastError != nil {
return result.LastError
}
if result.Err == "" && result.ErrMsg == "" { if result.Err == "" && result.ErrMsg == "" {
return nil return nil
} }
@ -3267,14 +3201,13 @@ func (db *Database) run(socket *mongoSocket, cmd, result interface{}) (err error
} }
if result != nil { if result != nil {
err = bson.Unmarshal(data, result) err = bson.Unmarshal(data, result)
if err != nil { if err == nil {
debugf("Run command unmarshaling failed: %#v", op, err)
return err
}
if globalDebug && globalLogger != nil {
var res bson.M var res bson.M
bson.Unmarshal(data, &res) bson.Unmarshal(data, &res)
debugf("Run command unmarshaled: %#v, result: %#v", op, res) debugf("Run command unmarshaled: %#v, result: %#v", op, res)
} else {
debugf("Run command unmarshaling failed: %#v", op, err)
return err
} }
} }
return checkQueryError(op.collection, data) return checkQueryError(op.collection, data)
@ -3621,34 +3554,6 @@ func (iter *Iter) Close() error {
return err return err
} }
// Done returns true only if a follow up Next call is guaranteed
// to return false.
//
// For an iterator created with Tail, Done may return false for
// an iterator that has no more data. Otherwise it's guaranteed
// to return false only if there is data or an error happened.
//
// Done may block waiting for a pending query to verify whether
// more data is actually available or not.
func (iter *Iter) Done() bool {
iter.m.Lock()
defer iter.m.Unlock()
for {
if iter.docData.Len() > 0 {
return false
}
if iter.docsToReceive > 1 {
return true
}
if iter.docsToReceive > 0 {
iter.gotReply.Wait()
continue
}
return iter.op.cursorId == 0
}
}
// Timeout returns true if Next returned false due to a timeout of // Timeout returns true if Next returned false due to a timeout of
// a tailable cursor. In those cases, Next may be called again to continue // a tailable cursor. In those cases, Next may be called again to continue
// the iteration at the previous cursor position. // the iteration at the previous cursor position.
@ -4303,17 +4208,8 @@ func (q *Query) Apply(change Change, result interface{}) (info *ChangeInfo, err
session.SetMode(Strong, false) session.SetMode(Strong, false)
var doc valueResult var doc valueResult
for i := 0; i < maxUpsertRetries; i++ {
err = session.DB(dbname).Run(&cmd, &doc) err = session.DB(dbname).Run(&cmd, &doc)
if err != nil {
if err == nil {
break
}
if change.Upsert && IsDup(err) {
// Retry duplicate key errors on upserts.
// https://docs.mongodb.com/v3.2/reference/method/db.collection.update/#use-unique-indexes
continue
}
if qerr, ok := err.(*QueryError); ok && qerr.Message == "No matching object found" { if qerr, ok := err.(*QueryError); ok && qerr.Message == "No matching object found" {
return nil, ErrNotFound return nil, ErrNotFound
} }
@ -4362,12 +4258,12 @@ type BuildInfo struct {
// equal to the provided version number. If more than one number is // equal to the provided version number. If more than one number is
// provided, numbers will be considered as major, minor, and so on. // provided, numbers will be considered as major, minor, and so on.
func (bi *BuildInfo) VersionAtLeast(version ...int) bool { func (bi *BuildInfo) VersionAtLeast(version ...int) bool {
for i, vi := range version { for i := range version {
if i == len(bi.VersionArray) { if i == len(bi.VersionArray) {
return false return false
} }
if bivi := bi.VersionArray[i]; bivi != vi { if bi.VersionArray[i] < version[i] {
return bivi >= vi return false
} }
} }
return true return true
@ -4629,7 +4525,7 @@ func (c *Collection) writeOp(op interface{}, ordered bool) (lerr *LastError, err
lerr.N += oplerr.N lerr.N += oplerr.N
lerr.modified += oplerr.modified lerr.modified += oplerr.modified
if err != nil { if err != nil {
for ei := range oplerr.ecases { for ei := range lerr.ecases {
oplerr.ecases[ei].Index += i oplerr.ecases[ei].Index += i
} }
lerr.ecases = append(lerr.ecases, oplerr.ecases...) lerr.ecases = append(lerr.ecases, oplerr.ecases...)

112
vendor/vendor.json vendored
View file

@ -3,16 +3,16 @@
"ignore": "test", "ignore": "test",
"package": [ "package": [
{ {
"checksumSHA1": "iIUYZyoanCQQTUaWsu8b+iOSPt4=", "checksumSHA1": "g/V4qrXjUGG9B+e3hB+4NAYJ5Gs=",
"path": "github.com/gorilla/context", "path": "github.com/gorilla/context",
"revision": "a8d44e7d8e4d532b6a27a02dd82abb31cc1b01bd", "revision": "08b5f424b9271eedf6f9f0ce86cb9396ed337a42",
"revisionTime": "2016-04-22T13:42:37Z" "revisionTime": "2016-08-17T18:46:32Z"
}, },
{ {
"checksumSHA1": "/WoZGh9L9hFOcrU9d7rAeFPDy1U=", "checksumSHA1": "oDsOWp1nBMGpQ9gCFnczGADwZTE=",
"path": "github.com/gorilla/mux", "path": "github.com/gorilla/mux",
"revision": "9c19ed558d5df4da88e2ade9c8940d742aef0e7e", "revision": "0a192a193177452756c362c20087ddafcf6829c4",
"revisionTime": "2016-05-02T17:56:24Z" "revisionTime": "2016-09-02T15:33:43Z"
}, },
{ {
"checksumSHA1": "41syjEeyv9W/6j89XArd1yyWNBU=", "checksumSHA1": "41syjEeyv9W/6j89XArd1yyWNBU=",
@ -21,10 +21,10 @@
"revisionTime": "2016-04-13T04:16:32Z" "revisionTime": "2016-04-13T04:16:32Z"
}, },
{ {
"checksumSHA1": "klfNfdEPsrWYLps2qBkLgfJsNYI=", "checksumSHA1": "hEnH6sgR83Qfx7UNnphNNlelmj0=",
"path": "github.com/gorilla/websocket", "path": "github.com/gorilla/websocket",
"revision": "a69d25be2fe2923a97c2af6849b2f52426f68fc0", "revision": "a91eba7f97777409bc2c443f5534d41dd20c5720",
"revisionTime": "2016-08-02T13:32:03Z" "revisionTime": "2017-03-19T17:27:27Z"
}, },
{ {
"checksumSHA1": "jOXIxsHHi2+Kq4evLnADdtynshs=", "checksumSHA1": "jOXIxsHHi2+Kq4evLnADdtynshs=",
@ -33,7 +33,7 @@
"revisionTime": "2014-10-26T12:11:21Z" "revisionTime": "2014-10-26T12:11:21Z"
}, },
{ {
"checksumSHA1": "6TsXaoh9csBUNp2OkeLJPVyaHuo=", "checksumSHA1": "eSeZeKPsZJy0bpJy3Lgcs9xYBtI=",
"path": "github.com/ian-kent/go-log/appenders", "path": "github.com/ian-kent/go-log/appenders",
"revision": "5731446c36ab9f716106ce0731f484c50fdf1ad1", "revision": "5731446c36ab9f716106ce0731f484c50fdf1ad1",
"revisionTime": "2016-01-13T21:12:17Z" "revisionTime": "2016-01-13T21:12:17Z"
@ -77,80 +77,80 @@
{ {
"checksumSHA1": "3mLt9lHm23gzSdVgjZQmaNxoR2Q=", "checksumSHA1": "3mLt9lHm23gzSdVgjZQmaNxoR2Q=",
"path": "github.com/mailhog/MailHog-Server/api", "path": "github.com/mailhog/MailHog-Server/api",
"revision": "921a9aa69cd19d72557eca20af047851a471e402", "revision": "0c43753ba6d5865286f0973a811eb7fc7e7353a3",
"revisionTime": "2016-07-10T18:18:07Z" "revisionTime": "2017-04-16T18:46:45Z"
}, },
{ {
"checksumSHA1": "fBFfD/YTkxKmuCE7SlEL4/im9nU=", "checksumSHA1": "fBFfD/YTkxKmuCE7SlEL4/im9nU=",
"path": "github.com/mailhog/MailHog-Server/config", "path": "github.com/mailhog/MailHog-Server/config",
"revision": "921a9aa69cd19d72557eca20af047851a471e402", "revision": "0c43753ba6d5865286f0973a811eb7fc7e7353a3",
"revisionTime": "2016-07-10T18:18:07Z" "revisionTime": "2017-04-16T18:46:45Z"
}, },
{ {
"checksumSHA1": "P0jngUraVKUOjL+uPeTc6XPAAPs=", "checksumSHA1": "P0jngUraVKUOjL+uPeTc6XPAAPs=",
"path": "github.com/mailhog/MailHog-Server/monkey", "path": "github.com/mailhog/MailHog-Server/monkey",
"revision": "921a9aa69cd19d72557eca20af047851a471e402", "revision": "0c43753ba6d5865286f0973a811eb7fc7e7353a3",
"revisionTime": "2016-07-10T18:18:07Z" "revisionTime": "2017-04-16T18:46:45Z"
}, },
{ {
"checksumSHA1": "ShfenzyuLqJ86PzEB4PuP+6v2h0=", "checksumSHA1": "ShfenzyuLqJ86PzEB4PuP+6v2h0=",
"path": "github.com/mailhog/MailHog-Server/smtp", "path": "github.com/mailhog/MailHog-Server/smtp",
"revision": "921a9aa69cd19d72557eca20af047851a471e402", "revision": "0c43753ba6d5865286f0973a811eb7fc7e7353a3",
"revisionTime": "2016-07-10T18:18:07Z" "revisionTime": "2017-04-16T18:46:45Z"
}, },
{ {
"checksumSHA1": "c+xf40wui4+o6gvZ+8NRL1oRviY=", "checksumSHA1": "c+xf40wui4+o6gvZ+8NRL1oRviY=",
"path": "github.com/mailhog/MailHog-Server/websockets", "path": "github.com/mailhog/MailHog-Server/websockets",
"revision": "921a9aa69cd19d72557eca20af047851a471e402", "revision": "0c43753ba6d5865286f0973a811eb7fc7e7353a3",
"revisionTime": "2016-07-10T18:18:07Z" "revisionTime": "2017-04-16T18:46:45Z"
}, },
{ {
"checksumSHA1": "W1EzMlu4z1V8MbzGZdrx6RJ50o0=", "checksumSHA1": "At166WzE7Ma17bS+0FOpT8B28dA=",
"path": "github.com/mailhog/MailHog-UI/assets", "path": "github.com/mailhog/MailHog-UI/assets",
"revision": "bb034fed0530baa12c3a9fae44b8aef39b370c3b", "revision": "d3d5082cb7a3cc2683cf0ff9055792ef75170824",
"revisionTime": "2016-11-15T21:43:29Z" "revisionTime": "2017-04-16T18:44:30Z"
}, },
{ {
"checksumSHA1": "Sx9nmFV6wOzDqJIALIEEDLJnnEM=", "checksumSHA1": "Sx9nmFV6wOzDqJIALIEEDLJnnEM=",
"path": "github.com/mailhog/MailHog-UI/config", "path": "github.com/mailhog/MailHog-UI/config",
"revision": "2ed49d703422c00c734882808bec9872cb8fc6ce", "revision": "d3d5082cb7a3cc2683cf0ff9055792ef75170824",
"revisionTime": "2016-07-10T18:14:28Z" "revisionTime": "2017-04-16T18:44:30Z"
}, },
{ {
"checksumSHA1": "66iQbxUhUQuur+pyVwdK4IqFYXs=", "checksumSHA1": "66iQbxUhUQuur+pyVwdK4IqFYXs=",
"path": "github.com/mailhog/MailHog-UI/web", "path": "github.com/mailhog/MailHog-UI/web",
"revision": "2ed49d703422c00c734882808bec9872cb8fc6ce", "revision": "d3d5082cb7a3cc2683cf0ff9055792ef75170824",
"revisionTime": "2016-07-10T18:14:28Z" "revisionTime": "2017-04-16T18:44:30Z"
}, },
{ {
"checksumSHA1": "ZhN2RVIK/L9LO9WGjducyi2buIk=", "checksumSHA1": "ZhN2RVIK/L9LO9WGjducyi2buIk=",
"path": "github.com/mailhog/data", "path": "github.com/mailhog/data",
"revision": "da1c207f0293654419657db063f7d74f5fb550ab", "revision": "98fee9be8f0ca761ad40016ff0dfb4e4d81e2aa8",
"revisionTime": "2016-07-07T21:39:15Z" "revisionTime": "2016-08-10T07:45:39Z"
}, },
{ {
"checksumSHA1": "vyMXU+/pSliAx1yRf6YdKRhF9Ik=", "checksumSHA1": "vyMXU+/pSliAx1yRf6YdKRhF9Ik=",
"path": "github.com/mailhog/http", "path": "github.com/mailhog/http",
"revision": "066a2dfd9d8bf7a94a2df4e1b4b9db3556b56bbb", "revision": "f60054419202104f75c0fcaf132ca6e2ceae37f4",
"revisionTime": "2015-10-06T19:19:25Z" "revisionTime": "2016-08-10T07:45:55Z"
}, },
{ {
"checksumSHA1": "heQAWAi4zskIWnHQlPXLhv9EXyQ=", "checksumSHA1": "fAi7lDdIXYe0NIXdDKci8jsCjLc=",
"path": "github.com/mailhog/mhsendmail/cmd", "path": "github.com/mailhog/mhsendmail/cmd",
"revision": "3882be848aee17512ea9b7ba6044ef4eb9f813ad", "revision": "c097b3bc3fe79388dd44769557c61f424705816e",
"revisionTime": "2016-07-07T20:27:15Z" "revisionTime": "2016-09-20T20:42:33Z"
}, },
{ {
"checksumSHA1": "eD34ZZL4TFFtDl7tCXMEpp+tDSU=", "checksumSHA1": "eD34ZZL4TFFtDl7tCXMEpp+tDSU=",
"path": "github.com/mailhog/smtp", "path": "github.com/mailhog/smtp",
"revision": "0e36ecc166f43c24cc7b34c9db695119899f0062", "revision": "0c4e9b7e0625fec61d0c30d7b2f6c62852be6c54",
"revisionTime": "2015-12-10T17:32:52Z" "revisionTime": "2016-11-19T23:01:07Z"
}, },
{ {
"checksumSHA1": "1VoLtRGkR3BchTiAMDAnabctM/Q=", "checksumSHA1": "VFMqtv5B8sXJR50Rwn21jjJZw3E=",
"path": "github.com/mailhog/storage", "path": "github.com/mailhog/storage",
"revision": "4905934e26472cc98306d257a519a919b405a66d", "revision": "426662792547f066565458d62f52cb05fabc5535",
"revisionTime": "2016-07-07T20:35:54Z" "revisionTime": "2016-11-15T21:39:50Z"
}, },
{ {
"checksumSHA1": "mbhJnsNwGAwkTQH5c2hVRO9YmxA=", "checksumSHA1": "mbhJnsNwGAwkTQH5c2hVRO9YmxA=",
@ -159,10 +159,10 @@
"revisionTime": "2016-01-29T03:59:39Z" "revisionTime": "2016-01-29T03:59:39Z"
}, },
{ {
"checksumSHA1": "s0GYwa3YNJJXON/b+6ci2Sz9mEw=", "checksumSHA1": "5KvHyB1CImtwZT3fwNkNUlc8R0k=",
"path": "github.com/spf13/pflag", "path": "github.com/spf13/pflag",
"revision": "cb88ea77998c3f024757528e3305022ab50b43be", "revision": "9ff6c6923cfffbcd502984b8e0c80539a94968b7",
"revisionTime": "2016-03-17T00:02:28Z" "revisionTime": "2017-01-30T21:42:45Z"
}, },
{ {
"checksumSHA1": "pf4v+LE9ec8YJ6KAv46/OA9U6os=", "checksumSHA1": "pf4v+LE9ec8YJ6KAv46/OA9U6os=",
@ -173,26 +173,26 @@
{ {
"checksumSHA1": "aTiJJQgo4zVRwQ9O3y0IY7O9cAk=", "checksumSHA1": "aTiJJQgo4zVRwQ9O3y0IY7O9cAk=",
"path": "github.com/tinylib/msgp/msgp", "path": "github.com/tinylib/msgp/msgp",
"revision": "58d334d948735fbc930e06def9d6870de827e07d", "revision": "05e600edf28f1b907005ddf6b0488fe68b819971",
"revisionTime": "2016-03-28T02:32:21Z" "revisionTime": "2016-05-24T15:06:06Z"
}, },
{ {
"checksumSHA1": "vE43s37+4CJ2CDU6TlOUOYE0K9c=", "checksumSHA1": "vE43s37+4CJ2CDU6TlOUOYE0K9c=",
"path": "golang.org/x/crypto/bcrypt", "path": "golang.org/x/crypto/bcrypt",
"revision": "77f4136a99ffb5ecdbdd0226bd5cb146cf56bc0e", "revision": "f6b343c37ca80bfa8ea539da67a0b621f84fab1d",
"revisionTime": "2016-06-07T10:36:12Z" "revisionTime": "2016-12-21T04:54:10Z"
}, },
{ {
"checksumSHA1": "JsJdKXhz87gWenMwBeejTOeNE7k=", "checksumSHA1": "JsJdKXhz87gWenMwBeejTOeNE7k=",
"path": "golang.org/x/crypto/blowfish", "path": "golang.org/x/crypto/blowfish",
"revision": "77f4136a99ffb5ecdbdd0226bd5cb146cf56bc0e", "revision": "f6b343c37ca80bfa8ea539da67a0b621f84fab1d",
"revisionTime": "2016-06-07T10:36:12Z" "revisionTime": "2016-12-21T04:54:10Z"
}, },
{ {
"checksumSHA1": "Ye8lEnmzH4uUJ9GONq/Tp3Pekmo=", "checksumSHA1": "o+ZqT87RLprKCGKMboo5vCxdAvA=",
"path": "gopkg.in/mgo.v2", "path": "gopkg.in/mgo.v2",
"revision": "01084657862d7b12d3b10ffd0394357abf8f8bc2", "revision": "29cc868a5ca65f401ff318143f9408d02f4799cc",
"revisionTime": "2016-08-01T21:38:24Z" "revisionTime": "2016-06-09T18:00:28Z"
}, },
{ {
"checksumSHA1": "zTjQOFGy4XTv4L33Kd2FhrV+mbM=", "checksumSHA1": "zTjQOFGy4XTv4L33Kd2FhrV+mbM=",
@ -207,16 +207,16 @@
"revisionTime": "2016-08-01T21:38:24Z" "revisionTime": "2016-08-01T21:38:24Z"
}, },
{ {
"checksumSHA1": "LEvMCnprte47qdAxWvQ/zRxVF1U=", "checksumSHA1": "CkCDTsyN2Lbj1cL8+oaYKoPpI9w=",
"path": "gopkg.in/mgo.v2/internal/sasl", "path": "gopkg.in/mgo.v2/internal/sasl",
"revision": "01084657862d7b12d3b10ffd0394357abf8f8bc2", "revision": "29cc868a5ca65f401ff318143f9408d02f4799cc",
"revisionTime": "2016-08-01T21:38:24Z" "revisionTime": "2016-06-09T18:00:28Z"
}, },
{ {
"checksumSHA1": "+1WDRPaOphSCmRMxVPIPBV4aubc=", "checksumSHA1": "+1WDRPaOphSCmRMxVPIPBV4aubc=",
"path": "gopkg.in/mgo.v2/internal/scram", "path": "gopkg.in/mgo.v2/internal/scram",
"revision": "01084657862d7b12d3b10ffd0394357abf8f8bc2", "revision": "29cc868a5ca65f401ff318143f9408d02f4799cc",
"revisionTime": "2016-08-01T21:38:24Z" "revisionTime": "2016-06-09T18:00:28Z"
} }
], ],
"rootPath": "github.com/mailhog/MailHog" "rootPath": "github.com/mailhog/MailHog"