func (ui *UIHandler) serveUploadHelper(rw http.ResponseWriter, req *http.Request) { if ui.root.Storage == nil { httputil.ServeJSONError(rw, httputil.ServerError("No BlobRoot configured")) return } mr, err := req.MultipartReader() if err != nil { httputil.ServeJSONError(rw, httputil.ServerError("reading body: "+err.Error())) return } var got []*uploadHelperGotItem var modTime types.Time3339 for { part, err := mr.NextPart() if err == io.EOF { break } if err != nil { httputil.ServeJSONError(rw, httputil.ServerError("reading body: "+err.Error())) break } if part.FormName() == "modtime" { payload, err := ioutil.ReadAll(part) if err != nil { log.Printf("ui uploadhelper: unable to read part for modtime: %v", err) continue } modTime = types.ParseTime3339OrZero(string(payload)) continue } fileName := part.FileName() if fileName == "" { continue } br, err := schema.WriteFileFromReaderWithModTime(ui.root.Storage, fileName, modTime.Time(), part) if err != nil { httputil.ServeJSONError(rw, httputil.ServerError("writing to blobserver: "+err.Error())) return } got = append(got, &uploadHelperGotItem{ FileName: part.FileName(), ModTime: modTime, FormName: part.FormName(), FileRef: br, }) } httputil.ReturnJSON(rw, &uploadHelperResponse{Got: got}) }
func (sh *SetupHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { if !auth.LocalhostAuthorized(req) { fmt.Fprintf(rw, "<html><body>Setup only allowed from localhost"+ "<p><a href='/'>Back</a></p>"+ "</body></html>\n") return } if req.Method == "POST" { err := req.ParseMultipartForm(10e6) if err != nil { httputil.ServerError(rw, req, err) return } if len(req.Form) > 0 { handleSetupChange(rw, req) return } if strings.Contains(req.URL.Path, "restartCamli") { err = osutil.RestartProcess() if err != nil { log.Fatal("Failed to restart: " + err.Error()) } } } sendWizard(rw, req, false) }
func (ui *UIHandler) serveUploadHelper(rw http.ResponseWriter, req *http.Request) { if ui.root.Storage == nil { httputil.ServeJSONError(rw, httputil.ServerError("No BlobRoot configured")) return } mr, err := req.MultipartReader() if err != nil { httputil.ServeJSONError(rw, httputil.ServerError("reading body: "+err.Error())) return } var got []*uploadHelperGotItem for { part, err := mr.NextPart() if err == io.EOF { break } if err != nil { httputil.ServeJSONError(rw, httputil.ServerError("reading body: "+err.Error())) break } fileName := part.FileName() if fileName == "" { continue } br, err := schema.WriteFileFromReader(ui.root.Storage, fileName, part) if err != nil { httputil.ServeJSONError(rw, httputil.ServerError("writing to blobserver: "+err.Error())) return } got = append(got, &uploadHelperGotItem{ FileName: part.FileName(), FormName: part.FormName(), FileRef: br, }) } httputil.ReturnJSON(rw, &uploadHelperResponse{Got: got}) }
func setupHome(rw http.ResponseWriter, req *http.Request) { port := httputil.RequestTargetPort(req) localhostAddr, err := netutil.Localhost() if err != nil { httputil.ServerError(rw, req, err) } ourAddr := &net.TCPAddr{IP: localhostAddr, Port: port} rAddr, err := net.ResolveTCPAddr("tcp", req.RemoteAddr) if err != nil { fmt.Printf("camlistored: unable to resolve RemoteAddr %q: %v", req.RemoteAddr, err) return } uid, err := netutil.AddrPairUserid(rAddr, ourAddr) if err != nil { httputil.ServerError(rw, req, err) } fmt.Fprintf(rw, "Hello %q\n", req.RemoteAddr) fmt.Fprintf(rw, "<p>uid = %d\n", syscall.Getuid()) fmt.Fprintf(rw, "<p>euid = %d\n", syscall.Geteuid()) fmt.Fprintf(rw, "<p>http_local_uid(%q => %q) = %d (%v)\n", req.RemoteAddr, ourAddr, uid, err) }
func sendWizard(rw http.ResponseWriter, req *http.Request, hasChanged bool) { config, err := jsonconfig.ReadFile(osutil.UserServerConfigPath()) if err != nil { httputil.ServerError(rw, req, err) return } err = flattenPublish(config) if err != nil { httputil.ServerError(rw, req, err) return } funcMap := template.FuncMap{ "printWizard": printWizard, "inputIsGallery": func(inputName string) bool { return inputName == "gallery" }, } body := `<form id="WizardForm" action="setup" method="post" enctype="multipart/form-data">` body += `{{range $k,$v := .}}{{printf "%v" $k}} <input type="text" size="30" name ="{{printf "%v" $k}}" value="{{printWizard $v}}" {{if inputIsGallery $k}}placeholder="/pics/,sha1-xxxx,pics.css"{{end}}><br />{{end}}` body += `<input type="submit" form="WizardForm" value="Save"></form>` if hasChanged { body += `<p> Configuration succesfully rewritten </p>` } tmpl, err := template.New("wizard").Funcs(funcMap).Parse(topWizard + body + bottomWizard) if err != nil { httputil.ServerError(rw, req, err) return } err = tmpl.Execute(rw, config) if err != nil { httputil.ServerError(rw, req, err) return } }
func discoveryHelper(rw http.ResponseWriter, req *http.Request, dr *camtypes.Discovery) { rw.Header().Set("Content-Type", "text/javascript") if cb := req.FormValue("cb"); identOrDotPattern.MatchString(cb) { fmt.Fprintf(rw, "%s(", cb) defer rw.Write([]byte(");\n")) } else if v := req.FormValue("var"); identOrDotPattern.MatchString(v) { fmt.Fprintf(rw, "%s = ", v) defer rw.Write([]byte(";\n")) } bytes, err := json.MarshalIndent(dr, "", " ") if err != nil { httputil.ServeJSONError(rw, httputil.ServerError("encoding discovery information: "+err.Error())) return } rw.Write(bytes) }
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") }
func handleSetupChange(rw http.ResponseWriter, req *http.Request) { hilevelConf, err := jsonconfig.ReadFile(osutil.UserServerConfigPath()) if err != nil { httputil.ServerError(rw, req, err) return } hasChanged := false var el interface{} publish := jsonconfig.Obj{} for k, v := range req.Form { if _, ok := hilevelConf[k]; !ok { if k != "gallery" && k != "blog" { continue } } switch k { case "https": b, err := strconv.ParseBool(v[0]) if err != nil { httputil.ServerError(rw, req, fmt.Errorf("https field expects a boolean value")) } el = b case "replicateTo": // TODO(mpl): figure out why it is always seen as different from the conf el = []interface{}{} if len(v[0]) > 0 { els := []string{} vals := strings.Split(v[0], ",") els = append(els, vals...) el = els } // TODO(mpl): "handler,rootPermanode[,style]" for each published entity for now. // we will need something more readable later probably case "gallery", "blog": if len(v[0]) > 0 { pub := strings.Split(v[0], ",") if len(pub) < 2 || len(pub) > 3 { // no need to fail loudly for now as we'll probably change this format continue } handler := jsonconfig.Obj{} handler["template"] = k handler["rootPermanode"] = pub[1] if len(pub) > 2 { handler["style"] = pub[2] } publish[pub[0]] = handler } continue default: el = v[0] } if reflect.DeepEqual(hilevelConf[k], el) { continue } hasChanged = true hilevelConf[k] = el } // "publish" wasn't checked yet if !reflect.DeepEqual(hilevelConf["publish"], publish) { hilevelConf["publish"] = publish hasChanged = true } if hasChanged { err = rewriteConfig(&hilevelConf, osutil.UserServerConfigPath()) if err != nil { httputil.ServerError(rw, req, err) return } } sendWizard(rw, req, hasChanged) return }
// serveBlobRef sends 'blobref' to 'conn' as directed by the Range header in 'req' func serveBlobRef(conn http.ResponseWriter, req *http.Request, blobRef *blobref.BlobRef, fetcher blobref.StreamingFetcher) { if w, ok := fetcher.(blobserver.ContextWrapper); ok { fetcher = w.WrapContext(req) } file, size, err := fetcher.FetchStreaming(blobRef) switch err { case nil: break case os.ErrNotExist: conn.WriteHeader(http.StatusNotFound) fmt.Fprintf(conn, "Blob %q not found", blobRef) return default: httputil.ServerError(conn, req, err) return } defer file.Close() seeker, isSeeker := file.(io.Seeker) reqRange := httprange.FromRequest(req) if reqRange.SkipBytes() != 0 && isSeeker { // TODO: set the Range-specific response headers too, // acknowledging that we honored the content range // request. _, err = seeker.Seek(reqRange.SkipBytes(), 0) if err != nil { httputil.ServerError(conn, req, err) return } } var input io.Reader = file if reqRange.LimitBytes() != -1 { input = io.LimitReader(file, reqRange.LimitBytes()) } remainBytes := size - reqRange.SkipBytes() if reqRange.LimitBytes() != -1 && reqRange.LimitBytes() < remainBytes { remainBytes = reqRange.LimitBytes() } conn.Header().Set("Content-Type", "application/octet-stream") if reqRange.IsWholeFile() { conn.Header().Set("Content-Length", strconv.FormatInt(size, 10)) // If it's small and all UTF-8, assume it's text and // just render it in the browser. This is more for // demos/debuggability than anything else. It isn't // part of the spec. if size <= 32<<10 { var buf bytes.Buffer _, err := io.Copy(&buf, input) if err != nil { httputil.ServerError(conn, req, err) return } if utf8.Valid(buf.Bytes()) { conn.Header().Set("Content-Type", "text/plain; charset=utf-8") } input = &buf } } if !reqRange.IsWholeFile() { conn.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", reqRange.SkipBytes(), reqRange.SkipBytes()+remainBytes, size)) conn.WriteHeader(http.StatusPartialContent) } bytesCopied, err := io.Copy(conn, input) // If there's an error at this point, it's too late to tell the client, // as they've already been receiving bytes. But they should be smart enough // to verify the digest doesn't match. But we close the (chunked) response anyway, // to further signal errors. killConnection := func() { if hj, ok := conn.(http.Hijacker); ok { log.Printf("Force-closing TCP connection to signal error sending %q", blobRef) if closer, _, err := hj.Hijack(); err != nil { closer.Close() } } } if err != nil { fmt.Fprintf(os.Stderr, "Error sending file: %v, err=%v\n", blobRef, err) killConnection() return } if bytesCopied != remainBytes { fmt.Fprintf(os.Stderr, "Error sending file: %v, copied=%d, not %d\n", blobRef, bytesCopied, remainBytes) killConnection() return } }
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.ServerError(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 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, err := commonUploadResponse(blobReceiver, req) if err != nil { httputil.ServerError(conn, req, err) } 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 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.BlobRef.String(), err)) } else { conn.Header().Add("X-Camlistore-Vivified", got.BlobRef.String()) } } } if errText != "" { ret["errorText"] = errText } httputil.ReturnJSON(conn, ret) }
// serveBlobRef sends 'blobref' to 'conn' as directed by the Range header in 'req' func serveBlobRef(conn http.ResponseWriter, req *http.Request, blobRef *blobref.BlobRef, fetcher blobref.StreamingFetcher) { if w, ok := fetcher.(blobserver.ContextWrapper); ok { fetcher = w.WrapContext(req) } file, size, err := fetcher.FetchStreaming(blobRef) switch err { case nil: break case os.ErrNotExist: conn.WriteHeader(http.StatusNotFound) fmt.Fprintf(conn, "Blob %q not found", blobRef) return default: httputil.ServerError(conn, req, err) return } defer file.Close() seeker, isSeeker := file.(io.Seeker) reqRange := httprange.FromRequest(req) if reqRange.SkipBytes() != 0 && isSeeker { // TODO: set the Range-specific response headers too, // acknowledging that we honored the content range // request. _, err = seeker.Seek(reqRange.SkipBytes(), 0) if err != nil { httputil.ServerError(conn, req, err) return } } var input io.Reader = file if reqRange.LimitBytes() != -1 { input = io.LimitReader(file, reqRange.LimitBytes()) } remainBytes := size - reqRange.SkipBytes() if reqRange.LimitBytes() != -1 && reqRange.LimitBytes() < remainBytes { remainBytes = reqRange.LimitBytes() } // Assume this generic content type by default. For better // demos we'll try to sniff and guess the "right" MIME type in // certain cases (no Range requests, etc) but this isn't part // of the Camli spec at all. We just do it to ease demos. contentType := "application/octet-stream" if reqRange.IsWholeFile() { const peekSize = 1024 bufReader := bufio.NewReaderSize(input, peekSize) header, _ := bufReader.Peek(peekSize) if len(header) >= 8 { switch { case utf8.Valid(header): contentType = "text/plain; charset=utf-8" case bytes.HasPrefix(header, []byte{0xff, 0xd8, 0xff, 0xe2}): contentType = "image/jpeg" case bytes.HasPrefix(header, []byte{0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa}): contentType = "image/png" } } input = bufReader conn.Header().Set("Content-Length", strconv.FormatInt(size, 10)) } conn.Header().Set("Content-Type", contentType) if !reqRange.IsWholeFile() { conn.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", reqRange.SkipBytes(), reqRange.SkipBytes()+remainBytes, size)) conn.WriteHeader(http.StatusPartialContent) } bytesCopied, err := io.Copy(conn, input) // If there's an error at this point, it's too late to tell the client, // as they've already been receiving bytes. But they should be smart enough // to verify the digest doesn't match. But we close the (chunked) response anyway, // to further signal errors. killConnection := func() { if hj, ok := conn.(http.Hijacker); ok { log.Printf("Force-closing TCP connection to signal error sending %q", blobRef) if closer, _, err := hj.Hijack(); err != nil { closer.Close() } } } if err != nil { fmt.Fprintf(os.Stderr, "Error sending file: %v, err=%v\n", blobRef, err) killConnection() return } if bytesCopied != remainBytes { fmt.Fprintf(os.Stderr, "Error sending file: %v, copied=%d, not %d\n", blobRef, bytesCopied, remainBytes) killConnection() return } }