func (sh *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { suffix := httputil.PathSuffix(req) handlers := getHandler switch { case httputil.IsGet(req): // use default from above case req.Method == "POST": handlers = postHandler default: handlers = nil } fn := handlers[strings.TrimPrefix(suffix, "camli/search/")] if fn != nil { fn(sh, rw, req) return } // TODO: discovery for the endpoints & better error message with link to discovery info ret := camtypes.SearchErrorResponse{ Error: "Unsupported search path or method", ErrorType: "input", } httputil.ReturnJSON(rw, &ret) }
func wantsClosure(req *http.Request) bool { if httputil.IsGet(req) { suffix := httputil.PathSuffix(req) return closurePattern.MatchString(suffix) } return false }
func getSuffixMatches(req *http.Request, pattern *regexp.Regexp) bool { if httputil.IsGet(req) { suffix := httputil.PathSuffix(req) return pattern.MatchString(suffix) } return false }
func wantsPermanode(req *http.Request) bool { if httputil.IsGet(req) && blob.ValidRefString(req.FormValue("p")) { // The new UI is handled by index.html. if req.FormValue("newui") != "1" { return true } } return false }
// fromHTTP panics with an httputil value on failure func (r *DescribeRequest) fromHTTP(req *http.Request) { switch { case httputil.IsGet(req): r.fromHTTPGet(req) case req.Method == "POST": r.fromHTTPPost(req) default: panic("Unsupported method") } }
func (o *ownerAuth) SendUnauthorized(rw http.ResponseWriter, req *http.Request) bool { if !httputil.IsGet(req) { return false } c := appengine.NewContext(req) loginURL, err := user.LoginURL(c, req.URL.String()) if err != nil { c.Errorf("Fetching LoginURL: %v", err) return false } http.Redirect(rw, req, loginURL, http.StatusFound) return true }
func (hh *HelpHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { suffix := httputil.PathSuffix(req) if !httputil.IsGet(req) { http.Error(rw, "Illegal help method.", http.StatusMethodNotAllowed) return } switch suffix { case "": hh.serveHelpHTML(rw, req) default: http.Error(rw, "Illegal help path.", http.StatusNotFound) } }
func (sh *StatusHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { suffix := httputil.PathSuffix(req) if !httputil.IsGet(req) { http.Error(rw, "Illegal status method.", http.StatusMethodNotAllowed) return } switch suffix { case "status.json": sh.serveStatusJSON(rw, req) case "": sh.serveStatusHTML(rw, req) default: http.Error(rw, "Illegal status path.", 404) } }
func (hh *HelpHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { suffix := httputil.PathSuffix(req) if !httputil.IsGet(req) { http.Error(rw, "Illegal help method.", http.StatusMethodNotAllowed) return } switch suffix { case "": if clientConfig := req.FormValue("clientConfig"); clientConfig != "" { if clientConfigOnly, err := strconv.ParseBool(clientConfig); err == nil && clientConfigOnly { httputil.ReturnJSON(rw, hh.clientConfig) return } } hh.serveHelpHTML(rw, req) default: http.Error(rw, "Illegal help path.", http.StatusNotFound) } }
// serveRef gets the file at ref from fetcher and serves its contents. // It is used by Service as a one time handler to serve to the thumbnail child process on localhost. func serveRef(rw http.ResponseWriter, req *http.Request, ref blob.Ref, fetcher blob.Fetcher) { if !httputil.IsGet(req) { http.Error(rw, "Invalid download method.", 400) return } if !httputil.IsLocalhost(req) { http.Error(rw, "Forbidden.", 403) return } parts := strings.Split(req.URL.Path, "/") if len(parts) < 2 { http.Error(rw, "Malformed GET URL.", 400) return } blobRef, ok := blob.Parse(parts[1]) if !ok { http.Error(rw, "Malformed GET URL.", 400) return } // only serves its ref if blobRef != ref { log.Printf("videothumbnail: access to %v forbidden; wrong blobref for handler", blobRef) http.Error(rw, "Forbidden.", 403) return } rw.Header().Set("Content-Type", "application/octet-stream") fr, err := schema.NewFileReader(fetcher, ref) if err != nil { httputil.ServeError(rw, req, err) return } defer fr.Close() http.ServeContent(rw, req, "", time.Now(), fr) }
func (sh *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { ret := jsonMap() suffix := httputil.PathSuffix(req) if httputil.IsGet(req) { switch suffix { case "camli/search/recent": sh.serveRecentPermanodes(rw, req) return case "camli/search/permanodeattr": sh.servePermanodesWithAttr(rw, req) return case "camli/search/describe": sh.serveDescribe(rw, req) return case "camli/search/claims": sh.serveClaims(rw, req) return case "camli/search/files": sh.serveFiles(rw, req) return case "camli/search/signerattrvalue": sh.serveSignerAttrValue(rw, req) return case "camli/search/signerpaths": sh.serveSignerPaths(rw, req) return case "camli/search/edgesto": sh.serveEdgesTo(rw, req) return } } // TODO: discovery for the endpoints & better error message with link to discovery info ret["error"] = "Unsupported search path or method" ret["errorType"] = "input" httputil.ReturnJSON(rw, ret) }
func (sh *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { ret := jsonMap() suffix := httputil.PathSuffix(req) if httputil.IsGet(req) { fn := getHandler[strings.TrimPrefix(suffix, "camli/search/")] if fn != nil { fn(sh, rw, req) return } } if req.Method == "POST" { switch suffix { case "camli/search/query": sh.serveQuery(rw, req) return } } // TODO: discovery for the endpoints & better error message with link to discovery info ret["error"] = "Unsupported search path or method" ret["errorType"] = "input" httputil.ReturnJSON(rw, ret) }
func (ih *ImageHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request, file blob.Ref) { if !httputil.IsGet(req) { http.Error(rw, "Invalid method", 400) return } mw, mh := ih.MaxWidth, ih.MaxHeight if mw == 0 || mh == 0 || mw > search.MaxImageSize || mh > search.MaxImageSize { http.Error(rw, "bogus dimensions", 400) return } key := cacheKey(file.String(), mw, mh) etag := blob.SHA1FromString(key).String()[5:] inm := req.Header.Get("If-None-Match") if inm != "" { if strings.Trim(inm, `"`) == etag { rw.WriteHeader(http.StatusNotModified) return } } else { if !disableThumbCache && req.Header.Get("If-Modified-Since") != "" { rw.WriteHeader(http.StatusNotModified) return } } var imageData []byte format := "" cacheHit := false if ih.thumbMeta != nil && !disableThumbCache { var buf bytes.Buffer format = ih.scaledCached(&buf, file) if format != "" { cacheHit = true imageData = buf.Bytes() } } if !cacheHit { imi, err := singleResize.Do(key, func() (interface{}, error) { return ih.scaleImage(file) }) if err != nil { http.Error(rw, err.Error(), 500) return } im := imi.(*formatAndImage) imageData = im.image if ih.thumbMeta != nil { err := ih.cacheScaled(bytes.NewReader(imageData), key) if err != nil { log.Printf("image resize: %v", err) } } } h := rw.Header() if !disableThumbCache { h.Set("Expires", time.Now().Add(oneYear).Format(http.TimeFormat)) h.Set("Last-Modified", time.Now().Format(http.TimeFormat)) h.Set("Etag", strconv.Quote(etag)) } h.Set("Content-Type", imageContentTypeOfFormat(format)) size := len(imageData) h.Set("Content-Length", fmt.Sprint(size)) imageBytesServedVar.Add(int64(size)) if req.Method == "GET" { n, err := rw.Write(imageData) if err != nil { if strings.Contains(err.Error(), "broken pipe") { // boring. return } // TODO: vlog this: log.Printf("error serving thumbnail of file schema %s: %v", file, err) return } if n != size { log.Printf("error serving thumbnail of file schema %s: sent %d, expected size of %d", file, n, size) return } } }
// Unauthenticated user. Be paranoid. func handleGetViaSharing(conn http.ResponseWriter, req *http.Request, blobRef blob.Ref, fetcher blob.StreamingFetcher) error { if !httputil.IsGet(req) { return &shareError{code: invalidMethod, response: badRequest, message: "Invalid method"} } viaPathOkay := false startTime := time.Now() defer func() { if !viaPathOkay { // Insert a delay, to hide timing attacks probing // for the existence of blobs. sleep := fetchFailureDelay - (time.Now().Sub(startTime)) time.Sleep(sleep) } }() viaBlobs := make([]blob.Ref, 0) if via := req.FormValue("via"); via != "" { for _, vs := range strings.Split(via, ",") { if br, ok := blob.Parse(vs); ok { viaBlobs = append(viaBlobs, br) } else { return &shareError{code: invalidVia, response: badRequest, message: "Malformed blobref in via param"} } } } fetchChain := make([]blob.Ref, 0) fetchChain = append(fetchChain, viaBlobs...) fetchChain = append(fetchChain, blobRef) isTransitive := false for i, br := range fetchChain { switch i { case 0: file, size, err := fetcher.FetchStreaming(br) if err != nil { return unauthorized(shareFetchFailed, "Fetch chain 0 of %s failed: %v", br, err) } defer file.Close() if size > schema.MaxSchemaBlobSize { return unauthorized(shareBlobTooLarge, "Fetch chain 0 of %s too large", br) } blob, err := schema.BlobFromReader(br, file) if err != nil { return unauthorized(shareReadFailed, "Can't create a blob from %v: %v", br, err) } share, ok := blob.AsShare() if !ok { return unauthorized(shareBlobInvalid, "Fetch chain 0 of %s wasn't a valid Share", br) } if share.IsExpired() { return unauthorized(shareExpired, "Share is expired") } if len(fetchChain) > 1 && fetchChain[1].String() != share.Target().String() { return unauthorized(shareTargetInvalid, "Fetch chain 0->1 (%s -> %q) unauthorized, expected hop to %q", br, fetchChain[1], share.Target()) } isTransitive = share.IsTransitive() if len(fetchChain) > 2 && !isTransitive { return unauthorized(shareNotTransitive, "Share is not transitive") } case len(fetchChain) - 1: // Last one is fine (as long as its path up to here has been proven, and it's // not the first thing in the chain) continue default: file, _, err := fetcher.FetchStreaming(br) if err != nil { return unauthorized(viaChainFetchFailed, "Fetch chain %d of %s failed: %v", i, br, err) } defer file.Close() lr := io.LimitReader(file, schema.MaxSchemaBlobSize) slurpBytes, err := ioutil.ReadAll(lr) if err != nil { return unauthorized(viaChainReadFailed, "Fetch chain %d of %s failed in slurp: %v", i, br, err) } saught := fetchChain[i+1].String() if bytes.Index(slurpBytes, []byte(saught)) == -1 { return unauthorized(viaChainInvalidLink, "Fetch chain %d of %s failed; no reference to %s", i, br, saught) } } } if assemble, _ := strconv.ParseBool(req.FormValue("assemble")); assemble { if !isTransitive { return unauthorized(assembleNonTransitive, "Cannot assemble non-transitive share") } dh := &DownloadHandler{ Fetcher: fetcher, // TODO(aa): It would be nice to specify a local cache here, as the UI handler does. } dh.ServeHTTP(conn, req, blobRef) } else { gethandler.ServeBlobRef(conn, req, blobRef, fetcher) } viaPathOkay = true return nil }
// ServeHTTP streams a zip archive of all the files "under" // zh.root. That is, all the files pointed by file permanodes, // which are directly members of zh.root or recursively down // directory permanodes and permanodes members. // To build the fullpath of a file in a collection, it uses // the collection title if present, its blobRef otherwise, as // a directory name. func (zh *zipHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { // TODO: use http.ServeContent, so Range requests work and downloads can be resumed. // Will require calculating the zip length once first (ideally as cheaply as possible, // with dummy counting writer and dummy all-zero-byte-files of a fixed size), // and then making a dummy ReadSeeker for ServeContent that can seek to the end, // and then seek back to the beginning, but then seeks forward make it remember // to skip that many bytes from the archive/zip writer when answering Reads. if !httputil.IsGet(req) { http.Error(rw, "Invalid method", http.StatusMethodNotAllowed) return } bf, err := zh.blobList("/", zh.root) if err != nil { log.Printf("Could not serve zip for %v: %v", zh.root, err) http.Error(rw, "Server error", http.StatusInternalServerError) return } blobFiles := renameDuplicates(bf) // TODO(mpl): streaming directly won't work on appengine if the size goes // over 32 MB. Deal with that. h := rw.Header() h.Set("Content-Type", "application/zip") filename := zh.filename if filename == "" { filename = "download.zip" } h.Set("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename": filename})) zw := zip.NewWriter(rw) etag := sha1.New() for _, file := range blobFiles { etag.Write([]byte(file.blobRef.String())) } h.Set("Etag", fmt.Sprintf(`"%x"`, etag.Sum(nil))) for _, file := range blobFiles { fr, err := schema.NewFileReader(zh.storageSeekFetcher(), file.blobRef) if err != nil { log.Printf("Can not add %v in zip, not a file: %v", file.blobRef, err) http.Error(rw, "Server error", http.StatusInternalServerError) return } f, err := zw.CreateHeader( &zip.FileHeader{ Name: file.path, Method: zip.Store, }) if err != nil { log.Printf("Could not create %q in zip: %v", file.path, err) http.Error(rw, "Server error", http.StatusInternalServerError) return } _, err = io.Copy(f, fr) fr.Close() if err != nil { log.Printf("Could not zip %q: %v", file.path, err) return } } err = zw.Close() if err != nil { log.Printf("Could not close zipwriter: %v", err) return } }
func wantsFileTreePage(req *http.Request) bool { return httputil.IsGet(req) && blob.ValidRefString(req.FormValue("d")) }
func wantsBlobInfo(req *http.Request) bool { return httputil.IsGet(req) && blob.ValidRefString(req.FormValue("b")) }
func wantsPermanode(req *http.Request) bool { return httputil.IsGet(req) && blob.ValidRefString(req.FormValue("p")) }
func wantsDiscovery(req *http.Request) bool { return httputil.IsGet(req) && (req.Header.Get("Accept") == "text/x-camli-configuration" || camliMode(req) == "config") }
func (ih *ImageHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request, file blob.Ref) { if !httputil.IsGet(req) { http.Error(rw, "Invalid method", 400) return } mw, mh := ih.MaxWidth, ih.MaxHeight if mw == 0 || mh == 0 || mw > search.MaxImageSize || mh > search.MaxImageSize { http.Error(rw, "bogus dimensions", 400) return } if req.Header.Get("If-Modified-Since") != "" { // Immutable, so any copy's a good copy. rw.WriteHeader(http.StatusNotModified) return } var buf bytes.Buffer var err error format := "" cacheHit := false if ih.sc != nil { format, err = ih.scaledCached(&buf, file) if err != nil { log.Printf("image resize: %v", err) } else { cacheHit = true } } if !cacheHit { format, err = ih.scaleImage(&buf, file) if err != nil { http.Error(rw, err.Error(), 500) return } if ih.sc != nil { name := cacheKey(file.String(), mw, mh) bufcopy := buf.Bytes() err = ih.cacheScaled(bytes.NewBuffer(bufcopy), name) if err != nil { log.Printf("image resize: %v", err) } } } h := rw.Header() h.Set("Expires", time.Now().Add(oneYear).Format(http.TimeFormat)) h.Set("Last-Modified", time.Now().Format(http.TimeFormat)) h.Set("Content-Type", imageContentTypeOfFormat(format)) size := buf.Len() h.Set("Content-Length", fmt.Sprintf("%d", size)) if req.Method == "GET" { n, err := io.Copy(rw, &buf) if err != nil { log.Printf("error serving thumbnail of file schema %s: %v", file, err) return } if n != int64(size) { log.Printf("error serving thumbnail of file schema %s: sent %d, expected size of %d", file, n, size) return } } }