diff --git a/website/camweb.go b/website/camweb.go index 3d6e9cd02..8cd1713fd 100644 --- a/website/camweb.go +++ b/website/camweb.go @@ -82,6 +82,8 @@ var ( gceLogName = flag.String("gce_log_name", "", "GCE Cloud Logging log name; if non-empty, logs go to Cloud Logging instead of Apache-style local disk log files") gceJWTFile = flag.String("gce_jwt_file", "", "If non-empty, a filename to the GCE Service Account's JWT (JSON) config file.") gitContainer = flag.Bool("git_container", false, "Use git from the `camlistore/git` Docker container; if false, the system `git` is used.") + + flagChromeBugRepro = flag.Bool("chrome_bug", false, "Run the chrome bug repro demo for issue #660. True in production.") ) var ( @@ -466,6 +468,7 @@ func setProdFlags() { log.Fatal("can't use dev mode in production") } log.Printf("Running in production; configuring prod flags & containers") + *flagChromeBugRepro = true *httpAddr = ":80" *httpsAddr = ":443" *buildbotBackend = "https://travis-ci.org/camlistore/camlistore" @@ -755,6 +758,12 @@ func main() { }() } + if *flagChromeBugRepro { + go func() { + log.Printf("Repro handler failed: %v", repro(":8001", "foo:bar")) + }() + } + select { case err := <-emailErr: log.Fatalf("Error sending emails: %v", err) diff --git a/website/chrome_bug-repro.go b/website/chrome_bug-repro.go new file mode 100644 index 000000000..fd0df1e37 --- /dev/null +++ b/website/chrome_bug-repro.go @@ -0,0 +1,232 @@ +package main + +import ( + "bytes" + "encoding/base64" + "fmt" + "io" + "log" + "mime" + "net/http" + "regexp" + "strings" + "time" +) + +var basicAuthPattern = regexp.MustCompile(`^Basic ([a-zA-Z0-9\+/=]+)`) + +// basicAuth returns the username and password provided in the Authorization +// header of the request, or an error if anything went wrong. +func basicAuth(req *http.Request) (user string, password string, err error) { + auth := req.Header.Get("Authorization") + if auth == "" { + return "", "", fmt.Errorf("Missing \"Authorization\" in header") + } + matches := basicAuthPattern.FindStringSubmatch(auth) + if len(matches) != 2 { + return "", "", fmt.Errorf("Bogus Authorization header") + } + encoded := matches[1] + enc := base64.StdEncoding + decBuf := make([]byte, enc.DecodedLen(len(encoded))) + n, err := enc.Decode(decBuf, []byte(encoded)) + if err != nil { + return "", "", err + } + pieces := strings.SplitN(string(decBuf[0:n]), ":", 2) + if len(pieces) != 2 { + return "", "", fmt.Errorf("didn't get two pieces") + } + return pieces[0], pieces[1], nil +} + +type userPass struct { + username string + password string +} + +func (up userPass) isAllowed(req *http.Request) bool { + user, pass, err := basicAuth(req) + if err != nil { + log.Printf("Authorization failed: %v", err) + return false + } + return user == up.username && pass == up.password +} + +func sendUnauthorized(rw http.ResponseWriter, req *http.Request) { + realm := "" + rw.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", realm)) + rw.WriteHeader(http.StatusUnauthorized) + fmt.Fprintf(rw, "
+ Open this page in a new incognito window, otherwise some caching messes up the repro. And apparently you also need to close all the other incognito windows where you tried this, when you want to retry it in a new one. + Open the debug console. +
+ ++ Start with: + + (username: foo, password: bar) + which should send a FormData with a Blob in it, + and notice that it fails (whereas it doesn't on firefox), because the Blob in the authenticated retry was not resent with the original contents. +
+ ++ Now do it again: + + and notice that it works this time, because the request is authenticated right from the start now, so there's no retry needed, therefore the bug can't happen. +
+ ++ Alternatively, load the page in a new incognito window, and start with a + or a (FormData without a Blob) + and notice that not only there's no problem with any of those, but also that a subsequent + + works fine too, for the same reasons explained above (no retry needed). +
+ ++ Note: the same kind of bug/behaviour can be observed when automatically following a 307 (which also does not fail on firefox). +
+ + +` +}