diff options
author | Nick Thomas <nick@gitlab.com> | 2019-01-11 13:46:51 +0300 |
---|---|---|
committer | Nick Thomas <nick@gitlab.com> | 2019-01-11 13:46:51 +0300 |
commit | f3f841888d45936c6f5d9f5c0306cd1077a94c13 (patch) | |
tree | 92372b22dc25176e738a17a3bc7841c6c20408be | |
parent | fd23b054ec4e40735feae2d5a799aa7611174f54 (diff) | |
parent | bc30f40b50d2aff92c9247137cfaeb245e87dff8 (diff) |
Merge branch 'gzip-content-type' into 'master'
Prevent wrong mimetype being set for GZipped files with unknown file extension
Closes #181
See merge request gitlab-org/gitlab-pages!122
-rw-r--r-- | internal/domain/domain.go | 27 | ||||
-rw-r--r-- | internal/domain/domain_test.go | 65 | ||||
-rw-r--r-- | shared/pages/group/group.test.io/public/image-nogzip.unknown | bin | 0 -> 14 bytes | |||
-rw-r--r-- | shared/pages/group/group.test.io/public/image.unknown | bin | 0 -> 14 bytes | |||
-rw-r--r-- | shared/pages/group/group.test.io/public/image.unknown.gz | bin | 0 -> 46 bytes | |||
-rw-r--r-- | shared/pages/group/group.test.io/public/text-nogzip.unknown | 1 | ||||
-rw-r--r-- | shared/pages/group/group.test.io/public/text.unknown | 1 | ||||
-rw-r--r-- | shared/pages/group/group.test.io/public/text.unknown.gz | bin | 0 -> 37 bytes |
8 files changed, 65 insertions, 29 deletions
diff --git a/internal/domain/domain.go b/internal/domain/domain.go index 2c4f4e29..64311e65 100644 --- a/internal/domain/domain.go +++ b/internal/domain/domain.go @@ -230,6 +230,26 @@ func (d *D) HasProject(r *http.Request) bool { return false } +// Detect file's content-type either by extension or mime-sniffing. +// Implementation is adapted from Golang's `http.serveContent()` +// See https://github.com/golang/go/blob/902fc114272978a40d2e65c2510a18e870077559/src/net/http/fs.go#L194 +func (d *D) detectContentType(path string) (string, error) { + contentType := mime.TypeByExtension(filepath.Ext(path)) + if contentType == "" { + var buf [512]byte + file, err := os.Open(path) + if err != nil { + return "", err + } + defer file.Close() + // Using `io.ReadFull()` because `file.Read()` may be chunked. + // Ignoring errors because we don't care if the 512 bytes cannot be read. + n, _ := io.ReadFull(file, buf[:]) + contentType = http.DetectContentType(buf[:n]) + } + return contentType, nil +} + func (d *D) serveFile(w http.ResponseWriter, r *http.Request, origPath string) error { fullPath := handleGZip(w, r, origPath) @@ -250,7 +270,12 @@ func (d *D) serveFile(w http.ResponseWriter, r *http.Request, origPath string) e w.Header().Set("Expires", time.Now().Add(10*time.Minute).Format(time.RFC1123)) } - // ServeContent sets Content-Type for us + contentType, err := d.detectContentType(origPath) + if err != nil { + return err + } + w.Header().Set("Content-Type", contentType) + http.ServeContent(w, r, origPath, fi.ModTime(), file) return nil } diff --git a/internal/domain/domain_test.go b/internal/domain/domain_test.go index 7544f501..32bf2e3d 100644 --- a/internal/domain/domain_test.go +++ b/internal/domain/domain_test.go @@ -181,7 +181,7 @@ func TestIsHTTPSOnly(t *testing.T) { } } -func testHTTPGzip(t *testing.T, handler http.HandlerFunc, mode, url string, values url.Values, acceptEncoding string, str interface{}, ungzip bool) { +func testHTTPGzip(t *testing.T, handler http.HandlerFunc, mode, url string, values url.Values, acceptEncoding string, str interface{}, contentType string, ungzip bool) { w := httptest.NewRecorder() req, err := http.NewRequest(mode, url+"?"+values.Encode(), nil) require.NoError(t, err) @@ -204,6 +204,8 @@ func testHTTPGzip(t *testing.T, handler http.HandlerFunc, mode, url string, valu } else { assert.Contains(t, w.Body.String(), str) } + + assert.Equal(t, contentType, w.Header().Get("Content-Type")) } func TestGroupServeHTTPGzip(t *testing.T) { @@ -223,43 +225,50 @@ func TestGroupServeHTTPGzip(t *testing.T) { testSet := []struct { mode string // HTTP mode url string // Test URL - params url.Values // Test URL params acceptEncoding string // Accept encoding header body interface{} // Expected body at above URL - ungzip bool // Do we expect the request to require unzip? + contentType string // Expected content-type + ungzip bool // Expect the response to be gzipped? }{ // No gzip encoding requested - {"GET", "http://group.test.io/", nil, "", "main-dir", false}, - {"GET", "http://group.test.io/", nil, "identity", "main-dir", false}, - {"GET", "http://group.test.io/", nil, "gzip; q=0", "main-dir", false}, - // gzip encoding requeste}, - {"GET", "http://group.test.io/", nil, "*", "main-dir", true}, - {"GET", "http://group.test.io/", nil, "identity, gzip", "main-dir", true}, - {"GET", "http://group.test.io/", nil, "gzip", "main-dir", true}, - {"GET", "http://group.test.io/", nil, "gzip; q=1", "main-dir", true}, - {"GET", "http://group.test.io/", nil, "gzip; q=0.9", "main-dir", true}, - {"GET", "http://group.test.io/", nil, "gzip, deflate", "main-dir", true}, - {"GET", "http://group.test.io/", nil, "gzip; q=1, deflate", "main-dir", true}, - {"GET", "http://group.test.io/", nil, "gzip; q=0.9, deflate", "main-dir", true}, + {"GET", "/index.html", "", "main-dir", "text/html; charset=utf-8", false}, + {"GET", "/index.html", "identity", "main-dir", "text/html; charset=utf-8", false}, + {"GET", "/index.html", "gzip; q=0", "main-dir", "text/html; charset=utf-8", false}, + // gzip encoding requested, + {"GET", "/index.html", "*", "main-dir", "text/html; charset=utf-8", true}, + {"GET", "/index.html", "identity, gzip", "main-dir", "text/html; charset=utf-8", true}, + {"GET", "/index.html", "gzip", "main-dir", "text/html; charset=utf-8", true}, + {"GET", "/index.html", "gzip; q=1", "main-dir", "text/html; charset=utf-8", true}, + {"GET", "/index.html", "gzip; q=0.9", "main-dir", "text/html; charset=utf-8", true}, + {"GET", "/index.html", "gzip, deflate", "main-dir", "text/html; charset=utf-8", true}, + {"GET", "/index.html", "gzip; q=1, deflate", "main-dir", "text/html; charset=utf-8", true}, + {"GET", "/index.html", "gzip; q=0.9, deflate", "main-dir", "text/html; charset=utf-8", true}, // gzip encoding requested, but url does not have compressed content on disk - {"GET", "http://group.test.io/project2/", nil, "*", "project2-main", false}, - {"GET", "http://group.test.io/project2/", nil, "identity, gzip", "project2-main", false}, - {"GET", "http://group.test.io/project2/", nil, "gzip", "project2-main", false}, - {"GET", "http://group.test.io/project2/", nil, "gzip; q=1", "project2-main", false}, - {"GET", "http://group.test.io/project2/", nil, "gzip; q=0.9", "project2-main", false}, - {"GET", "http://group.test.io/project2/", nil, "gzip, deflate", "project2-main", false}, - {"GET", "http://group.test.io/project2/", nil, "gzip; q=1, deflate", "project2-main", false}, - {"GET", "http://group.test.io/project2/", nil, "gzip; q=0.9, deflate", "project2-main", false}, + {"GET", "/project2/index.html", "*", "project2-main", "text/html; charset=utf-8", false}, + {"GET", "/project2/index.html", "identity, gzip", "project2-main", "text/html; charset=utf-8", false}, + {"GET", "/project2/index.html", "gzip", "project2-main", "text/html; charset=utf-8", false}, + {"GET", "/project2/index.html", "gzip; q=1", "project2-main", "text/html; charset=utf-8", false}, + {"GET", "/project2/index.html", "gzip; q=0.9", "project2-main", "text/html; charset=utf-8", false}, + {"GET", "/project2/index.html", "gzip, deflate", "project2-main", "text/html; charset=utf-8", false}, + {"GET", "/project2/index.html", "gzip; q=1, deflate", "project2-main", "text/html; charset=utf-8", false}, + {"GET", "/project2/index.html", "gzip; q=0.9, deflate", "project2-main", "text/html; charset=utf-8", false}, // malformed headers - {"GET", "http://group.test.io/", nil, ";; gzip", "main-dir", false}, - {"GET", "http://group.test.io/", nil, "middle-out", "main-dir", false}, - {"GET", "http://group.test.io/", nil, "gzip; quality=1", "main-dir", false}, + {"GET", "/index.html", ";; gzip", "main-dir", "text/html; charset=utf-8", false}, + {"GET", "/index.html", "middle-out", "main-dir", "text/html; charset=utf-8", false}, + {"GET", "/index.html", "gzip; quality=1", "main-dir", "text/html; charset=utf-8", false}, // Symlinked .gz files are not supported - {"GET", "http://group.test.io/gz-symlink", nil, "*", "data", false}, + {"GET", "/gz-symlink", "*", "data", "text/plain; charset=utf-8", false}, + // Unknown file-extension, with text content + {"GET", "/text.unknown", "*", "hello", "text/plain; charset=utf-8", true}, + {"GET", "/text-nogzip.unknown", "*", "hello", "text/plain; charset=utf-8", false}, + // Unknown file-extension, with PNG content + {"GET", "/image.unknown", "*", "GIF89a", "image/gif", true}, + {"GET", "/image-nogzip.unknown", "*", "GIF89a", "image/gif", false}, } for _, tt := range testSet { - testHTTPGzip(t, serveFileOrNotFound(testGroup), tt.mode, tt.url, tt.params, tt.acceptEncoding, tt.body, tt.ungzip) + URL := "http://group.test.io" + tt.url + testHTTPGzip(t, serveFileOrNotFound(testGroup), tt.mode, URL, nil, tt.acceptEncoding, tt.body, tt.contentType, tt.ungzip) } } diff --git a/shared/pages/group/group.test.io/public/image-nogzip.unknown b/shared/pages/group/group.test.io/public/image-nogzip.unknown Binary files differnew file mode 100644 index 00000000..edaf2b97 --- /dev/null +++ b/shared/pages/group/group.test.io/public/image-nogzip.unknown diff --git a/shared/pages/group/group.test.io/public/image.unknown b/shared/pages/group/group.test.io/public/image.unknown Binary files differnew file mode 100644 index 00000000..edaf2b97 --- /dev/null +++ b/shared/pages/group/group.test.io/public/image.unknown diff --git a/shared/pages/group/group.test.io/public/image.unknown.gz b/shared/pages/group/group.test.io/public/image.unknown.gz Binary files differnew file mode 100644 index 00000000..4d71e2ad --- /dev/null +++ b/shared/pages/group/group.test.io/public/image.unknown.gz diff --git a/shared/pages/group/group.test.io/public/text-nogzip.unknown b/shared/pages/group/group.test.io/public/text-nogzip.unknown new file mode 100644 index 00000000..b6fc4c62 --- /dev/null +++ b/shared/pages/group/group.test.io/public/text-nogzip.unknown @@ -0,0 +1 @@ +hello
\ No newline at end of file diff --git a/shared/pages/group/group.test.io/public/text.unknown b/shared/pages/group/group.test.io/public/text.unknown new file mode 100644 index 00000000..b6fc4c62 --- /dev/null +++ b/shared/pages/group/group.test.io/public/text.unknown @@ -0,0 +1 @@ +hello
\ No newline at end of file diff --git a/shared/pages/group/group.test.io/public/text.unknown.gz b/shared/pages/group/group.test.io/public/text.unknown.gz Binary files differnew file mode 100644 index 00000000..484e01a1 --- /dev/null +++ b/shared/pages/group/group.test.io/public/text.unknown.gz |