func handleVerify(conn http.ResponseWriter, req *http.Request) { if !(req.Method == "POST" && req.URL.Path == "/camli/sig/verify") { httputil.BadRequestError(conn, "Inconfigured handler.") return } req.ParseForm() sjson := req.FormValue("sjson") if sjson == "" { httputil.BadRequestError(conn, "Missing sjson parameter.") return } m := make(map[string]interface{}) vreq := jsonsign.NewVerificationRequest(sjson, pubKeyFetcher) if vreq.Verify() { m["signatureValid"] = 1 m["verifiedData"] = vreq.PayloadMap } else { m["signatureValid"] = 0 m["errorMessage"] = vreq.Err.Error() } conn.WriteHeader(http.StatusOK) // no HTTP response code fun, error info in JSON httputil.ReturnJSON(conn, m) }
func (im *imp) ServeCallback(w http.ResponseWriter, r *http.Request, ctx *importer.SetupContext) { tempToken := ctx.AccountNode.Attr(importer.AcctAttrTempToken) tempSecret := ctx.AccountNode.Attr(importer.AcctAttrTempSecret) if tempToken == "" || tempSecret == "" { log.Printf("twitter: no temp creds in callback") httputil.BadRequestError(w, "no temp creds in callback") return } if tempToken != r.FormValue("oauth_token") { log.Printf("unexpected oauth_token: got %v, want %v", r.FormValue("oauth_token"), tempToken) httputil.BadRequestError(w, "unexpected oauth_token") return } oauthClient, err := ctx.NewOAuthClient(oAuthURIs) if err != nil { err = fmt.Errorf("error getting OAuth client: %v", err) httputil.ServeError(w, r, err) return } tokenCred, vals, err := oauthClient.RequestToken( ctxutil.Client(ctx), &oauth.Credentials{ Token: tempToken, Secret: tempSecret, }, r.FormValue("oauth_verifier"), ) if err != nil { httputil.ServeError(w, r, fmt.Errorf("Error getting request token: %v ", err)) return } userid := vals.Get("user_id") if userid == "" { httputil.ServeError(w, r, fmt.Errorf("Couldn't get user id: %v", err)) return } if err := ctx.AccountNode.SetAttrs( importer.AcctAttrAccessToken, tokenCred.Token, importer.AcctAttrAccessTokenSecret, tokenCred.Secret, ); err != nil { httputil.ServeError(w, r, fmt.Errorf("Error setting token attributes: %v", err)) return } u, err := getUserInfo(importer.OAuthContext{ctx.Context, oauthClient, tokenCred}) if err != nil { httputil.ServeError(w, r, fmt.Errorf("Couldn't get user info: %v", err)) return } if err := ctx.AccountNode.SetAttrs( importer.AcctAttrUserID, u.ID, importer.AcctAttrName, u.Name, importer.AcctAttrUserName, u.ScreenName, nodeattr.Title, fmt.Sprintf("%s's Twitter Account", u.ScreenName), ); err != nil { httputil.ServeError(w, r, fmt.Errorf("Error setting attribute: %v", err)) return } http.Redirect(w, r, ctx.AccountURL(), http.StatusFound) }
func handleSign(conn http.ResponseWriter, req *http.Request) { if !(req.Method == "POST" && req.URL.Path == "/camli/sig/sign") { httputil.BadRequestError(conn, "Inconfigured handler.") return } req.ParseForm() jsonStr := req.FormValue("json") if jsonStr == "" { httputil.BadRequestError(conn, "Missing json parameter") return } if len(jsonStr) > kMaxJsonLength { httputil.BadRequestError(conn, "json parameter too large") return } sreq := &jsonsign.SignRequest{UnsignedJSON: jsonStr, Fetcher: pubKeyFetcher} signedJson, err := sreq.Sign() if err != nil { // TODO: some aren't really a "bad request" httputil.BadRequestError(conn, fmt.Sprintf("%v", err)) return } conn.Write([]byte(signedJson)) }
func handleRemove(conn http.ResponseWriter, req *http.Request, storage blobserver.Storage) { if w, ok := storage.(blobserver.ContextWrapper); ok { storage = w.WrapContext(req) } if req.Method != "POST" { log.Fatalf("Invalid method; handlers misconfigured") } configer, ok := storage.(blobserver.Configer) if !ok { conn.WriteHeader(http.StatusForbidden) fmt.Fprintf(conn, "Remove handler's blobserver.Storage isn't a blobserver.Configer; can't remove") return } if !configer.Config().IsQueue { conn.WriteHeader(http.StatusForbidden) fmt.Fprintf(conn, "Can only remove blobs from a queue.\n") return } n := 0 toRemove := make([]*blobref.BlobRef, 0) toRemoveStr := make([]string, 0) for { n++ if n > maxRemovesPerRequest { httputil.BadRequestError(conn, fmt.Sprintf("Too many removes in this request; max is %d", maxRemovesPerRequest)) return } key := fmt.Sprintf("blob%v", n) value := req.FormValue(key) if value == "" { break } ref := blobref.Parse(value) if ref == nil { httputil.BadRequestError(conn, "Bogus blobref for key "+key) return } toRemove = append(toRemove, ref) toRemoveStr = append(toRemoveStr, ref.String()) } err := storage.RemoveBlobs(toRemove) if err != nil { conn.WriteHeader(http.StatusInternalServerError) log.Printf("Server error during remove: %v", err) fmt.Fprintf(conn, "Server error") return } reply := make(map[string]interface{}, 0) reply["removed"] = toRemoveStr httputil.ReturnJSON(conn, reply) }
func (imp) ServeCallback(w http.ResponseWriter, r *http.Request, ctx *importer.SetupContext) { tempToken := ctx.AccountNode.Attr(importer.AcctAttrTempToken) tempSecret := ctx.AccountNode.Attr(importer.AcctAttrTempSecret) if tempToken == "" || tempSecret == "" { log.Printf("flicker: no temp creds in callback") httputil.BadRequestError(w, "no temp creds in callback") return } if tempToken != r.FormValue("oauth_token") { log.Printf("unexpected oauth_token: got %v, want %v", r.FormValue("oauth_token"), tempToken) httputil.BadRequestError(w, "unexpected oauth_token") return } oauthClient, err := ctx.NewOAuthClient(oAuthURIs) if err != nil { err = fmt.Errorf("error getting OAuth client: %v", err) httputil.ServeError(w, r, err) return } tokenCred, vals, err := oauthClient.RequestToken( ctx.Context.HTTPClient(), &oauth.Credentials{ Token: tempToken, Secret: tempSecret, }, r.FormValue("oauth_verifier"), ) if err != nil { httputil.ServeError(w, r, fmt.Errorf("Error getting request token: %v ", err)) return } userID := vals.Get("user_nsid") if userID == "" { httputil.ServeError(w, r, fmt.Errorf("Couldn't get user id: %v", err)) return } username := vals.Get("username") if username == "" { httputil.ServeError(w, r, fmt.Errorf("Couldn't get user name: %v", err)) return } // TODO(mpl): get a few more bits of info (first name, last name etc) like I did for twitter, if possible. if err := ctx.AccountNode.SetAttrs( importer.AcctAttrAccessToken, tokenCred.Token, importer.AcctAttrAccessTokenSecret, tokenCred.Secret, importer.AcctAttrUserID, userID, importer.AcctAttrUserName, username, ); err != nil { httputil.ServeError(w, r, fmt.Errorf("Error setting basic account attributes: %v", err)) return } http.Redirect(w, r, ctx.AccountURL(), http.StatusFound) }
func (im *imp) ServeCallback(w http.ResponseWriter, r *http.Request, ctx *importer.SetupContext) { creds := im.creds() if creds == nil { log.Printf("twitter: nil creds in callback") httputil.BadRequestError(w, "nil creds in callback") return } if creds.Token != r.FormValue("oauth_token") { log.Printf("unexpected oauth_token: got %v, want %v", r.FormValue("oauth_token"), creds.Token) httputil.BadRequestError(w, "unexpected oauth_token") return } tokenCred, vals, err := oauthClient.RequestToken(ctx.Context.HTTPClient(), creds, r.FormValue("oauth_verifier")) if err != nil { httputil.ServeError(w, r, fmt.Errorf("Error getting request token: %v ", err)) return } userid := vals.Get("user_id") if userid == "" { httputil.ServeError(w, r, fmt.Errorf("Couldn't get user id: %v", err)) return } im.setCreds(tokenCred) u, err := im.getUserInfo(ctx.Context) if err != nil { httputil.ServeError(w, r, fmt.Errorf("Couldn't get user info: %v", err)) return } firstName, lastName := "", "" if u.Name != "" { if pieces := strings.Fields(u.Name); len(pieces) == 2 { firstName = pieces[0] lastName = pieces[1] } } if err := ctx.AccountNode.SetAttrs( acctAttrUserID, u.ID, acctAttrUserFirst, firstName, acctAttrUserLast, lastName, acctAttrScreenName, u.ScreenName, acctAttrAccessToken, tokenCred.Token, ); err != nil { httputil.ServeError(w, r, fmt.Errorf("Error setting attribute: %v", err)) return } http.Redirect(w, r, ctx.AccountURL(), http.StatusFound) }
func handleRemove(rw http.ResponseWriter, req *http.Request, storage blobserver.Storage) { if req.Method != "POST" { log.Fatalf("Invalid method; handlers misconfigured") } configer, ok := storage.(blobserver.Configer) if !ok { rw.WriteHeader(http.StatusForbidden) fmt.Fprintf(rw, "Remove handler's blobserver.Storage isn't a blobserver.Configer; can't remove") return } if !configer.Config().Deletable { rw.WriteHeader(http.StatusForbidden) fmt.Fprintf(rw, "storage does not permit deletes.\n") return } n := 0 toRemove := make([]blob.Ref, 0) for { n++ if n > maxRemovesPerRequest { httputil.BadRequestError(rw, fmt.Sprintf("Too many removes in this request; max is %d", maxRemovesPerRequest)) return } key := fmt.Sprintf("blob%v", n) value := req.FormValue(key) if value == "" { break } ref, ok := blob.Parse(value) if !ok { httputil.BadRequestError(rw, "Bogus blobref for key "+key) return } toRemove = append(toRemove, ref) } err := storage.RemoveBlobs(toRemove) if err != nil { rw.WriteHeader(http.StatusInternalServerError) log.Printf("Server error during remove: %v", err) fmt.Fprintf(rw, "Server error") return } httputil.ReturnJSON(rw, &RemoveResponse{Removed: toRemove}) }
func (h *shareHandler) serveHTTP(rw http.ResponseWriter, req *http.Request) error { var err error pathSuffix := httputil.PathSuffix(req) if len(pathSuffix) == 0 { // This happens during testing because we don't go through PrefixHandler pathSuffix = strings.TrimLeft(req.URL.Path, "/") } pathParts := strings.SplitN(pathSuffix, "/", 2) blobRef, ok := blob.Parse(pathParts[0]) if !ok { err = &shareError{code: invalidURL, response: badRequest, message: fmt.Sprintf("Malformed share pathSuffix: %s", pathSuffix)} } else { err = handleGetViaSharing(rw, req, blobRef, h.fetcher) } if se, ok := err.(*shareError); ok { switch se.response { case badRequest: httputil.BadRequestError(rw, err.Error()) case unauthorizedRequest: log.Print(err) auth.SendUnauthorized(rw, req) } } return err }
func (im *imp) ServeHTTP(w http.ResponseWriter, r *http.Request) { if strings.HasSuffix(r.URL.Path, "/login") { im.serveLogin(w, r) } else if strings.HasSuffix(r.URL.Path, "/callback") { im.serveCallback(w, r) } else { httputil.BadRequestError(w, "Unknown path: %s", r.URL.Path) } }
func (im *imp) serveCallback(w http.ResponseWriter, r *http.Request) { if im.user == nil { httputil.BadRequestError(w, "Flickr importer: unexpected state: expected temporary oauth session") return } if im.user.Cred.Token != r.FormValue("oauth_token") { httputil.BadRequestError(w, "Flickr importer: unexpected oauth_token") return } tokenCred, form, err := oauthClient.RequestToken(im.host.HTTPClient(), im.user.Cred, r.FormValue("oauth_verifier")) if err != nil { httputil.ServeError(w, r, fmt.Errorf("Flickr importer: error getting request token: %s ", err)) return } im.user = &userInfo{ Id: form.Get("user_nsid"), Username: form.Get("username"), Cred: tokenCred, } im.writeCredentials() http.Redirect(w, r, im.host.BaseURL+"?mode=start", 302) }
func handlePut(conn http.ResponseWriter, req *http.Request, blobReceiver blobserver.BlobReceiver) { if w, ok := blobReceiver.(blobserver.ContextWrapper); ok { blobReceiver = w.WrapContext(req) } blobRef := blobref.FromPattern(kPutPattern, req.URL.Path) if blobRef == nil { httputil.BadRequestError(conn, "Malformed PUT URL.") return } if !blobRef.IsSupported() { httputil.BadRequestError(conn, "unsupported object hash function") return } _, err := blobReceiver.ReceiveBlob(blobRef, req.Body) if err != nil { httputil.ServerError(conn, err) return } fmt.Fprint(conn, "OK") }
// CreatePutUploadHandler returns the handler that receives a single // blob at the blob's final URL, via the PUT method. See // doc/protocol/blob-upload-protocol.txt. func CreatePutUploadHandler(storage blobserver.BlobReceiver) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { if req.Method != "PUT" { log.Printf("Inconfigured upload handler.") httputil.BadRequestError(rw, "Inconfigured handler.") return } // For non-chunked uploads, we catch it here. For chunked uploads, it's caught // by blobserver.Receive's LimitReader. if req.ContentLength > blobserver.MaxBlobSize { httputil.BadRequestError(rw, "blob too big") return } blobrefStr := path.Base(req.URL.Path) br, ok := blob.Parse(blobrefStr) if !ok { log.Printf("Invalid PUT request to %q", req.URL.Path) httputil.BadRequestError(rw, "Bad path") return } if !br.IsSupported() { httputil.BadRequestError(rw, "unsupported object hash function") return } _, err := blobserver.Receive(storage, br, req.Body) if err == blobserver.ErrCorruptBlob { httputil.BadRequestError(rw, "data doesn't match declared digest") return } if err != nil { httputil.ServeError(rw, req, err) return } rw.WriteHeader(http.StatusNoContent) }) }
func handleCamliSig(conn http.ResponseWriter, req *http.Request) { handler := func(conn http.ResponseWriter, req *http.Request) { httputil.BadRequestError(conn, "Unsupported path or method.") } switch req.Method { case "POST": switch req.URL.Path { case "/camli/sig/sign": handler = auth.RequireAuth(handleSign) case "/camli/sig/verify": handler = handleVerify } } handler(conn, req) }
// handleSearch runs the requested search query against the search handler, and // if the results are within the domain allowed by the master query, forwards them // back to the client. func (a *Handler) handleSearch(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { camhttputil.BadRequestError(w, camhttputil.InvalidMethodError{}.Error()) return } if a.sh == nil { http.Error(w, "app proxy has no search handler", 500) return } a.masterQueryMu.RLock() if a.masterQuery == nil { http.Error(w, "search is not allowed", http.StatusForbidden) a.masterQueryMu.RUnlock() return } a.masterQueryMu.RUnlock() var sq search.SearchQuery if err := sq.FromHTTP(r); err != nil { camhttputil.ServeJSONError(w, err) return } sr, err := a.sh.Query(&sq) if err != nil { camhttputil.ServeJSONError(w, err) return } // check this search is in the allowed domain if !a.allowProxySearchResponse(sr) { // there's a chance our domainBlobs cache is expired so let's // refresh it and retry, but no more than once per minute. if err := a.refreshDomainBlobs(); err != nil { http.Error(w, "search scope is forbidden", http.StatusForbidden) return } if !a.allowProxySearchResponse(sr) { http.Error(w, "search scope is forbidden", http.StatusForbidden) return } } camhttputil.ReturnJSON(w, sr) }
func (im *imp) serveCallback(w http.ResponseWriter, r *http.Request) { if im.cred.Token != r.FormValue("oauth_token") { httputil.BadRequestError(w, "Twitter importer: unexpected oauth_token") return } tokenCred, vals, err := oauthClient.RequestToken(im.host.HTTPClient(), im.cred, r.FormValue("oauth_verifier")) if err != nil { httputil.ServeError(w, r, fmt.Errorf("Twitter importer: error getting request token: %s ", err)) return } im.cred = tokenCred userid := vals.Get("user_id") if userid == "" { log.Printf("Couldn't get user id: %v", err) http.Error(w, "can't get user id", 500) return } im.userid = userid http.Redirect(w, r, im.host.BaseURL+"?mode=start", 302) }
func handleStat(conn http.ResponseWriter, req *http.Request, storage blobserver.BlobStatter) { if w, ok := storage.(blobserver.ContextWrapper); ok { storage = w.WrapContext(req) } toStat := make([]*blobref.BlobRef, 0) switch req.Method { case "POST": fallthrough case "GET": camliVersion := req.FormValue("camliversion") if camliVersion == "" { httputil.BadRequestError(conn, "No camliversion") return } n := 0 for { n++ key := fmt.Sprintf("blob%v", n) value := req.FormValue(key) if value == "" { n-- break } if n > maxStatBlobs { httputil.BadRequestError(conn, "Too many stat blob checks") return } ref := blobref.Parse(value) if ref == nil { httputil.BadRequestError(conn, "Bogus blobref for key "+key) return } toStat = append(toStat, ref) } default: httputil.BadRequestError(conn, "Invalid method.") return } waitSeconds := 0 if waitStr := req.FormValue("maxwaitsec"); waitStr != "" { waitSeconds, _ = strconv.Atoi(waitStr) switch { case waitSeconds < 0: waitSeconds = 0 case waitSeconds > 30: // TODO: don't hard-code 30. push this up into a blobserver interface // for getting the configuration of the server (ultimately a flag in // in the binary) waitSeconds = 30 } } statRes := make([]map[string]interface{}, 0) if len(toStat) > 0 { blobch := make(chan blobref.SizedBlobRef) resultch := make(chan error, 1) go func() { err := storage.StatBlobs(blobch, toStat, time.Duration(waitSeconds)*time.Second) close(blobch) resultch <- err }() for sb := range blobch { ah := make(map[string]interface{}) ah["blobRef"] = sb.BlobRef.String() ah["size"] = sb.Size statRes = append(statRes, ah) } err := <-resultch if err != nil { log.Printf("Stat error: %v", err) conn.WriteHeader(http.StatusInternalServerError) return } } configer, _ := storage.(blobserver.Configer) ret, err := commonUploadResponse(configer, req) if err != nil { httputil.ServeError(conn, req, err) } ret["canLongPoll"] = false if configer != nil { if conf := configer.Config(); conf != nil { ret["canLongPoll"] = conf.CanLongPoll } } ret["stat"] = statRes httputil.ReturnJSON(conn, ret) }
func handleMultiPartUpload(conn http.ResponseWriter, req *http.Request, blobReceiver blobserver.BlobReceiveConfiger) { if !(req.Method == "POST" && strings.Contains(req.URL.Path, "/camli/upload")) { log.Printf("Inconfigured handler upload handler") httputil.BadRequestError(conn, "Inconfigured handler.") return } receivedBlobs := make([]blob.SizedRef, 0, 10) multipart, err := req.MultipartReader() if multipart == nil { httputil.BadRequestError(conn, fmt.Sprintf( "Expected multipart/form-data POST request; %v", err)) return } var errText string addError := func(s string) { log.Printf("Client error: %s", s) if errText == "" { errText = s return } errText = errText + "\n" + s } for { mimePart, err := multipart.NextPart() if err == io.EOF { break } if err != nil { addError(fmt.Sprintf("Error reading multipart section: %v", err)) break } contentDisposition, params, err := mime.ParseMediaType(mimePart.Header.Get("Content-Disposition")) if err != nil { addError("invalid Content-Disposition") break } if contentDisposition != "form-data" { addError(fmt.Sprintf("Expected Content-Disposition of \"form-data\"; got %q", contentDisposition)) break } formName := params["name"] ref, ok := blob.Parse(formName) if !ok { addError(fmt.Sprintf("Ignoring form key %q", formName)) continue } if oldAppEngineHappySpec { _, hasContentType := mimePart.Header["Content-Type"] if !hasContentType { addError(fmt.Sprintf("Expected Content-Type header for blobref %s; see spec", ref)) continue } _, hasFileName := params["filename"] if !hasFileName { addError(fmt.Sprintf("Expected 'filename' Content-Disposition parameter for blobref %s; see spec", ref)) continue } } var tooBig int64 = blobserver.MaxBlobSize + 1 var readBytes int64 blobGot, err := blobserver.Receive(blobReceiver, ref, &readerutil.CountingReader{ io.LimitReader(mimePart, tooBig), &readBytes, }) if readBytes == tooBig { err = fmt.Errorf("blob over the limit of %d bytes", blobserver.MaxBlobSize) } if err != nil { addError(fmt.Sprintf("Error receiving blob %v: %v\n", ref, err)) break } log.Printf("Received blob %v\n", blobGot) receivedBlobs = append(receivedBlobs, blobGot) } ret, err := commonUploadResponse(blobReceiver, req) if err != nil { httputil.ServeError(conn, req, err) } received := make([]map[string]interface{}, 0) for _, got := range receivedBlobs { blob := make(map[string]interface{}) blob["blobRef"] = got.Ref.String() blob["size"] = got.Size received = append(received, blob) } ret["received"] = received if req.Header.Get("X-Camlistore-Vivify") == "1" { for _, got := range receivedBlobs { err := vivify(blobReceiver, got) if err != nil { addError(fmt.Sprintf("Error vivifying blob %v: %v\n", got.Ref.String(), err)) } else { conn.Header().Add("X-Camlistore-Vivified", got.Ref.String()) } } } if errText != "" { ret["errorText"] = errText } httputil.ReturnJSON(conn, ret) }
// Unauthenticated user. Be paranoid. func handleGetViaSharing(conn http.ResponseWriter, req *http.Request, blobRef blob.Ref, fetcher blob.StreamingFetcher) { if req.Method != "GET" && req.Method != "HEAD" { httputil.BadRequestError(conn, "Invalid method") return } 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 { httputil.BadRequestError(conn, "Malformed blobref in via param") return } } } fetchChain := make([]blob.Ref, 0) fetchChain = append(fetchChain, viaBlobs...) fetchChain = append(fetchChain, blobRef) for i, br := range fetchChain { switch i { case 0: file, size, err := fetcher.FetchStreaming(br) if err != nil { log.Printf("Fetch chain 0 of %s failed: %v", br.String(), err) auth.SendUnauthorized(conn, req) return } defer file.Close() if size > schema.MaxSchemaBlobSize { log.Printf("Fetch chain 0 of %s too large", br.String()) auth.SendUnauthorized(conn, req) return } blob, err := schema.BlobFromReader(br, file) if err != nil { log.Printf("Can't create a blob from %v: %v", br.String(), err) auth.SendUnauthorized(conn, req) return } share, ok := blob.AsShare() if !ok { log.Printf("Fetch chain 0 of %s wasn't a valid Share", br.String()) auth.SendUnauthorized(conn, req) return } if len(fetchChain) > 1 && fetchChain[1].String() != share.Target().String() { log.Printf("Fetch chain 0->1 (%s -> %q) unauthorized, expected hop to %q", br.String(), fetchChain[1].String(), share.Target().String()) auth.SendUnauthorized(conn, req) return } 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 { log.Printf("Fetch chain %d of %s failed: %v", i, br.String(), err) auth.SendUnauthorized(conn, req) return } defer file.Close() lr := io.LimitReader(file, schema.MaxSchemaBlobSize) slurpBytes, err := ioutil.ReadAll(lr) if err != nil { log.Printf("Fetch chain %d of %s failed in slurp: %v", i, br.String(), err) auth.SendUnauthorized(conn, req) return } saught := fetchChain[i+1].String() if bytes.IndexAny(slurpBytes, saught) == -1 { log.Printf("Fetch chain %d of %s failed; no reference to %s", i, br.String(), saught) auth.SendUnauthorized(conn, req) return } } } viaPathOkay = true gethandler.ServeBlobRef(conn, req, blobRef, fetcher) }
func unsupportedHandler(conn http.ResponseWriter, req *http.Request) { httputil.BadRequestError(conn, "Unsupported camlistore path or method.") }
func (im imp) ServeHTTP(w http.ResponseWriter, r *http.Request) { httputil.BadRequestError(w, "Unexpected path: %s", r.URL.Path) }
func handleMultiPartUpload(conn http.ResponseWriter, req *http.Request, blobReceiver blobserver.BlobReceiveConfiger) { if w, ok := blobReceiver.(blobserver.ContextWrapper); ok { blobReceiver = wrapReceiveConfiger(w, req, blobReceiver) } if !(req.Method == "POST" && strings.Contains(req.URL.Path, "/camli/upload")) { log.Printf("Inconfigured handler upload handler") httputil.BadRequestError(conn, "Inconfigured handler.") return } receivedBlobs := make([]blobref.SizedBlobRef, 0, 10) multipart, err := req.MultipartReader() if multipart == nil { httputil.BadRequestError(conn, fmt.Sprintf( "Expected multipart/form-data POST request; %v", err)) return } var errText string addError := func(s string) { log.Printf("Client error: %s", s) if errText == "" { errText = s return } errText = errText + "\n" + s } for { mimePart, err := multipart.NextPart() if err == io.EOF { break } if err != nil { addError(fmt.Sprintf("Error reading multipart section: %v", err)) break } //POST-r60: //contentDisposition, params, err := mime.ParseMediaType(mimePart.Header.Get("Content-Disposition")) //if err != nil { // addError(err.String()) // break //} // r60: contentDisposition, params, err := mime.ParseMediaType(mimePart.Header.Get("Content-Disposition")) if contentDisposition == "" || err != nil { addError("invalid Content-Disposition") break } if contentDisposition != "form-data" { addError(fmt.Sprintf("Expected Content-Disposition of \"form-data\"; got %q", contentDisposition)) break } formName := params["name"] ref := blobref.Parse(formName) if ref == nil { addError(fmt.Sprintf("Ignoring form key %q", formName)) continue } if oldAppEngineHappySpec { _, hasContentType := mimePart.Header["Content-Type"] if !hasContentType { addError(fmt.Sprintf("Expected Content-Type header for blobref %s; see spec", ref)) continue } _, hasFileName := params["filename"] if !hasFileName { addError(fmt.Sprintf("Expected 'filename' Content-Disposition parameter for blobref %s; see spec", ref)) continue } } blobGot, err := blobReceiver.ReceiveBlob(ref, mimePart) if err != nil { addError(fmt.Sprintf("Error receiving blob %v: %v\n", ref, err)) break } log.Printf("Received blob %v\n", blobGot) receivedBlobs = append(receivedBlobs, blobGot) } ret := commonUploadResponse(blobReceiver, req) received := make([]map[string]interface{}, 0) for _, got := range receivedBlobs { blob := make(map[string]interface{}) blob["blobRef"] = got.BlobRef.String() blob["size"] = got.Size received = append(received, blob) } ret["received"] = received if errText != "" { ret["errorText"] = errText } httputil.ReturnJson(conn, ret) }
// Unauthenticated user. Be paranoid. func handleGetViaSharing(conn http.ResponseWriter, req *http.Request, blobRef *blobref.BlobRef, fetcher blobref.StreamingFetcher) { if w, ok := fetcher.(blobserver.ContextWrapper); ok { fetcher = w.WrapContext(req) } viaPathOkay := false startTime := time.Now() defer func() { if !viaPathOkay { // Insert a delay, to hide timing attacks probing // for the existence of blobs. sleep := fetchFailureDelayNs - (time.Now().Sub(startTime)) if sleep > 0 { time.Sleep(sleep) } } }() viaBlobs := make([]*blobref.BlobRef, 0) if via := req.FormValue("via"); via != "" { for _, vs := range strings.Split(via, ",") { if br := blobref.Parse(vs); br == nil { httputil.BadRequestError(conn, "Malformed blobref in via param") return } else { viaBlobs = append(viaBlobs, br) } } } fetchChain := make([]*blobref.BlobRef, 0) fetchChain = append(fetchChain, viaBlobs...) fetchChain = append(fetchChain, blobRef) for i, br := range fetchChain { switch i { case 0: file, size, err := fetcher.FetchStreaming(br) if err != nil { log.Printf("Fetch chain 0 of %s failed: %v", br.String(), err) auth.SendUnauthorized(conn, req) return } defer file.Close() if size > maxJSONSize { log.Printf("Fetch chain 0 of %s too large", br.String()) auth.SendUnauthorized(conn, req) return } jd := json.NewDecoder(file) m := make(map[string]interface{}) if err := jd.Decode(&m); err != nil { log.Printf("Fetch chain 0 of %s wasn't JSON: %v", br.String(), err) auth.SendUnauthorized(conn, req) return } if m["camliType"].(string) != "share" { log.Printf("Fetch chain 0 of %s wasn't a share", br.String()) auth.SendUnauthorized(conn, req) return } if len(fetchChain) > 1 && fetchChain[1].String() != m["target"].(string) { log.Printf("Fetch chain 0->1 (%s -> %q) unauthorized, expected hop to %q", br.String(), fetchChain[1].String(), m["target"]) auth.SendUnauthorized(conn, req) return } 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 { log.Printf("Fetch chain %d of %s failed: %v", i, br.String(), err) auth.SendUnauthorized(conn, req) return } defer file.Close() lr := io.LimitReader(file, maxJSONSize) slurpBytes, err := ioutil.ReadAll(lr) if err != nil { log.Printf("Fetch chain %d of %s failed in slurp: %v", i, br.String(), err) auth.SendUnauthorized(conn, req) return } saught := fetchChain[i+1].String() if bytes.IndexAny(slurpBytes, saught) == -1 { log.Printf("Fetch chain %d of %s failed; no reference to %s", i, br.String(), saught) auth.SendUnauthorized(conn, req) return } } } viaPathOkay = true serveBlobRef(conn, req, blobRef, fetcher) }
func handleStat(conn http.ResponseWriter, req *http.Request, storage blobserver.BlobStatter) { res := new(protocol.StatResponse) if configer, ok := storage.(blobserver.Configer); ok { if conf := configer.Config(); conf != nil { res.CanLongPoll = conf.CanLongPoll } } needStat := map[blob.Ref]bool{} switch req.Method { case "POST": fallthrough case "GET", "HEAD": camliVersion := req.FormValue("camliversion") if camliVersion == "" { httputil.BadRequestError(conn, "No camliversion") return } n := 0 for { n++ key := fmt.Sprintf("blob%v", n) value := req.FormValue(key) if value == "" { n-- break } if n > maxStatBlobs { httputil.BadRequestError(conn, "Too many stat blob checks") return } ref, ok := blob.Parse(value) if !ok { httputil.BadRequestError(conn, "Bogus blobref for key "+key) return } needStat[ref] = true } default: httputil.BadRequestError(conn, "Invalid method.") return } waitSeconds := 0 if waitStr := req.FormValue("maxwaitsec"); waitStr != "" { waitSeconds, _ = strconv.Atoi(waitStr) switch { case waitSeconds < 0: waitSeconds = 0 case waitSeconds > 30: // TODO: don't hard-code 30. push this up into a blobserver interface // for getting the configuration of the server (ultimately a flag in // in the binary) waitSeconds = 30 } } deadline := time.Now().Add(time.Duration(waitSeconds) * time.Second) toStat := make([]blob.Ref, 0, len(needStat)) buildToStat := func() { toStat = toStat[:0] for br := range needStat { toStat = append(toStat, br) } } for len(needStat) > 0 { buildToStat() blobch := make(chan blob.SizedRef) resultch := make(chan error, 1) go func() { err := storage.StatBlobs(blobch, toStat) close(blobch) resultch <- err }() for sb := range blobch { res.Stat = append(res.Stat, &protocol.RefAndSize{ Ref: sb.Ref, Size: uint32(sb.Size), }) delete(needStat, sb.Ref) } err := <-resultch if err != nil { log.Printf("Stat error: %v", err) conn.WriteHeader(http.StatusInternalServerError) return } if len(needStat) == 0 || waitSeconds == 0 || time.Now().After(deadline) { break } buildToStat() blobserver.WaitForBlob(storage, deadline, toStat) } httputil.ReturnJSON(conn, res) }
func handleMultiPartUpload(rw http.ResponseWriter, req *http.Request, blobReceiver blobserver.BlobReceiveConfiger) { res := new(protocol.UploadResponse) if !(req.Method == "POST" && strings.Contains(req.URL.Path, "/camli/upload")) { log.Printf("Inconfigured handler upload handler") httputil.BadRequestError(rw, "Inconfigured handler.") return } receivedBlobs := make([]blob.SizedRef, 0, 10) multipart, err := req.MultipartReader() if multipart == nil { httputil.BadRequestError(rw, fmt.Sprintf( "Expected multipart/form-data POST request; %v", err)) return } var errBuf bytes.Buffer addError := func(s string) { log.Printf("Client error: %s", s) if errBuf.Len() > 0 { errBuf.WriteByte('\n') } errBuf.WriteString(s) } for { mimePart, err := multipart.NextPart() if err == io.EOF { break } if err != nil { addError(fmt.Sprintf("Error reading multipart section: %v", err)) break } contentDisposition, params, err := mime.ParseMediaType(mimePart.Header.Get("Content-Disposition")) if err != nil { addError("invalid Content-Disposition") break } if contentDisposition != "form-data" { addError(fmt.Sprintf("Expected Content-Disposition of \"form-data\"; got %q", contentDisposition)) break } formName := params["name"] ref, ok := blob.Parse(formName) if !ok { addError(fmt.Sprintf("Ignoring form key %q", formName)) continue } var tooBig int64 = blobserver.MaxBlobSize + 1 var readBytes int64 blobGot, err := blobserver.Receive(blobReceiver, ref, &readerutil.CountingReader{ io.LimitReader(mimePart, tooBig), &readBytes, }) if readBytes == tooBig { err = fmt.Errorf("blob over the limit of %d bytes", blobserver.MaxBlobSize) } if err != nil { addError(fmt.Sprintf("Error receiving blob %v: %v\n", ref, err)) break } log.Printf("Received blob %v\n", blobGot) receivedBlobs = append(receivedBlobs, blobGot) } res.Received = receivedBlobs if req.Header.Get("X-Camlistore-Vivify") == "1" { for _, got := range receivedBlobs { err := vivify(blobReceiver, got) if err != nil { addError(fmt.Sprintf("Error vivifying blob %v: %v\n", got.Ref.String(), err)) } else { rw.Header().Add("X-Camlistore-Vivified", got.Ref.String()) } } } res.ErrorText = errBuf.String() httputil.ReturnJSON(rw, res) }