func AdminCounters(w http.ResponseWriter, r *http.Request, cfg config.Configuration, ctx model.Context) { w.Header().Set("Vary", "Content-Type") w.Header().Set("Cache-Control", "s-maxage=0, max-age=0") var status = 200 event := ctx.Events.New(ctx.RemoteAddr, []string{"admin", "counters"}, "", "") event.Update(r.Header.Get("user-agent"), 0) defer event.Done() stats := ctx.Metrics.GetStats() type Out struct { Counters map[string]int64 Uptime time.Duration UptimeReadable string Now time.Time } data := Out{ Counters: stats, Uptime: ctx.Metrics.Uptime(), UptimeReadable: humanize.Time(ctx.Metrics.StartTime()), Now: time.Now().UTC(), } if r.Header.Get("Accept") == "application/json" { w.Header().Set("Content-Type", "application/json") output.JSONresponse(w, status, data, ctx) } else { output.HTMLresponse(w, "counters", status, data, ctx) } return }
func FetchBin(w http.ResponseWriter, r *http.Request, cfg config.Configuration, ctx model.Context) { w.Header().Set("Cache-Control", "s-maxage=15") w.Header().Set("Vary", "Content-Type") var status = 200 params := mux.Vars(r) bin := params["bin"] if err := verifyBin(bin); err != nil { http.Error(w, "Invalid bin", 400) return } event := ctx.Events.New(ctx.RemoteAddr, []string{"bin", "view"}, bin, "") defer event.Done() var err error b, err := ctx.Backend.GetBinMetaData(bin) if err != nil { if ctx.Backend.BinExists(bin) { ctx.Log.Println(err) event.Update(err.Error(), 2) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } else { // This bin does not exist (but can be created) event.Update("Bin does not exist", 1) status = 404 b = ctx.Backend.NewBin(bin) } } if b.Expired { http.Error(w, "This bin expired "+b.ExpiresReadable+".", 410) return } ctx.Metrics.Incr("total-view-bin") ctx.Metrics.Incr("bin-view bin=" + bin + " referer=" + r.Header.Get("Referer")) if r.Header.Get("Accept") == "application/json" { w.Header().Set("Content-Type", "application/json") output.JSONresponse(w, status, b, ctx) return } else { if len(b.Files) == 0 { output.HTMLresponse(w, "newbin", status, b, ctx) } else { output.HTMLresponse(w, "viewbin", status, b, ctx) } return } }
func AdminEvents(w http.ResponseWriter, r *http.Request, cfg config.Configuration, ctx model.Context) { w.Header().Set("Vary", "Content-Type") w.Header().Set("Cache-Control", "s-maxage=0, max-age=0") var status = 200 event := ctx.Events.New(ctx.RemoteAddr, []string{"admin", "events"}, "", "") event.Update(r.Header.Get("user-agent"), 0) defer event.Done() //u, err := url.Parse(r.RequestURI) //if err != nil { // ctx.Log.Println(err) //} //queryParams, err := url.ParseQuery(u.RawQuery) //if err != nil { // ctx.Log.Println(err) //} //filter := metrics.Event{ // Bin: queryParams.Get("bin"), // Category: queryParams.Get("category"), // Filename: queryParams.Get("filename"), // RemoteAddr: queryParams.Get("remoteaddr"), // URL: queryParams.Get("url"), //} type Out struct { Events []events.Event Uptime time.Duration UptimeReadable string Now time.Time } data := Out{ Events: ctx.Events.GetAllEvents(0, 10000), Uptime: ctx.Metrics.Uptime(), UptimeReadable: humanize.Time(ctx.Metrics.StartTime()), Now: time.Now().UTC(), } if r.Header.Get("Accept") == "application/json" { w.Header().Set("Content-Type", "application/json") output.JSONresponse(w, status, data, ctx) } else { output.HTMLresponse(w, "events", status, data, ctx) } return }
func ViewIndex(w http.ResponseWriter, r *http.Request, cfg config.Configuration, ctx model.Context) { t := model.Tag{} tag := randomString(cfg.DefaultTagLength) err := t.SetTag(tag) if err != nil { ctx.Log.Println(err) http.Error(w, "Internal Server Error", 500) return } ctx.Log.Println("Tag generated: " + t.Tag) headers := make(map[string]string) headers["Cache-Control"] = "max-age=0" headers["Location"] = ctx.Baseurl + "/" + t.Tag var status = 302 output.JSONresponse(w, status, headers, t, ctx) }
func AdminBins(w http.ResponseWriter, r *http.Request, cfg config.Configuration, ctx model.Context) { w.Header().Set("Vary", "Content-Type") w.Header().Set("Cache-Control", "s-maxage=0, max-age=0") var status = 200 event := ctx.Events.New(ctx.RemoteAddr, []string{"admin", "bins"}, "", "") event.Update(r.Header.Get("user-agent"), 0) defer event.Done() //event := metrics.Event{ // Category: "admin-login", // RemoteAddr: ctx.RemoteAddr, // Text: r.Header.Get("user-agent"), // URL: r.RequestURI, //} //ctx.Metrics.AddEvent(event) bins := ctx.Backend.GetBinsMetaData() type Out struct { Bins []fs.Bin BinsReadable string Uptime time.Duration UptimeReadable string Now time.Time } data := Out{ Bins: bins, BinsReadable: humanize.Comma(int64(len(bins))), Uptime: ctx.Metrics.Uptime(), UptimeReadable: humanize.Time(ctx.Metrics.StartTime()), Now: time.Now().UTC(), } if r.Header.Get("Accept") == "application/json" { w.Header().Set("Content-Type", "application/json") output.JSONresponse(w, status, data, ctx) } else { output.HTMLresponse(w, "bins", status, data, ctx) } return }
func NewBin(w http.ResponseWriter, r *http.Request, cfg config.Configuration, ctx model.Context) { w.Header().Set("Cache-Control", "s-maxage=0, max-age=0") w.Header().Set("Vary", "Content-Type") // XXX: Should ensure that the bin does not exist from before. bin := randomString(cfg.DefaultBinLength) b := ctx.Backend.NewBin(bin) ctx.Metrics.Incr("total-new-bin") var status = 200 if r.Header.Get("Accept") == "application/json" { w.Header().Set("Content-Type", "application/json") output.JSONresponse(w, status, b, ctx) } else { output.HTMLresponse(w, "newbin", status, b, ctx) } return }
func Upload(w http.ResponseWriter, r *http.Request, cfg config.Configuration, ctx model.Context) { var err error f := model.File{} f.RemoteAddr = r.RemoteAddr f.UserAgent = r.Header.Get("User-Agent") // Extract the tag from the request if r.Header.Get("tag") == "" { tag := randomString(cfg.DefaultTagLength) err = f.SetTag(tag) ctx.Log.Println("Tag generated: " + f.Tag) } else { tag := r.Header.Get("tag") err = f.SetTag(tag) ctx.Log.Println("Tag specified: " + tag) } if err != nil { ctx.Log.Println(err) http.Error(w, err.Error(), http.StatusBadRequest) return } f.SetTagDir(cfg.Filedir) ctx.Log.Println("Tag directory: " + f.TagDir) // Write the request body to a temporary file err = f.WriteTempfile(r.Body, cfg.Tempdir) if err != nil { ctx.Log.Println("Unable to write tempfile: ", err) // Clean up by removing the tempfile f.ClearTemp() http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } ctx.Log.Println("Tempfile: " + f.Tempfile) ctx.Log.Println("Tempfile size: " + strconv.FormatInt(f.Bytes, 10) + " bytes") // Do not accept files that are 0 bytes if f.Bytes == 0 { ctx.Log.Println("Empty files are not allowed. Aborting.") // Clean up by removing the tempfile f.ClearTemp() http.Error(w, "No content. The file size must be more than "+ "0 bytes.", http.StatusBadRequest) return } // Calculate and verify the checksum checksum := r.Header.Get("content-sha256") if checksum != "" { ctx.Log.Println("Checksum specified: " + checksum) } err = f.VerifySHA256(checksum) ctx.Log.Println("Checksum calculated: " + f.Checksum) if err != nil { ctx.Log.Println("The specified checksum did not match") http.Error(w, "Checksum did not match", http.StatusConflict) return } // Trigger new tag t := model.Tag{} t.SetTag(f.Tag) t.SetTagDir(cfg.Filedir) if !t.TagDirExists() { if cfg.TriggerNewTag != "" { ctx.Log.Println("Executing trigger: New tag") triggerNewTagHandler(cfg.TriggerNewTag, f.Tag) } } // Create the tag directory if it does not exist err = f.EnsureTagDirectoryExists() if err != nil { ctx.Log.Println("Unable to create tag directory: ", f.TagDir) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } t.CalculateExpiration(cfg.Expiration) expired, err := t.IsExpired(cfg.Expiration) if err != nil { ctx.Log.Println(err) http.Error(w, "Internal server error", 500) return } if expired { ctx.Log.Println("The tag has expired. Aborting.") http.Error(w, "This tag has expired.", 410) return } // Extract the filename from the request fname := r.Header.Get("filename") if fname == "" { ctx.Log.Println("Filename generated: " + f.Checksum) f.SetFilename(f.Checksum) } else { ctx.Log.Println("Filename specified: " + fname) err = f.SetFilename(fname) if err != nil { ctx.Log.Println(err) http.Error(w, "Invalid filename specified. It contains illegal characters or is too short.", http.StatusBadRequest) return } } if fname != f.Filename { ctx.Log.Println("Filename sanitized: " + f.Filename) } err = f.DetectMIME() if err != nil { ctx.Log.Println("Unable to detect MIME: ", err) } else { ctx.Log.Println("MIME detected: " + f.MIME) } ctx.Log.Println("Media type: " + f.MediaType()) if f.MediaType() == "image" { err = f.ParseExif() if err != nil { ctx.Log.Println(err) } if exif.IsCriticalError(err) == false { err = f.ExtractDateTime() if err != nil { ctx.Log.Println(err) } } // iOS devices provide only one filename even when uploading // multiple images. Providing some workaround for this below. // XXX: Refactoring needed. if isWorkaroundNeeded(f.UserAgent) && !f.DateTime.IsZero() { var fname string dt := f.DateTime.Format("060102-150405") // List of filenames to modify if f.Filename == "image.jpeg" { fname = "img-" + dt + ".jpeg" } if f.Filename == "image.gif" { fname = "img-" + dt + ".gif" } if f.Filename == "image.png" { fname = "img-" + dt + ".png" } if fname != "" { ctx.Log.Println("Filename workaround triggered") ctx.Log.Println("Filename modified: " + fname) err = f.SetFilename(fname) if err != nil { ctx.Log.Println(err) } } } //err = f.GenerateThumbnail() //if err != nil { // ctx.Log.Println(err) //} extra := make(map[string]string) if !f.DateTime.IsZero() { extra["DateTime"] = f.DateTime.String() } f.Extra = extra } // Promote file from tempdir to the published tagdir f.Publish() // Clean up by removing the tempfile f.ClearTemp() err = f.StatInfo() if err != nil { ctx.Log.Println(err) http.Error(w, "Internal Server Error", 500) return } f.GenerateLinks(cfg.Baseurl) f.CreatedAt = time.Now().UTC() //f.ExpiresAt = time.Now().UTC().Add(24 * 7 * 4 * time.Hour) if cfg.TriggerUploadedFile != "" { ctx.Log.Println("Executing trigger: Uploaded file") triggerUploadedFileHandler(cfg.TriggerUploadedFile, f.Tag, f.Filename) } ctx.WorkQueue <- f headers := make(map[string]string) headers["Content-Type"] = "application/json" var status = 201 output.JSONresponse(w, status, headers, f, ctx) }
func FetchTag(w http.ResponseWriter, r *http.Request, cfg config.Configuration, ctx model.Context) { var err error params := mux.Vars(r) t := model.Tag{} err = t.SetTag(params["tag"]) if err != nil { ctx.Log.Println(err) http.Error(w, "Invalid tag", 400) return } t.SetTagDir(cfg.Filedir) t.CalculateExpiration(cfg.Expiration) if t.TagDirExists() { expired, err := t.IsExpired(cfg.Expiration) if err != nil { ctx.Log.Println(err) http.Error(w, "Internal server error", 500) return } if expired { ctx.Log.Println("Expired: " + t.ExpirationReadable) http.Error(w, "This tag has expired.", 410) return } err = t.StatInfo() if err != nil { ctx.Log.Println(err) http.Error(w, "Internal Server Error", 500) return } err = t.List(cfg.Baseurl) if err != nil { ctx.Log.Println(err) http.Error(w, "Error reading the tag contents.", 404) return } } //t.GenerateLinks(cfg.Baseurl) headers := make(map[string]string) headers["Cache-Control"] = "max-age=1" var status = 200 if r.Header.Get("Content-Type") == "application/zip" || r.FormValue("o") == "zip" { headers["Content-Type"] = "application/zip" // Generate a map of paths to add to the zip response var paths []string for _, f := range t.Files { path := filepath.Join(f.TagDir, f.Filename) paths = append(paths, path) } output.ZIPresponse(w, status, t.Tag, headers, paths, ctx) return } if r.Header.Get("Content-Type") == "application/json" { headers["Content-Type"] = "application/json" output.JSONresponse(w, status, headers, t, ctx) return } else { output.HTMLresponse(w, "viewtag", status, headers, t, ctx) return } }
func DeleteFile(w http.ResponseWriter, r *http.Request, cfg config.Configuration, ctx model.Context) { var err error params := mux.Vars(r) f := model.File{} f.SetFilename(params["filename"]) if err != nil { ctx.Log.Println(err) http.Error(w, "Invalid filename specified. It contains illegal characters or is too short.", 400) return } err = f.SetTag(params["tag"]) if err != nil { ctx.Log.Println(err) http.Error(w, "Invalid tag specified. It contains illegal characters or is too short.", 400) return } f.SetTagDir(cfg.Filedir) if f.Exists() == false { ctx.Log.Println("The file does not exist.") http.Error(w, "File Not Found", 404) return } t := model.Tag{} t.SetTag(f.Tag) t.SetTagDir(cfg.Filedir) t.CalculateExpiration(cfg.Expiration) expired, err := t.IsExpired(cfg.Expiration) if err != nil { ctx.Log.Println(err) http.Error(w, "Internal server error", 500) return } if expired { ctx.Log.Println("Expired: " + t.ExpirationReadable) http.Error(w, "This tag has expired.", 410) return } f.GenerateLinks(cfg.Baseurl) err = f.DetectMIME() if err != nil { ctx.Log.Println("Unable to detect MIME: ", err) } err = f.StatInfo() if err != nil { ctx.Log.Println(err) http.Error(w, "Internal Server Error", 500) return } err = f.Remove() if err != nil { ctx.Log.Println("Unable to remove file: ", err) http.Error(w, "Internal Server Error", 500) return } headers := make(map[string]string) headers["Content-Type"] = "application/json" var status = 200 output.JSONresponse(w, status, headers, f, ctx) return }
func AdminDashboard(w http.ResponseWriter, r *http.Request, cfg config.Configuration, ctx model.Context) { w.Header().Set("Vary", "Content-Type") w.Header().Set("Cache-Control", "s-maxage=0, max-age=0") var status = 200 eventsInProgress := ctx.Events.GetEventsInProgress(0, 0) event := ctx.Events.New(ctx.RemoteAddr, []string{"admin", "dashboard"}, "", "") event.Update(r.Header.Get("user-agent"), 0) defer event.Done() logins := ctx.Events.GetEventsByTags([]string{"admin"}, 0, 3) bins := ctx.Backend.GetBinsMetaData() stats := ctx.Metrics.GetStats() // Detect time limit for showing recent events limitTime := time.Now().UTC().Add(-48 * time.Hour) if len(logins) >= 2 { limitTime = logins[1].StartTime() } var recentUploads []events.Event uploads := ctx.Events.GetEventsByTags([]string{"upload"}, 0, 0) for _, f := range uploads { if f.StartTime().After(limitTime) { if f.IsDone() && f.Status() == 0 { recentUploads = append(recentUploads, f) } } } var recentEvents []events.Event allEvents := ctx.Events.GetAllEvents(1, 0) for _, e := range allEvents { if e.StartTime().After(limitTime) { recentEvents = append(recentEvents, e) } } type Out struct { Bins []fs.Bin BinsReadable string Events []events.Event EventsInProgress []events.Event Uploads []events.Event Files int FilesReadable string Bytes int64 BytesReadable string Stats map[string]int64 Logins []events.Event Uptime time.Duration UptimeReadable string Now time.Time } var files int var bytes int64 for _, b := range bins { files = files + len(b.Files) bytes = bytes + b.Bytes } data := Out{ Bins: bins, Events: recentEvents, EventsInProgress: eventsInProgress, Uploads: recentUploads, Files: files, Bytes: bytes, BytesReadable: humanize.Bytes(uint64(bytes)), BinsReadable: humanize.Comma(int64(len(bins))), FilesReadable: humanize.Comma(int64(files)), Stats: stats, Logins: logins, Uptime: ctx.Metrics.Uptime(), UptimeReadable: humanize.Time(ctx.Metrics.StartTime()), Now: time.Now().UTC(), } if r.Header.Get("Accept") == "application/json" { w.Header().Set("Content-Type", "application/json") output.JSONresponse(w, status, data, ctx) } else { output.HTMLresponse(w, "dashboard", status, data, ctx) } return }
func FetchFile(w http.ResponseWriter, r *http.Request, cfg config.Configuration, ctx model.Context) { w.Header().Set("Cache-Control", "s-maxage=30") w.Header().Set("Vary", "Content-Type") // Query parameters u, err := url.Parse(r.RequestURI) if err != nil { ctx.Log.Println(err) } queryParams, err := url.ParseQuery(u.RawQuery) if err != nil { ctx.Log.Println(err) } params := mux.Vars(r) bin := params["bin"] if err := verifyBin(bin); err != nil { http.Error(w, "Invalid bin", 400) return } b, err := ctx.Backend.GetBinMetaData(bin) if err != nil { ctx.Log.Println(err) http.Error(w, "Not found", 404) return } if b.Expired { http.Error(w, "This bin expired "+b.ExpiresReadable+".", 410) return } filename := params["filename"] if err := verifyFilename(filename); err != nil { http.Error(w, "Invalid filename", 400) return } f, err := ctx.Backend.GetFileMetaData(bin, filename) if err != nil { ctx.Log.Println(err) http.Error(w, "Not found", 404) return } if r.Header.Get("Accept") == "application/json" { w.Header().Set("Content-Type", "application/json") output.JSONresponse(w, 200, f, ctx) return } width, _ := strconv.Atoi(queryParams.Get("width")) height, _ := strconv.Atoi(queryParams.Get("height")) if (width > 0) || (height > 0) { fp, err := ctx.Backend.GetThumbnail(bin, filename, width, height) if err != nil { ctx.Log.Println(err) http.Error(w, "Image not found", 404) return } ctx.Metrics.Incr("total-thumbnails-viewed") http.ServeContent(w, r, f.Filename, f.CreatedAt, fp) return } event := ctx.Events.New(ctx.RemoteAddr, []string{"file", "download"}, bin, filename) defer event.Done() event.Update(humanize.Bytes(uint64(f.Bytes)), 0) ctx.Metrics.Incr("total-file-download") ctx.Metrics.Incr("current-file-download") defer ctx.Metrics.Decr("current-file-download") ctx.Metrics.Incr("file-download bin=" + bin + " filename=" + filename + " referer=" + r.Header.Get("Referer")) fp, err := ctx.Backend.GetFile(bin, filename) if err != nil { ctx.Log.Println(err) event.Update(err.Error(), 2) http.Error(w, "Not found", 404) return } w.Header().Set("Content-SHA256", f.Checksum) if cfg.TriggerDownloadFile != "" { ctx.Log.Println("Executing trigger: Download file") triggerDownloadFileHandler(cfg.TriggerDownloadFile, bin, filename) } http.ServeContent(w, r, f.Filename, f.CreatedAt, fp) }
func Upload(w http.ResponseWriter, r *http.Request, cfg config.Configuration, ctx model.Context) { r.Close = true bin := r.Header.Get("bin") if err := verifyBin(bin); err != nil { http.Error(w, "Invalid bin", 400) return } b, err := ctx.Backend.GetBinMetaData(bin) if err == nil { if b.Expired { http.Error(w, "This bin expired "+b.ExpiresReadable+".", 410) return } } filename := sanitizeFilename(r.Header.Get("filename")) if err := verifyFilename(filename); err != nil { http.Error(w, "Invalid filename", 400) return } ctx.Metrics.Incr("current-upload") defer ctx.Metrics.Decr("current-upload") event := ctx.Events.New(ctx.RemoteAddr, []string{"file", "upload"}, bin, filename) defer event.Done() if i, err := strconv.Atoi(r.Header.Get("content-length")); err == nil { event.Update("Size: "+humanize.Bytes(uint64(i)), 0) } if ctx.Backend.BinExists(bin) == false { if cfg.TriggerNewBin != "" { ctx.Log.Println("Executing trigger: New bin") triggerNewBinHandler(cfg.TriggerNewBin, bin) } } f, err := ctx.Backend.UploadFile(bin, filename, r.Body) if err != nil { ctx.Log.Println(err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) event.Update(err.Error(), 2) return } ctx.Metrics.Incr("total-upload") if cfg.TriggerUploadFile != "" { ctx.Log.Println("Executing trigger: Uploaded file") triggerUploadFileHandler(cfg.TriggerUploadFile, f.Bin, f.Filename) } // Purging any old content if cfg.CacheInvalidation { for _, l := range f.Links { if err := shared.PurgeURL(l.Href, ctx.Log); err != nil { ctx.Log.Println(err) } } } j := model.Job{} j.Filename = f.Filename j.Bin = f.Bin j.Log = ctx.Log j.Cfg = &cfg ctx.WorkQueue <- j w.Header().Set("Content-Type", "application/json") var status = 201 output.JSONresponse(w, status, f, ctx) }