func NewAuthenticatedServer(c *config.Configuration, strategy imageprocessor.ImageProcessorStrategy, auth Authenticator) *Server { factory := imagestore.NewFactory(c) httpclient := &http.Client{} stores := factory.NewImageStores() hashGenerator := factory.NewHashGenerator(stores) return &Server{c, httpclient, stores, hashGenerator, strategy, auth} }
func NewServer(c *config.Configuration, strategy imageprocessor.ImageProcessorStrategy) *Server { factory := imagestore.NewFactory(c) httpclient := &http.Client{} stores := factory.NewImageStores() store := stores[0] hashGenerator := factory.NewHashGenerator(store) authenticator := &PassthroughAuthenticator{} return &Server{c, httpclient, store, hashGenerator, strategy, authenticator} }
func (s *Server) buildThumbResponse(upload *uploadedfile.UploadedFile) (map[string]interface{}, error) { factory := imagestore.NewFactory(s.Config) thumbsResp := map[string]interface{}{} for _, t := range upload.GetThumbs() { thumbName := fmt.Sprintf("%s/%s", upload.GetHash(), t.GetName()) tObj := factory.NewStoreObject(thumbName, upload.GetMime(), "thumbnail") err := tObj.Store(t, s.ImageStore) if err != nil { return nil, err } thumbsResp[t.GetName()] = tObj.Url } return thumbsResp, nil }
func (s *Server) uploadFile(uploadFile io.Reader, fileName string, thumbs []*uploadedfile.ThumbFile, user *AuthenticatedUser) ServerResponse { tmpFile, err := ioutil.TempFile(os.TempDir(), "image") if err != nil { fmt.Println(err) return ServerResponse{ Error: "Unable to write to /tmp", Status: http.StatusInternalServerError, } } defer tmpFile.Close() _, err = io.Copy(tmpFile, uploadFile) if err != nil { fmt.Println(err) return ServerResponse{ Error: "Unable to copy image to disk!", Status: http.StatusInternalServerError, } } upload, err := uploadedfile.NewUploadedFile(fileName, tmpFile.Name(), thumbs) defer upload.Clean() if err != nil { return ServerResponse{ Error: "Error detecting mime type!", Status: http.StatusInternalServerError, } } processor, err := s.processorStrategy(s.Config, upload) if err != nil { log.Printf("Error creating processor factory: %s", err.Error()) return ServerResponse{ Error: "Unable to process image!", Status: http.StatusInternalServerError, } } err = processor.Run(upload) if err != nil { log.Printf("Error processing %+v: %s", upload, err.Error()) return ServerResponse{ Error: "Unable to process image!", Status: http.StatusInternalServerError, } } upload.SetHash(s.hashGenerator.Get()) factory := imagestore.NewFactory(s.Config) obj := factory.NewStoreObject(upload.GetHash(), upload.GetMime(), "original") uploadFilepath := upload.GetPath() uploadFileFd, err := os.Open(uploadFilepath) if err != nil { log.Printf("Error opening processed output %+v at %s: %s", upload, uploadFilepath, err.Error()) return ServerResponse{ Error: "Unable to save image!", Status: http.StatusInternalServerError, } } obj, err = s.ImageStore.Save(uploadFileFd, obj) if err != nil { log.Printf("Error saving processed output to store: %s", err.Error()) return ServerResponse{ Error: "Unable to save image!", Status: http.StatusInternalServerError, } } thumbsResp := map[string]interface{}{} for _, t := range upload.GetThumbs() { thumbName := fmt.Sprintf("%s/%s", upload.GetHash(), t.GetName()) tObj := factory.NewStoreObject(thumbName, upload.GetMime(), "t") tPath := t.GetPath() tFile, err := os.Open(tPath) if err != nil { return ServerResponse{ Error: "Unable to save thumbnail!", Status: http.StatusInternalServerError, } } tObj, err = s.ImageStore.Save(tFile, tObj) if err != nil { return ServerResponse{ Error: "Unable to save thumbnail!", Status: http.StatusInternalServerError, } } thumbsResp[t.GetName()] = tObj.Url } size, err := upload.FileSize() if err != nil { return ServerResponse{ Error: "Unable to fetch image metadata!", Status: http.StatusInternalServerError, } } width, height, err := upload.Dimensions() if err != nil { return ServerResponse{ Error: "Error fetching upload dimensions: " + err.Error(), Status: http.StatusInternalServerError, } } var userID string if user != nil { userID = string(user.UserID) } resp := ImageResponse{ Link: obj.Url, Mime: obj.MimeType, Hash: upload.GetHash(), Name: fileName, Size: size, Width: width, Height: height, OCRText: upload.GetOCRText(), Thumbs: thumbsResp, UserID: userID, } return ServerResponse{ Data: resp, Status: http.StatusOK, } }
func (s *Server) uploadFile(uploadFile io.Reader, fileName string, thumbs []*uploadedfile.ThumbFile, user *AuthenticatedUser) ServerResponse { tmpFile, err := saveToTmp(uploadFile) if err != nil { return ServerResponse{ Error: "Error saving to disk!", Status: http.StatusInternalServerError, } } upload, err := uploadedfile.NewUploadedFile(fileName, tmpFile, thumbs) defer upload.Clean() if err != nil { return ServerResponse{ Error: "Error detecting mime type!", Status: http.StatusInternalServerError, } } processor, err := s.processorStrategy(s.Config, upload) if err != nil { log.Printf("Error creating processor factory: %s", err.Error()) return ServerResponse{ Error: "Unable to process image!", Status: http.StatusInternalServerError, } } err = processor.Run(upload) if err != nil { log.Printf("Error processing %+v: %s", upload, err.Error()) return ServerResponse{ Error: "Unable to process image!", Status: http.StatusInternalServerError, } } upload.SetHash(s.hashGenerator.Get()) factory := imagestore.NewFactory(s.Config) obj := factory.NewStoreObject(upload.GetHash(), upload.GetMime(), "original") uploadFilepath := upload.GetPath() obj, err = s.ImageStore.Save(uploadFilepath, obj) if err != nil { log.Printf("Error saving processed output to store: %s", err.Error()) return ServerResponse{ Error: "Unable to save image!", Status: http.StatusInternalServerError, } } thumbsResp, err := s.buildThumbResponse(upload) if err != nil { return ServerResponse{ Error: "Unable to process thumbnail!", Status: http.StatusInternalServerError, } } size, err := upload.FileSize() if err != nil { return ServerResponse{ Error: "Unable to fetch image metadata!", Status: http.StatusInternalServerError, } } width, height, err := upload.Dimensions() if err != nil { return ServerResponse{ Error: "Error fetching upload dimensions: " + err.Error(), Status: http.StatusInternalServerError, } } var userID string if user != nil { userID = string(user.UserID) } resp := ImageResponse{ Link: obj.Url, Mime: obj.MimeType, Hash: upload.GetHash(), Name: fileName, Size: size, Width: width, Height: height, OCRText: upload.GetOCRText(), Thumbs: thumbsResp, UserID: userID, } return ServerResponse{ Data: resp, Status: http.StatusOK, } }
func (s *Server) Configure(muxer *http.ServeMux) { var extractorFile fileExtractor = func(r *http.Request) (uploadFile io.Reader, filename string, uerr *UserError) { uploadFile, header, err := r.FormFile("image") if err != nil { return nil, "", &UserError{LogMessage: err, UserFacingMessage: errors.New("Error processing file")} } return uploadFile, header.Filename, nil } var extractorUrl fileExtractor = func(r *http.Request) (uploadFile io.Reader, filename string, uerr *UserError) { url := r.FormValue("image") uploadFile, err := s.download(url) if err != nil { return nil, "", &UserError{LogMessage: err, UserFacingMessage: errors.New("Error downloading URL!")} } return uploadFile, path.Base(url), nil } var extractorBase64 fileExtractor = func(r *http.Request) (uploadFile io.Reader, filename string, uerr *UserError) { input := r.FormValue("image") b64data := input[strings.IndexByte(input, ',')+1:] uploadFile = base64.NewDecoder(base64.StdEncoding, strings.NewReader(b64data)) return uploadFile, "", nil } type uploadEndpoint func(fileExtractor, *AuthenticatedUser) http.HandlerFunc var uploadHandler uploadEndpoint = func(extractor fileExtractor, user *AuthenticatedUser) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { uploadFile, filename, uerr := extractor(r) if uerr != nil { log.Printf("Error extracting files: %s", uerr.LogMessage.Error()) resp := ServerResponse{ Status: http.StatusBadRequest, Error: uerr.UserFacingMessage.Error(), } resp.Write(w) return } thumbs, err := parseThumbs(r) if err != nil { resp := ServerResponse{ Status: http.StatusBadRequest, Error: "Error parsing thumbnails!", } resp.Write(w) return } resp := s.uploadFile(uploadFile, filename, thumbs, user) switch uploadFile.(type) { case io.ReadCloser: defer uploadFile.(io.ReadCloser).Close() break default: break } resp.Write(w) } } // Wrap an existing upload endpoint with authentication, returning a new endpoint that 4xxs unless authentication is passed. authenticatedEndpoint := func(endpoint uploadEndpoint, extractor fileExtractor) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { requestVars := mux.Vars(r) attemptedUserIdString, ok := requestVars["user_id"] // They didn't send a user ID to a /user endpoint if !ok || attemptedUserIdString == "" { w.WriteHeader(http.StatusBadRequest) return } user, err := s.authenticator.GetUser(r) // Their HMAC was invalid or they are trying to upload to someone else's account if user == nil || err != nil || user.UserID != attemptedUserIdString { w.WriteHeader(http.StatusUnauthorized) log.Printf("Authentication error: %s", err.Error()) return } handler := endpoint(extractor, user) handler(w, r) } } thumbnailHandler := func(w http.ResponseWriter, r *http.Request) { imageID := r.FormValue("uid") factory := imagestore.NewFactory(s.Config) tObj := factory.NewStoreObject(imageID, "", "original") thumbs, err := parseThumbs(r) if err != nil { resp := ServerResponse{ Status: http.StatusBadRequest, Error: "Error parsing thumbnails!", } resp.Write(w) return } if len(thumbs) != 1 { resp := ServerResponse{ Status: http.StatusBadRequest, Error: "Wrong number of thumbnails, expected 1", } resp.Write(w) return } storeReader, err := s.ImageStore.Get(tObj) if err != nil { resp := ServerResponse{ Status: http.StatusBadRequest, Error: fmt.Sprintf("Error retrieving image with ID: %s", imageID), } resp.Write(w) return } defer storeReader.Close() storeFile, err := saveToTmp(storeReader) if err != nil { resp := ServerResponse{ Status: http.StatusBadRequest, Error: "Error parsing thumbnails!", } resp.Write(w) return } defer os.Remove(storeFile) upload, err := uploadedfile.NewUploadedFile("", storeFile, thumbs) if err != nil { log.Printf("Error processing %+v: %s", storeFile, err.Error()) resp := ServerResponse{ Error: "Unable to process thumbnail!", Status: http.StatusInternalServerError, } resp.Write(w) return } upload.SetHash(imageID) defer upload.Clean() processor, _ := imageprocessor.ThumbnailStrategy(s.Config, upload) err = processor.Run(upload) if err != nil { log.Printf("Error processing %+v: %s", upload, err.Error()) resp := ServerResponse{ Error: "Unable to process thumbnail!", Status: http.StatusInternalServerError, } resp.Write(w) return } ts := upload.GetThumbs() t := ts[0] thumbName := fmt.Sprintf("%s/%s", upload.GetHash(), t.GetName()) tObj = factory.NewStoreObject(thumbName, upload.GetMime(), "thumbnail") err = tObj.Store(t, s.ImageStore) if err != nil { log.Printf("Error storing %+v: %s", t, err.Error()) resp := ServerResponse{ Error: "Unable to store thumbnail!", Status: http.StatusInternalServerError, } resp.Write(w) return } http.ServeFile(w, r, t.GetPath()) } rootHandler := func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "<html><head><title>An open source image uploader by Imgur</title></head><body style=\"background-color: #2b2b2b; color: white\">") fmt.Fprint(w, "Congratulations! Your image upload server is up and running. Head over to the <a style=\"color: #85bf25 \" href=\"https://github.com/Imgur/mandible\">github</a> page for documentation") fmt.Fprint(w, "<br/><br/><br/><img src=\"http://i.imgur.com/YbfUjs5.png?2\" />") fmt.Fprint(w, "</body></html>") } router := mux.NewRouter() router.HandleFunc("/file", uploadHandler(extractorFile, nil)) router.HandleFunc("/url", uploadHandler(extractorUrl, nil)) router.HandleFunc("/base64", uploadHandler(extractorBase64, nil)) router.HandleFunc("/user/{user_id}/file", authenticatedEndpoint(uploadHandler, extractorBase64)) router.HandleFunc("/user/{user_id}/url", authenticatedEndpoint(uploadHandler, extractorUrl)) router.HandleFunc("/user/{user_id}/base64", authenticatedEndpoint(uploadHandler, extractorBase64)) router.HandleFunc("/thumbnail", thumbnailHandler) router.HandleFunc("/", rootHandler) muxer.Handle("/", router) }