| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "mime" |
| 6 | "net/http" |
| 7 | "net/url" |
| 8 | "path" |
| 9 | "strings" |
| 10 | |
| 11 | "github.com/stevedylandev/andromeda/pkg/web" |
| 12 | ) |
| 13 | |
| 14 | func (a *App) upload(w http.ResponseWriter, r *http.Request) { |
| 15 | bucket := r.PathValue("bucket") |
| 16 | r.Body = http.MaxBytesReader(w, r.Body, a.MaxUploadBytes+1<<20) |
| 17 | if err := r.ParseMultipartForm(a.MaxUploadBytes); err != nil { |
| 18 | web.RedirectWithError(w, r, browseHref(bucket, ""), "Upload too large or malformed") |
| 19 | return |
| 20 | } |
| 21 | prefix := strings.TrimSpace(r.FormValue("prefix")) |
| 22 | if prefix != "" && !strings.HasSuffix(prefix, "/") { |
| 23 | prefix += "/" |
| 24 | } |
| 25 | target := browseHref(bucket, prefix) |
| 26 | |
| 27 | files := r.MultipartForm.File["file"] |
| 28 | if len(files) == 0 { |
| 29 | web.RedirectWithError(w, r, target, "No file provided") |
| 30 | return |
| 31 | } |
| 32 | for _, header := range files { |
| 33 | if header.Size > a.MaxUploadBytes { |
| 34 | web.RedirectWithError(w, r, target, "File exceeds upload limit") |
| 35 | return |
| 36 | } |
| 37 | f, err := header.Open() |
| 38 | if err != nil { |
| 39 | web.RedirectWithError(w, r, target, "Failed to read upload") |
| 40 | return |
| 41 | } |
| 42 | name := path.Base(header.Filename) |
| 43 | if name == "" || name == "." || name == "/" { |
| 44 | f.Close() |
| 45 | web.RedirectWithError(w, r, target, "Invalid filename") |
| 46 | return |
| 47 | } |
| 48 | ct := header.Header.Get("Content-Type") |
| 49 | if ct == "" { |
| 50 | ct = mime.TypeByExtension(path.Ext(name)) |
| 51 | } |
| 52 | if ct == "" { |
| 53 | ct = "application/octet-stream" |
| 54 | } |
| 55 | if err := a.S3.Put(r.Context(), bucket, prefix+name, ct, f, header.Size); err != nil { |
| 56 | f.Close() |
| 57 | a.Log.Error("put", "bucket", bucket, "key", prefix+name, "err", err) |
| 58 | web.RedirectWithError(w, r, target, "Upload failed: "+err.Error()) |
| 59 | return |
| 60 | } |
| 61 | f.Close() |
| 62 | } |
| 63 | web.RedirectWithSuccess(w, r, target, "Uploaded") |
| 64 | } |
| 65 | |
| 66 | func (a *App) replace(w http.ResponseWriter, r *http.Request) { |
| 67 | bucket := r.PathValue("bucket") |
| 68 | r.Body = http.MaxBytesReader(w, r.Body, a.MaxUploadBytes+1<<20) |
| 69 | if err := r.ParseMultipartForm(a.MaxUploadBytes); err != nil { |
| 70 | web.RedirectWithError(w, r, browseHref(bucket, ""), "Upload too large or malformed") |
| 71 | return |
| 72 | } |
| 73 | key := strings.TrimSpace(r.FormValue("key")) |
| 74 | if key == "" { |
| 75 | http.Error(w, "key required", http.StatusBadRequest) |
| 76 | return |
| 77 | } |
| 78 | target := "/b/" + url.PathEscape(bucket) + "/object/" + escapeKeyPath(key) |
| 79 | f, header, err := r.FormFile("file") |
| 80 | if err != nil { |
| 81 | web.RedirectWithError(w, r, target, "No file provided") |
| 82 | return |
| 83 | } |
| 84 | defer f.Close() |
| 85 | if header.Size > a.MaxUploadBytes { |
| 86 | web.RedirectWithError(w, r, target, "File exceeds upload limit") |
| 87 | return |
| 88 | } |
| 89 | ct := header.Header.Get("Content-Type") |
| 90 | if ct == "" { |
| 91 | ct = mime.TypeByExtension(path.Ext(key)) |
| 92 | } |
| 93 | if ct == "" { |
| 94 | ct = "application/octet-stream" |
| 95 | } |
| 96 | if err := a.S3.Put(r.Context(), bucket, key, ct, f, header.Size); err != nil { |
| 97 | a.Log.Error("put replace", "bucket", bucket, "key", key, "err", err) |
| 98 | web.RedirectWithError(w, r, target, "Replace failed: "+err.Error()) |
| 99 | return |
| 100 | } |
| 101 | web.RedirectWithSuccess(w, r, target, "Replaced") |
| 102 | } |
| 103 | |
| 104 | func (a *App) deleteObject(w http.ResponseWriter, r *http.Request) { |
| 105 | bucket := r.PathValue("bucket") |
| 106 | if err := r.ParseForm(); err != nil { |
| 107 | http.Error(w, "bad request", http.StatusBadRequest) |
| 108 | return |
| 109 | } |
| 110 | key := strings.TrimSpace(r.FormValue("key")) |
| 111 | if key == "" { |
| 112 | http.Error(w, "key required", http.StatusBadRequest) |
| 113 | return |
| 114 | } |
| 115 | returnTo := strings.TrimSpace(r.FormValue("returnTo")) |
| 116 | if returnTo == "" { |
| 117 | returnTo = browseHref(bucket, parentOfKey(strings.TrimSuffix(key, "/"))) |
| 118 | } |
| 119 | if err := a.S3.Delete(r.Context(), bucket, key); err != nil { |
| 120 | a.Log.Error("delete", "bucket", bucket, "key", key, "err", err) |
| 121 | web.RedirectWithError(w, r, returnTo, "Delete failed: "+err.Error()) |
| 122 | return |
| 123 | } |
| 124 | web.RedirectWithSuccess(w, r, returnTo, "Deleted") |
| 125 | } |
| 126 | |
| 127 | func (a *App) mkdir(w http.ResponseWriter, r *http.Request) { |
| 128 | bucket := r.PathValue("bucket") |
| 129 | if err := r.ParseForm(); err != nil { |
| 130 | http.Error(w, "bad request", http.StatusBadRequest) |
| 131 | return |
| 132 | } |
| 133 | prefix := strings.TrimSpace(r.FormValue("prefix")) |
| 134 | if prefix != "" && !strings.HasSuffix(prefix, "/") { |
| 135 | prefix += "/" |
| 136 | } |
| 137 | name := strings.TrimSpace(r.FormValue("name")) |
| 138 | name = strings.Trim(name, "/") |
| 139 | if name == "" || strings.HasPrefix(name, ".") || strings.Contains(name, "/") { |
| 140 | web.RedirectWithError(w, r, browseHref(bucket, prefix), "Invalid folder name") |
| 141 | return |
| 142 | } |
| 143 | newPrefix := prefix + name + "/" |
| 144 | if err := a.S3.Put(r.Context(), bucket, newPrefix, "application/x-directory", bytes.NewReader(nil), 0); err != nil { |
| 145 | a.Log.Error("mkdir", "bucket", bucket, "key", newPrefix, "err", err) |
| 146 | web.RedirectWithError(w, r, browseHref(bucket, prefix), "Create folder failed: "+err.Error()) |
| 147 | return |
| 148 | } |
| 149 | web.RedirectWithSuccess(w, r, browseHref(bucket, newPrefix), "Folder created") |
| 150 | } |