func (dh *DownloadHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request, file *blobref.BlobRef) { if req.Method != "GET" && req.Method != "HEAD" { http.Error(rw, "Invalid download method", 400) return } fetchSeeker, err := dh.storageSeekFetcher() if err != nil { http.Error(rw, err.String(), 500) return } fr, err := schema.NewFileReader(fetchSeeker, file) if err != nil { http.Error(rw, "Can't serve file: "+err.String(), 500) return } defer fr.Close() schema := fr.FileSchema() rw.Header().Set("Content-Length", fmt.Sprintf("%d", schema.SumPartsSize())) // TODO: fr.FileSchema() and guess a mime type? For now: mimeType := "application/octet-stream" if dh.ForceMime != "" { mimeType = dh.ForceMime } rw.Header().Set("Content-Type", mimeType) if req.Method == "HEAD" { vbr := blobref.Parse(req.FormValue("verifycontents")) if vbr == nil { return } hash := vbr.Hash() if hash == nil { return } io.Copy(hash, fr) // ignore errors, caught later if vbr.HashMatches(hash) { rw.Header().Set("X-Camli-Contents", vbr.String()) } return } n, err := io.Copy(rw, fr) log.Printf("For %q request of %s: copied %d, %v", req.Method, req.URL.RawPath, n, err) if err != nil { log.Printf("error serving download of file schema %s: %v", file, err) return } if size := schema.SumPartsSize(); n != int64(size) { log.Printf("error serving download of file schema %s: sent %d, expected size of %d", file, n, size) return } }
func (ui *UIHandler) serveDownload(rw http.ResponseWriter, req *http.Request) { if req.Method != "GET" && req.Method != "HEAD" { http.Error(rw, "Invalid download method", 400) return } if ui.Storage == nil { http.Error(rw, "No BlobRoot configured", 500) return } fetchSeeker, err := ui.storageSeekFetcher() if err != nil { http.Error(rw, err.String(), 500) return } suffix := req.Header.Get("X-PrefixHandler-PathSuffix") m := downloadPattern.FindStringSubmatch(suffix) if m == nil { httputil.ErrorRouting(rw, req) return } fbr := blobref.Parse(m[1]) if fbr == nil { http.Error(rw, "Invalid blobref", 400) return } filename := m[2] if len(filename) > 0 { filename = filename[1:] // remove leading slash } fr, err := schema.NewFileReader(fetchSeeker, fbr) if err != nil { http.Error(rw, "Can't serve file: "+err.String(), 500) return } defer fr.Close() // TODO: fr.FileSchema() and guess a mime type? For now: schema := fr.FileSchema() rw.Header().Set("Content-Type", "application/octet-stream") rw.Header().Set("Content-Length", fmt.Sprintf("%d", schema.Size)) if req.Method == "HEAD" { vbr := blobref.Parse(req.FormValue("verifycontents")) if vbr == nil { return } hash := vbr.Hash() if hash == nil { return } io.Copy(hash, fr) // ignore errors, caught later if vbr.HashMatches(hash) { rw.Header().Set("X-Camli-Contents", vbr.String()) } return } n, err := io.Copy(rw, fr) log.Printf("For %q request of %s: copied %d, %v", req.Method, req.URL.RawPath, n, err) if err != nil { log.Printf("error serving download of file schema %s: %v", fbr, err) return } if n != int64(schema.Size) { log.Printf("error serving download of file schema %s: sent %d, expected size of %d", fbr, n, schema.Size) return } }
func (ui *UIHandler) serveThumbnail(rw http.ResponseWriter, req *http.Request) { if ui.Storage == nil { http.Error(rw, "No BlobRoot configured", 500) return } fetchSeeker, err := ui.storageSeekFetcher() if err != nil { http.Error(rw, err.String(), 500) return } suffix := req.Header.Get("X-PrefixHandler-PathSuffix") m := thumbnailPattern.FindStringSubmatch(suffix) if m == nil { httputil.ErrorRouting(rw, req) return } query := req.URL.Query() width, err := strconv.Atoi(query.Get("mw")) if err != nil { http.Error(rw, "Invalid specified max width 'mw': "+err.String(), 500) return } height, err := strconv.Atoi(query.Get("mh")) if err != nil { http.Error(rw, "Invalid specified height 'mh': "+err.String(), 500) return } blobref := blobref.Parse(m[1]) if blobref == nil { http.Error(rw, "Invalid blobref", 400) return } filename := m[2] if len(filename) > 0 { filename = filename[1:] // remove leading slash } fr, err := schema.NewFileReader(fetchSeeker, blobref) if err != nil { http.Error(rw, "Can't serve file: "+err.String(), 500) return } var buf bytes.Buffer n, err := io.Copy(&buf, fr) i, format, err := image.Decode(&buf) if err != nil { http.Error(rw, "Can't serve file: "+err.String(), 500) return } b := i.Bounds() // only do downscaling, otherwise just serve the original image if width < b.Dx() || height < b.Dy() { const huge = 2400 // If it's gigantic, it's more efficient to downsample first // and then resize; resizing will smooth out the roughness. // (trusting the moustachio guys on that one). if b.Dx() > huge || b.Dy() > huge { w, h := width*2, height*2 if b.Dx() > b.Dy() { w = b.Dx() * h / b.Dy() } else { h = b.Dy() * w / b.Dx() } i = resize.Resample(i, i.Bounds(), w, h) b = i.Bounds() } // conserve proportions. use the smallest of the two as the decisive one. if width > height { width = b.Dx() * height / b.Dy() } else { height = b.Dy() * width / b.Dx() } i = resize.Resize(i, b, width, height) // Encode as a new image buf.Reset() switch format { case "jpeg": err = jpeg.Encode(&buf, i, nil) default: err = png.Encode(&buf, i) } if err != nil { http.Error(rw, "Can't serve file: "+err.String(), 500) return } } ct := "" switch format { case "jpeg": ct = "image/jpeg" default: ct = "image/png" } rw.Header().Set("Content-Type", ct) size := buf.Len() rw.Header().Set("Content-Length", fmt.Sprintf("%d", size)) n, err = io.Copy(rw, &buf) if err != nil { log.Printf("error serving thumbnail of file schema %s: %v", blobref, err) return } if n != int64(size) { log.Printf("error serving thumbnail of file schema %s: sent %d, expected size of %d", blobref, n, size) return } }
func (ih *ImageHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request, file *blobref.BlobRef) { if req.Method != "GET" && req.Method != "HEAD" { http.Error(rw, "Invalid method", 400) return } mw, mh := ih.MaxWidth, ih.MaxHeight if mw == 0 || mh == 0 || mw > 2000 || mh > 2000 { http.Error(rw, "bogus dimensions", 400) return } fetchSeeker, err := ih.storageSeekFetcher() if err != nil { http.Error(rw, err.String(), 500) return } fr, err := schema.NewFileReader(fetchSeeker, file) if err != nil { http.Error(rw, "Can't serve file: "+err.String(), 500) return } var buf bytes.Buffer n, err := io.Copy(&buf, fr) if err != nil { log.Printf("image resize: error reading image %s: %v", file, err) return } i, format, err := image.Decode(bytes.NewBuffer(buf.Bytes())) if err != nil { http.Error(rw, "Can't serve file: "+err.String(), 500) return } b := i.Bounds() useBytesUnchanged := true isSquare := b.Dx() == b.Dy() if ih.Square && !isSquare { useBytesUnchanged = false i = squareImage(i) b = i.Bounds() } // only do downscaling, otherwise just serve the original image if mw < b.Dx() || mh < b.Dy() { useBytesUnchanged = false const huge = 2400 // If it's gigantic, it's more efficient to downsample first // and then resize; resizing will smooth out the roughness. // (trusting the moustachio guys on that one). if b.Dx() > huge || b.Dy() > huge { w, h := mw*2, mh*2 if b.Dx() > b.Dy() { w = b.Dx() * h / b.Dy() } else { h = b.Dy() * w / b.Dx() } i = resize.Resample(i, i.Bounds(), w, h) b = i.Bounds() } // conserve proportions. use the smallest of the two as the decisive one. if mw > mh { mw = b.Dx() * mh / b.Dy() } else { mh = b.Dy() * mw / b.Dx() } } if !useBytesUnchanged { i = resize.Resize(i, b, mw, mh) // Encode as a new image buf.Reset() switch format { case "jpeg": err = jpeg.Encode(&buf, i, nil) default: err = png.Encode(&buf, i) } if err != nil { http.Error(rw, "Can't serve file: "+err.String(), 500) return } } rw.Header().Set("Content-Type", imageContentTypeOfFormat(format)) size := buf.Len() rw.Header().Set("Content-Length", fmt.Sprintf("%d", size)) 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 } }