123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109 |
- // Copyright (C) 2014-2020 The Syncthing Authors.
- //
- // This Source Code Form is subject to the terms of the Mozilla Public
- // License, v. 2.0. If a copy of the MPL was not distributed with this file,
- // You can obtain one at https://mozilla.org/MPL/2.0/.
- // Package assets hold utilities for serving static assets.
- //
- // The actual assets live in auto subpackages instead of here,
- // because the set of assets varies per program.
- package assets
- import (
- "compress/gzip"
- "fmt"
- "io"
- "mime"
- "net/http"
- "path/filepath"
- "strconv"
- "strings"
- "time"
- )
- // An Asset is an embedded file to be served over HTTP.
- type Asset struct {
- Content string // Contents of asset, possibly gzipped.
- Gzipped bool
- Length int // Length of (decompressed) Content.
- Filename string // Original filename, determines Content-Type.
- Modified time.Time // Determines ETag and Last-Modified.
- }
- // Serve writes a gzipped asset to w.
- func Serve(w http.ResponseWriter, r *http.Request, asset Asset) {
- header := w.Header()
- mtype := MimeTypeForFile(asset.Filename)
- if mtype != "" {
- header.Set("Content-Type", mtype)
- }
- etag := fmt.Sprintf(`"%x"`, asset.Modified.Unix())
- header.Set("ETag", etag)
- header.Set("Last-Modified", asset.Modified.Format(http.TimeFormat))
- t, err := http.ParseTime(r.Header.Get("If-Modified-Since"))
- if err == nil && !asset.Modified.After(t) {
- w.WriteHeader(http.StatusNotModified)
- return
- }
- if r.Header.Get("If-None-Match") == etag {
- w.WriteHeader(http.StatusNotModified)
- return
- }
- switch {
- case !asset.Gzipped:
- header.Set("Content-Length", strconv.Itoa(len(asset.Content)))
- io.WriteString(w, asset.Content)
- case strings.Contains(r.Header.Get("Accept-Encoding"), "gzip"):
- header.Set("Content-Encoding", "gzip")
- header.Set("Content-Length", strconv.Itoa(len(asset.Content)))
- io.WriteString(w, asset.Content)
- default:
- header.Set("Content-Length", strconv.Itoa(asset.Length))
- // gunzip for browsers that don't want gzip.
- var gr *gzip.Reader
- gr, _ = gzip.NewReader(strings.NewReader(asset.Content))
- io.Copy(w, gr)
- gr.Close()
- }
- }
- // MimeTypeForFile returns the appropriate MIME type for an asset,
- // based on the filename.
- //
- // We use a built in table of the common types since the system
- // TypeByExtension might be unreliable. But if we don't know, we delegate
- // to the system. All our text files are in UTF-8.
- func MimeTypeForFile(file string) string {
- ext := filepath.Ext(file)
- switch ext {
- case ".htm", ".html":
- return "text/html; charset=utf-8"
- case ".css":
- return "text/css; charset=utf-8"
- case ".eot":
- return "application/vnd.ms-fontobject"
- case ".js":
- return "application/javascript; charset=utf-8"
- case ".json":
- return "application/json; charset=utf-8"
- case ".png":
- return "image/png"
- case ".svg":
- return "image/svg+xml; charset=utf-8"
- case ".ttf":
- return "font/ttf"
- case ".woff":
- return "font/woff"
- case ".woff2":
- return "font/woff2"
- default:
- return mime.TypeByExtension(ext)
- }
- }
|