mirror of
https://github.com/octoleo/syncthing.git
synced 2024-12-23 03:18:59 +00:00
105 lines
2.9 KiB
Go
105 lines
2.9 KiB
Go
// 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 := time.Parse(http.TimeFormat, 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 ".js":
|
|
return "application/javascript; charset=utf-8"
|
|
case ".json":
|
|
return "application/json; charset=utf-8"
|
|
case ".png":
|
|
return "image/png"
|
|
case ".ttf":
|
|
return "application/x-font-ttf"
|
|
case ".woff":
|
|
return "application/x-font-woff"
|
|
case ".svg":
|
|
return "image/svg+xml; charset=utf-8"
|
|
default:
|
|
return mime.TypeByExtension(ext)
|
|
}
|
|
}
|