// processExif attempts to rotate a JPEG based on the exif data. If the exif data // cannot be decoded or the orientation tag not read, we return nil so that the image // may continue to be uploaded. If there is an error encoding the image after // modification, this is returned to the caller. func (f *FileMetadataType) processExif() error { // Decode exif. ex, err := exif.Decode(bytes.NewReader(f.Content)) if err != nil { return nil } // Get orientation tag. tag, err := ex.Get(exif.Orientation) if err != nil { return nil } orientation, err := tag.Int(0) if err != nil { return nil } var ( angle int flipMode exifutil.FlipDirection switchDimensions bool ) angle, flipMode, switchDimensions = exifutil.ProcessOrientation(int64(orientation)) im, _, err := image.Decode(bytes.NewReader(f.Content)) if err != nil { return err } if angle != 0 { im = exifutil.Rotate(im, angle) } if flipMode != 0 { im = exifutil.Flip(im, flipMode) } if switchDimensions { f.Width, f.Height = f.Height, f.Width } // Encode JPEG and replace f.Content. buf := new(bytes.Buffer) err = jpeg.Encode(buf, im, nil) if err != nil { return err } f.Content = buf.Bytes() // Update the hash and filesize based on changed content. sha1, err := h.SHA1(f.Content) if err != nil { return err } f.FileHash = sha1 f.FileSize = int32(len(f.Content)) return nil }
// StoreGravatar stores the gravatar file in the database func StoreGravatar(gravatarURL string) (FileMetadataType, int, error) { // TODO(matt): reduce duplication with models.FileController resp, err := http.Get(gravatarURL) if err != nil { glog.Errorf("http.Get(`%s`) %+v", gravatarURL, err) return FileMetadataType{}, http.StatusInternalServerError, fmt.Errorf("Could not retrieve gravatar") } defer resp.Body.Close() fileContent, err := ioutil.ReadAll(resp.Body) if err != nil { glog.Errorf("ioutil.ReadAll(resp.Body) %+v", err) return FileMetadataType{}, http.StatusInternalServerError, fmt.Errorf("Could not read gravatar response") } metadata := FileMetadataType{} metadata.Content = fileContent metadata.FileSize = int32(len(metadata.Content)) metadata.FileHash, err = h.SHA1(metadata.Content) if err != nil { glog.Errorf("h.Sha1(metadata.Content) %+v", err) return FileMetadataType{}, http.StatusInternalServerError, fmt.Errorf("Could not generate file SHA-1") } metadata.MimeType = resp.Header.Get("Content-Type") metadata.Created = time.Now() metadata.AttachCount++ status, err := metadata.Insert(AvatarMaxWidth, AvatarMaxHeight) if err != nil { glog.Errorf("metadata.Insert(%d, %d) %+v", AvatarMaxWidth, AvatarMaxHeight, err) return FileMetadataType{}, status, fmt.Errorf("Could not insert gravatar file metadata") } return metadata, http.StatusOK, nil }
// Create handles POST func (ctl *FilesController) Create(c *models.Context) { if c.Auth.UserID < 1 { c.RespondWithErrorMessage(h.NoAuthMessage, http.StatusForbidden) return } mr, err := c.Request.MultipartReader() if err != nil { c.RespondWithErrorMessage( fmt.Sprintf("Only multipart forms can be posted: %v", err.Error()), http.StatusBadRequest, ) return } var files []models.FileMetadataType part, err := mr.NextPart() for err == nil { // FormName() is only populated if the part has // Content-Disposition set to "form-data" if part.FormName() != "" { if part.FileName() != "" { // Persist file and metadata md := models.FileMetadataType{} md.FileName = part.FileName() md.Created = time.Now() md.MimeType = part.Header.Get("Content-Type") md.Content, err = ioutil.ReadAll(part) if err != nil { c.RespondWithErrorMessage( fmt.Sprintf("Couldn't not read form part: %v", err.Error()), http.StatusBadRequest, ) return } sha1, err := h.SHA1(md.Content) if err != nil { c.RespondWithErrorMessage( fmt.Sprintf("Couldn't generate SHA-1: %v", err.Error()), http.StatusInternalServerError, ) return } md.FileHash = sha1 md.FileSize = int32(len(md.Content)) // Check whether the file size overflowed int32 (over 2GB) if int(md.FileSize) != len(md.Content) { c.RespondWithErrorMessage( "File too large. Max size = 2147482548 bytes (2GB)", http.StatusInternalServerError, ) return } // Resize if needed query := c.Request.URL.Query() var ( maxWidth int64 maxHeight int64 ) if query.Get("maxWidth") != "" { max, err := strconv.ParseInt(strings.Trim(query.Get("maxWidth"), " "), 10, 64) if err != nil || max < 0 { c.RespondWithErrorMessage("maxWidth needs to be a positive integer", http.StatusBadRequest) return } maxWidth = max } if query.Get("maxHeight") != "" { max, err := strconv.ParseInt(strings.Trim(query.Get("maxHeight"), " "), 10, 64) if err != nil || max < 0 { c.RespondWithErrorMessage("maxHeight needs to be a positive integer", http.StatusBadRequest) return } maxHeight = max } status, err := md.Insert(maxWidth, maxHeight) if err != nil { c.RespondWithErrorMessage( fmt.Sprintf("Couldn't upload file and metadata: %v", err.Error()), status, ) return } files = append(files, md) } } part, err = mr.NextPart() } c.RespondWithData(files) }
// ResizeImage will resize an image (usually an avatar) to fit within the given // constraints whilst preserving the aspect ratio func (f *FileMetadataType) ResizeImage( maxWidth int64, maxHeight int64, ) ( int, error, ) { var ( width int height int ) if maxWidth > 0 && f.Width > maxWidth { width = int(maxWidth) } if maxHeight > 0 && f.Height > maxHeight && f.Height > f.Width { width = 0 height = int(maxHeight) } if width == 0 && height == 0 { // Nothing to do, either the params weren't supplied or the image is // already small enough return http.StatusOK, nil } r := bytes.NewReader(f.Content) // middle var is format, i.e. which decoder was used: "gif", "jpeg", "png" // in the case of "gif", only the first frame is extracted img, format, err := image.Decode(r) if err != nil { glog.Errorf("image.Decode(r) %+v", err) return http.StatusBadRequest, err } m := imaging.Resize(img, width, height, imaging.Lanczos) var buf bytes.Buffer switch format { case "gif": err = gif.Encode(&buf, m, nil) if err != nil { glog.Errorf("gif.Encode(&buf, m, nil) %+v", err) return http.StatusBadRequest, err } f.MimeType = ImageGifMimeType case "jpeg": err = jpeg.Encode(&buf, m, nil) if err != nil { glog.Errorf("jpeg.Encode(&buf, m, nil) %+v", err) return http.StatusBadRequest, err } f.MimeType = ImageJpegMimeType default: err = png.Encode(&buf, m) if err != nil { glog.Errorf("png.Encode(&buf, m, nil) %+v", err) return http.StatusBadRequest, err } f.MimeType = ImagePngMimeType } // Update the file meta data f.Content = buf.Bytes() sha1, err := h.SHA1(f.Content) if err != nil { glog.Errorf("h.Sha1(f.Content) %+v", err) return http.StatusInternalServerError, fmt.Errorf("Couldn't generate SHA-1") } f.FileHash = sha1 f.FileSize = int32(len(f.Content)) im, _, err := image.DecodeConfig(bytes.NewReader(f.Content)) if err != nil { glog.Errorf( "image.DecodeConfig(bytes.NewReader(f.Content)) %+v", err, ) return http.StatusInternalServerError, err } f.Height = int64(im.Height) f.Width = int64(im.Width) return http.StatusOK, nil }