diff --git a/handlers-s3.go b/handlers-s3.go index 6d30fa5..b81a8e6 100644 --- a/handlers-s3.go +++ b/handlers-s3.go @@ -71,6 +71,12 @@ func handleCreateMultipartUpload(w http.ResponseWriter, req *http.Request) { // Derive the object key key := formatKey(cred.Prefix, r.Filename) + // Ensure that the file does not exist + err = headObject(key, cred) + if !errors.Is(err, errNotFound) { + errorResponse(w, req, fmt.Errorf("%w: the provided key exists", errConflict)) + } + result, err := initiateMultipartUpload(key, cred) if err != nil { errorResponse(w, req, err) @@ -104,7 +110,7 @@ func handleGetUploadedParts(w http.ResponseWriter, req *http.Request) { return } - parts := make(getUploadedPartsRes, 0, 0) + parts := make(getUploadedPartsRes, 0) var nextPartNumberMarker uint32 for { page, err := listParts(key, uploadID, cred, nextPartNumberMarker) diff --git a/handlers.go b/handlers.go index 5c1d5be..c2e528a 100644 --- a/handlers.go +++ b/handlers.go @@ -148,3 +148,9 @@ func handleCreateForm(w http.ResponseWriter, req *http.Request) { w.Write([]byte(id)) } + +/* help template */ + +func handleHelp(w http.ResponseWriter, req *http.Request) { + executeTemplate(w, "help.tmpl", nil) +} diff --git a/helpers.go b/helpers.go index 250a36f..a668681 100644 --- a/helpers.go +++ b/helpers.go @@ -10,6 +10,7 @@ var errBadRequest = errors.New("bad request") var errInternalServerError = errors.New("internal server error") var errUnauthorized = errors.New("unauthorized") var errForbidden = errors.New("forbidden") +var errConflict = errors.New("conflict") func errorResponseStatus(w http.ResponseWriter, req *http.Request, err error) { errorStatus := http.StatusInternalServerError @@ -24,6 +25,8 @@ func errorResponseStatus(w http.ResponseWriter, req *http.Request, err error) { errorStatus = http.StatusUnauthorized } else if errors.Is(err, errForbidden) { errorStatus = http.StatusForbidden + } else if errors.Is(err, errConflict) { + errorStatus = http.StatusConflict } w.WriteHeader(errorStatus) @@ -50,6 +53,8 @@ func responseToError(resp *http.Response) error { return errUnauthorized } else if resp.StatusCode == http.StatusForbidden { return errForbidden + } else if resp.StatusCode == http.StatusConflict { + return errConflict } return nil } diff --git a/main.go b/main.go index f79b803..b1c6f2f 100644 --- a/main.go +++ b/main.go @@ -28,6 +28,7 @@ func main() { router.Methods(http.MethodGet).Path("/").HandlerFunc(handleCreate) router.Methods(http.MethodPost).Path("/").HandlerFunc(handleCreateForm) + router.Methods(http.MethodGet).Path("/help").HandlerFunc(handleHelp) uploadRouter := router.PathPrefix("/{id}").Subrouter() uploadTemplateRouter := uploadRouter.Path("").Subrouter() diff --git a/s3.go b/s3.go index 9f2d6af..621004d 100644 --- a/s3.go +++ b/s3.go @@ -307,3 +307,34 @@ func abortMultipartUpload( return nil } + +/* headObject */ + +func headObject( + key string, + cred credential, +) error { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + unsignedReq, err := http.NewRequestWithContext(ctx, http.MethodHead, cred.Endpoint+"/"+key, nil) + if err != nil { + log.Printf("failure creating request: %v", err) + return err + } + + signedReq := sign(unsignedReq, cred) + resp, err := httpClientS3.Do(signedReq) + if err != nil { + log.Printf("failure connecting to endpoint: %v", err) + return err + } + defer resp.Body.Close() + err = endpointReturnedError(resp) + if err != nil { + log.Printf("endpoint responded negatively: %v", err) + return err + } + + return nil +} diff --git a/web/src/upload.js b/web/src/upload.js index e021951..1c8371e 100644 --- a/web/src/upload.js +++ b/web/src/upload.js @@ -14,6 +14,7 @@ uploadAreas.forEach(uploadArea => { const logClearBtn = uploadArea.querySelector('.log-clear') const dropArea = uploadArea.querySelector('.drop-area'); const statusArea = uploadArea.querySelector('.status-area'); + const noticeArea = uploadArea.querySelector('.notice-area'); /* Components */ @@ -25,6 +26,20 @@ uploadAreas.forEach(uploadArea => { log.clear(); }); + /* Error */ + + function showError(error='') { + let message = error.message || error.toString(); + if (message !== '') { + noticeArea.classList.remove('hidden'); + noticeArea.innerText = message; + } else { + noticeArea.classList.add('hidden'); + noticeArea.innerText = message; + } + window.scrollTo({ top: 0 }); + } + /* Uppy */ const uppy = new Uppy({ @@ -42,7 +57,19 @@ uploadAreas.forEach(uploadArea => { companionUrl: window.location.pathname, }); + uppy.on('upload-error', (f, error, res) => { + window.e = { f, error, res }; + if (error.message.contains('status: 409')) { + showError('A file with the same name already exists. Rename your file and try again'); + return; + } + showError(error); + }); + uppy.on('upload-retry', (id) => { + showError(); + }); uppy.on('upload-success', (f, res) => { + showError(); log.add({ name: f.name, size: f.size,