diff --git i/fileupload/fileupload.go w/fileupload/fileupload.go index 07fccf1..55104f8 100644 --- i/fileupload/fileupload.go +++ w/fileupload/fileupload.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "io" + "log" "mime" "net/http" "net/url" @@ -153,30 +154,35 @@ func (h *Handler) ServeHTTP(resp http.ResponseWriter, req *http.Request) { return } + var err error switch req.Method { case http.MethodOptions: resp.WriteHeader(http.StatusNoContent) case http.MethodHead, http.MethodGet: - h.fetch(resp, req) + err = h.fetch(resp, req) case http.MethodPost: - h.store(resp, req) + err = h.store(resp, req) default: http.Error(resp, "only OPTIONS, HEAD, GET and POST are allowed", http.StatusMethodNotAllowed) + err = errors.New(http.StatusText(http.StatusMethodNotAllowed)) + } + if err != nil { + log.Printf("upload: %s", err.Error()) } } -func (h *Handler) fetch(resp http.ResponseWriter, req *http.Request) { +func (h *Handler) fetch(resp http.ResponseWriter, req *http.Request) error { prefix := "/uploads/" if !strings.HasPrefix(req.URL.Path, prefix) { http.Error(resp, "invalid path", http.StatusNotFound) - return + return fmt.Errorf("invalid path") } filename := strings.TrimPrefix(req.URL.Path, prefix) filename = path.Join("/", filename)[1:] // prevent directory traversal if filename == "" { http.Error(resp, "invalid path", http.StatusNotFound) - return + return fmt.Errorf("invalid path") } basename, modTime, content, err := h.Uploader.load(req.Context(), filename) @@ -184,10 +190,10 @@ func (h *Handler) fetch(resp http.ResponseWriter, req *http.Request) { var httpErr *httpError if errors.As(err, &httpErr) { httpErr.Write(resp) - return + return err } http.Error(resp, "failed to open file", http.StatusNotFound) - return + return fmt.Errorf("failed to open file: %v", err) } defer content.Close() @@ -200,7 +206,7 @@ func (h *Handler) fetch(resp http.ResponseWriter, req *http.Request) { _, err := content.Seek(0, io.SeekStart) // rewind to output whole file if err != nil { http.Error(resp, "failed to seek file", http.StatusInternalServerError) - return + return fmt.Errorf("failed to seek file: %v", err) } } @@ -219,18 +225,20 @@ func (h *Handler) fetch(resp http.ResponseWriter, req *http.Request) { resp.Header().Set("Content-Disposition", contentDisp) http.ServeContent(resp, req, basename, modTime, content) + + return nil } -func (h *Handler) store(resp http.ResponseWriter, req *http.Request) { +func (h *Handler) store(resp http.ResponseWriter, req *http.Request) error { if req.URL.Path != "/uploads" { http.Error(resp, "invalid path", http.StatusNotFound) - return + return fmt.Errorf("invalid path") } authz := req.Header.Get("Authorization") if authz == "" { http.Error(resp, "missing Authorization header", http.StatusUnauthorized) - return + return fmt.Errorf("missing Authorization header") } var ( @@ -243,26 +251,26 @@ func (h *Handler) store(resp http.ResponseWriter, req *http.Request) { plainAuth := h.Auth.Plain if plainAuth == nil { http.Error(resp, "Basic scheme in Authorization header not supported", http.StatusBadRequest) - return + return fmt.Errorf("Basic scheme in Authorization header not supported") } var password string var ok bool username, password, ok = req.BasicAuth() if !ok { http.Error(resp, "invalid Authorization header", http.StatusBadRequest) - return + return fmt.Errorf("invalid Authorization header") } err = plainAuth.AuthPlain(req.Context(), h.DB, username, password) case "bearer": oauthAuth := h.Auth.OAuthBearer if oauthAuth == nil { http.Error(resp, "Bearer scheme in Authorization header not supported", http.StatusBadRequest) - return + return fmt.Errorf("Bearer scheme in Authorization header not supported") } username, err = oauthAuth.AuthOAuthBearer(req.Context(), h.DB, param) default: http.Error(resp, "unsupported Authorization header scheme", http.StatusBadRequest) - return + return fmt.Errorf("unsupported Authorization header scheme") } if err != nil { var msg string @@ -272,7 +280,7 @@ func (h *Handler) store(resp http.ResponseWriter, req *http.Request) { msg = "authentication failed" } http.Error(resp, msg, http.StatusForbidden) - return + return fmt.Errorf("%s: %v", msg, err) } var mimeType string @@ -284,7 +292,7 @@ func (h *Handler) store(resp http.ResponseWriter, req *http.Request) { mimeType, params, err = mime.ParseMediaType(contentType) if err != nil { http.Error(resp, "failed to parse Content-Type", http.StatusBadRequest) - return + return fmt.Errorf("failed to parse Content-Type: %v", err) } if mimeType == "application/octet-stream" { mimeType = "" @@ -295,7 +303,7 @@ func (h *Handler) store(resp http.ResponseWriter, req *http.Request) { // OK default: http.Error(resp, "unsupported charset", http.StatusUnsupportedMediaType) - return + return fmt.Errorf("unsupported charset") } } @@ -304,7 +312,7 @@ func (h *Handler) store(resp http.ResponseWriter, req *http.Request) { _, params, err := mime.ParseMediaType(contentDisp) if err != nil { http.Error(resp, "failed to parse Content-Disposition", http.StatusBadRequest) - return + return fmt.Errorf("failed to parse Content-Disposition: %v", err) } basename = path.Base(params["filename"]) } @@ -325,7 +333,7 @@ func (h *Handler) store(resp http.ResponseWriter, req *http.Request) { var httpErr *httpError if errors.As(err, &httpErr) { httpErr.Write(resp) - return + return err } status := http.StatusInternalServerError var maxBytesErr *http.MaxBytesError @@ -335,19 +343,21 @@ func (h *Handler) store(resp http.ResponseWriter, req *http.Request) { resp.Header().Set("Upload-Limit", fmt.Sprintf("maxsize=%v", maxBytesErr.Limit)) } http.Error(resp, "failed to write file", status) - return + return fmt.Errorf("failed to write file: %v", err) } u, err := url.Parse(out) if err != nil { http.Error(resp, "failed to write file", http.StatusInternalServerError) - return + return fmt.Errorf("failed to write file: %v", err) } - baseURL := url.URL{Path: "/uploads/"} + baseURL := url.URL{Path: "/_irc/uploads/"} out = baseURL.ResolveReference(u).String() resp.Header().Set("Location", out) resp.WriteHeader(http.StatusCreated) + + return nil } func generateToken(n int) (string, error) { diff --git i/fileupload/fs.go w/fileupload/fs.go index c0c401d..66337a1 100644 --- i/fileupload/fs.go +++ w/fileupload/fs.go @@ -56,7 +56,7 @@ func (fs *fileuploadFS) store(ctx context.Context, r io.Reader, username, mimeTy } dir := filepath.Join(fs.dir, username) - if err := os.MkdirAll(dir, 0700); err != nil { + if err := os.MkdirAll(dir, 0755); err != nil { return "", fmt.Errorf("failed to create user upload directory: %w", err) } @@ -77,7 +77,7 @@ func (fs *fileuploadFS) store(ctx context.Context, r io.Reader, username, mimeTy } basename += suffix - f, err = os.OpenFile(filepath.Join(dir, basename), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600) + f, err = os.OpenFile(filepath.Join(dir, basename), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644) if err == nil { break } else if !os.IsExist(err) {