[PATCH soju v3] Add CORS headers in fileUploadHandler
Export this patch
---
This also allows the content-disposition header, unfortunately '*'
doesnt seem to work for CORS headers when the authorization
header is set.
cmd/soju/main.go | 7 +++ --
fileupload/fileupload.go | 66 ++++++++++++++++++++++++++++++++++++++ --
2 files changed, 67 insertions(+), 6 deletions(-)
diff --git a/cmd/soju/main.go b/cmd/soju/main.go
index 7dfed44..04de78b 100644
--- a/cmd/soju/main.go
+++ b/cmd/soju/main.go
@@ -159,9 +159,10 @@ func main() {
fileUploadHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cfg := srv.Config()
h := fileupload.Handler{
- Uploader: cfg.FileUploader,
- DB: db,
- Auth: cfg.Auth,
+ Uploader: cfg.FileUploader,
+ DB: db,
+ Auth: cfg.Auth,
+ HTTPOrigins: cfg.HTTPOrigins,
}
h.ServeHTTP(w, r)
})
diff --git a/fileupload/fileupload.go b/fileupload/fileupload.go
index cceb4a5..e2f3846 100644
--- a/fileupload/fileupload.go
+++ b/fileupload/fileupload.go
@@ -3,11 +3,14 @@ package fileupload
import (
"crypto/rand"
"encoding/hex"
+ "errors"
"fmt"
"io"
"mime"
"net/http"
+ "net/url"
"path"
+ "path/filepath"
"strings"
"time"
@@ -53,14 +56,71 @@ func New(driver, source string) (Uploader, error) {
}
type Handler struct {
- Uploader Uploader
- Auth auth.Authenticator
- DB database.Database
+ Uploader Uploader
+ Auth auth.Authenticator
+ DB database.Database
+ HTTPOrigins []string
+ }
+
+ func (h *Handler) handleCors(resp http.ResponseWriter, req *http.Request) error {
+ resp.Header().Add("Access-Control-Allow-Credentials", "true")
+ resp.Header().Add("Access-Control-Allow-Headers", "authorization,content-type,content-disposition")
+ resp.Header().Add("Access-Control-Expose-Headers", "Location")
+
+ if len(h.HTTPOrigins) == 0 {
+ return nil
+ }
+
+ reqOrigin := req.Header.Get("Origin")
+ if reqOrigin == "" {
+ return nil
+ }
+ u, err := url.Parse(reqOrigin)
+ if err != nil {
+ http.Error(resp, "Unauthorized", http.StatusUnauthorized)
+ return fmt.Errorf("invald Origin header: %w", err)
+ }
+ if u.Host == req.Host {
+ resp.Header().Add("Access-Control-Allow-Origin", reqOrigin)
+ return nil
+ }
+
+ for _, origin := range h.HTTPOrigins {
+ if origin == "*" {
+ resp.Header().Add("Access-Control-Allow-Origin", reqOrigin)
+ resp.Header().Add("Vary", "Origin")
+ return nil
+ }
+
+ if strings.EqualFold(origin, reqOrigin) {
+ resp.Header().Add("Access-Control-Allow-Origin", origin)
+ return nil
+ }
+
+ match, err := filepath.Match(origin, reqOrigin)
+ if err != nil {
+ http.Error(resp, "Internal error", http.StatusInternalServerError)
+ return err
+ }
+ if match {
+ resp.Header().Add("Access-Control-Allow-Origin", reqOrigin)
+ resp.Header().Add("Vary", "Origin")
+ return nil
+ }
+ }
+
+ http.Error(resp, "Unauthorized Origin", http.StatusUnauthorized)
+
+ return errors.New("unauthorized origin")
}
func (h *Handler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
resp.Header().Set("Content-Security-Policy", "sandbox; default-src 'none'; script-src 'none';")
+ if err := h.handleCors(resp, req); err != nil {
+ return
+ }
+
if h.Uploader == nil {
http.NotFound(resp, req)
return
--
2.43.0
soju/patches/.build.yml: SUCCESS in 1m2s
[Add CORS headers in fileUploadHandler][0] v3 from [Alex McGrath][1]
[0]: https://lists.sr.ht/~emersion/soju-dev/patches/49321
[1]: mailto:amk@amk.ie
✓ #1146463 SUCCESS soju/patches/.build.yml https://builds.sr.ht/~emersion/job/1146463
Reworked this a bit:
- Dropped the special cases for "*" and exact match which were covered
by Match already. Also dropped the len(h.HTTPOrigins) == 0 special case.
- Add Content-Disposition in Access-Control-Expose-Headers.
- Validate http-origin patterns at config load time, so we don't have to
deal with the ErrBadPattern case here.
- Extract the loop into a separate function.
- Use path.Match instead of filepath.Match since this is not tied to the
OS separator.
- Always set Vary, so that the browser is aware that
Access-Control-Allow-Origin can change if it performs first a
first-party request and then a third-party one.
Pushed, thanks!