diff --git a/avatar/avatar.go b/avatar/avatar.go index ed691fa8..bc8d37bf 100644 --- a/avatar/avatar.go +++ b/avatar/avatar.go @@ -23,6 +23,9 @@ import ( "github.com/go-pkgz/auth/token" ) +// http.sniffLen is 512 bytes which is how much we need to read to detect content type +const sniffLen = 512 + // Proxy provides http handler for avatars from avatar.Store // On user login token will call Put and it will retrieve and save picture locally. type Proxy struct { @@ -100,7 +103,6 @@ func (p *Proxy) load(url string, client *http.Client) (rc io.ReadCloser, err err // Handler returns token routes for given provider func (p *Proxy) Handler(w http.ResponseWriter, r *http.Request) { - if r.Method != "GET" { w.WriteHeader(http.StatusMethodNotAllowed) } @@ -136,9 +138,21 @@ func (p *Proxy) Handler(w http.ResponseWriter, r *http.Request) { } }() - w.Header().Set("Content-Type", "image/*") + buf := make([]byte, sniffLen) + n, err := avReader.Read(buf) + if err != nil && err != io.EOF { + p.Logf("[WARN] can't read from avatar reader for %s, %s", avatarID, err) + rest.SendErrorJSON(w, r, p.L, http.StatusInternalServerError, err, "can't read avatar") + return + } w.Header().Set("Content-Length", strconv.Itoa(size)) + w.Header().Set("Content-Type", http.DetectContentType(buf)) w.WriteHeader(http.StatusOK) + if _, err = w.Write(buf[:n]); err != nil { + p.Logf("[WARN] can't write response to %s, %s", r.RemoteAddr, err) + return + } + // write the rest of response size if it's bigger than 512 bytes, or nothing as EOF would be sent right away then if _, err = io.Copy(w, avReader); err != nil { p.Logf("[WARN] can't send response to %s, %s", r.RemoteAddr, err) } diff --git a/avatar/avatar_test.go b/avatar/avatar_test.go index a5e0bc2b..84883b2d 100644 --- a/avatar/avatar_test.go +++ b/avatar/avatar_test.go @@ -107,9 +107,10 @@ func TestAvatar_Routes(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/pic.png" { - w.Header().Set("Content-Type", "image/*") + // set wrong content type, in reality would be image/png based on the first bytes + w.Header().Set("Content-Type", "image/jpg") w.Header().Set("Custom-Header", "xyz") - _, err := fmt.Fprint(w, "some picture bin data") + _, err := fmt.Fprint(w, "\x89PNG\x0D\x0A\x1A\x0A some picture bin data") require.NoError(t, err) return } @@ -154,16 +155,16 @@ func TestAvatar_Routes(t *testing.T) { handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusOK, rr.Code) - assert.Equal(t, []string{"image/*"}, rr.Header()["Content-Type"]) - assert.Equal(t, []string{"21"}, rr.Header()["Content-Length"]) + assert.Equal(t, []string{"image/png"}, rr.Header()["Content-Type"]) + assert.Equal(t, []string{"30"}, rr.Header()["Content-Length"]) assert.Equal(t, []string(nil), rr.Header()["Custom-Header"], "strip all custom headers") assert.NotNil(t, rr.Header()["Etag"]) bb := bytes.Buffer{} sz, err := io.Copy(&bb, rr.Body) assert.NoError(t, err) - assert.Equal(t, int64(21), sz) - assert.Equal(t, "some picture bin data", bb.String()) + assert.Equal(t, int64(30), sz) + assert.Equal(t, "\x89PNG\x0D\x0A\x1A\x0A some picture bin data", bb.String()) } { diff --git a/v2/avatar/avatar.go b/v2/avatar/avatar.go index 3e9b5c2a..f169f52d 100644 --- a/v2/avatar/avatar.go +++ b/v2/avatar/avatar.go @@ -23,6 +23,9 @@ import ( "github.com/go-pkgz/auth/v2/token" ) +// http.sniffLen is 512 bytes which is how much we need to read to detect content type +const sniffLen = 512 + // Proxy provides http handler for avatars from avatar.Store // On user login token will call Put and it will retrieve and save picture locally. type Proxy struct { @@ -100,7 +103,6 @@ func (p *Proxy) load(url string, client *http.Client) (rc io.ReadCloser, err err // Handler returns token routes for given provider func (p *Proxy) Handler(w http.ResponseWriter, r *http.Request) { - if r.Method != "GET" { w.WriteHeader(http.StatusMethodNotAllowed) } @@ -136,9 +138,21 @@ func (p *Proxy) Handler(w http.ResponseWriter, r *http.Request) { } }() - w.Header().Set("Content-Type", "image/*") + buf := make([]byte, sniffLen) + n, err := avReader.Read(buf) + if err != nil && err != io.EOF { + p.Logf("[WARN] can't read from avatar reader for %s, %s", avatarID, err) + rest.SendErrorJSON(w, r, p.L, http.StatusInternalServerError, err, "can't read avatar") + return + } w.Header().Set("Content-Length", strconv.Itoa(size)) + w.Header().Set("Content-Type", http.DetectContentType(buf)) w.WriteHeader(http.StatusOK) + if _, err = w.Write(buf[:n]); err != nil { + p.Logf("[WARN] can't write response to %s, %s", r.RemoteAddr, err) + return + } + // write the rest of response size if it's bigger than 512 bytes, or nothing as EOF would be sent right away then if _, err = io.Copy(w, avReader); err != nil { p.Logf("[WARN] can't send response to %s, %s", r.RemoteAddr, err) } diff --git a/v2/avatar/avatar_test.go b/v2/avatar/avatar_test.go index 937f8866..99e4571d 100644 --- a/v2/avatar/avatar_test.go +++ b/v2/avatar/avatar_test.go @@ -107,9 +107,10 @@ func TestAvatar_Routes(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/pic.png" { - w.Header().Set("Content-Type", "image/*") + // set wrong content type, in reality would be image/png based on the content of the file + w.Header().Set("Content-Type", "image/jpg") w.Header().Set("Custom-Header", "xyz") - _, err := fmt.Fprint(w, "some picture bin data") + _, err := fmt.Fprint(w, "\x89PNG\x0D\x0A\x1A\x0A some picture bin data") require.NoError(t, err) return } @@ -154,16 +155,16 @@ func TestAvatar_Routes(t *testing.T) { handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusOK, rr.Code) - assert.Equal(t, []string{"image/*"}, rr.Header()["Content-Type"]) - assert.Equal(t, []string{"21"}, rr.Header()["Content-Length"]) + assert.Equal(t, []string{"image/png"}, rr.Header()["Content-Type"]) + assert.Equal(t, []string{"30"}, rr.Header()["Content-Length"]) assert.Equal(t, []string(nil), rr.Header()["Custom-Header"], "strip all custom headers") assert.NotNil(t, rr.Header()["Etag"]) bb := bytes.Buffer{} sz, err := io.Copy(&bb, rr.Body) assert.NoError(t, err) - assert.Equal(t, int64(21), sz) - assert.Equal(t, "some picture bin data", bb.String()) + assert.Equal(t, int64(30), sz) + assert.Equal(t, "\x89PNG\x0D\x0A\x1A\x0A some picture bin data", bb.String()) } {