2020-11-17 23:44:52 +01:00
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package httpcache
import (
"encoding/base64"
"fmt"
"net/http"
"os"
"strconv"
2021-04-12 16:49:26 +02:00
"strings"
2020-11-17 23:44:52 +01:00
"time"
"code.gitea.io/gitea/modules/setting"
)
// GetCacheControl returns a suitable "Cache-Control" header value
func GetCacheControl ( ) string {
2021-01-15 05:17:03 +08:00
if ! setting . IsProd ( ) {
2020-11-17 23:44:52 +01:00
return "no-store"
}
return "private, max-age=" + strconv . FormatInt ( int64 ( setting . StaticCacheTime . Seconds ( ) ) , 10 )
}
// generateETag generates an ETag based on size, filename and file modification time
func generateETag ( fi os . FileInfo ) string {
etag := fmt . Sprint ( fi . Size ( ) ) + fi . Name ( ) + fi . ModTime ( ) . UTC ( ) . Format ( http . TimeFormat )
2021-04-12 16:49:26 +02:00
return ` " ` + base64 . StdEncoding . EncodeToString ( [ ] byte ( etag ) ) + ` " `
2020-11-17 23:44:52 +01:00
}
// HandleTimeCache handles time-based caching for a HTTP request
func HandleTimeCache ( req * http . Request , w http . ResponseWriter , fi os . FileInfo ) ( handled bool ) {
2021-04-12 16:49:26 +02:00
w . Header ( ) . Set ( "Cache-Control" , GetCacheControl ( ) )
2020-11-17 23:44:52 +01:00
ifModifiedSince := req . Header . Get ( "If-Modified-Since" )
if ifModifiedSince != "" {
t , err := time . Parse ( http . TimeFormat , ifModifiedSince )
if err == nil && fi . ModTime ( ) . Unix ( ) <= t . Unix ( ) {
w . WriteHeader ( http . StatusNotModified )
return true
}
}
w . Header ( ) . Set ( "Last-Modified" , fi . ModTime ( ) . Format ( http . TimeFormat ) )
return false
}
2021-04-12 16:49:26 +02:00
// HandleFileETagCache handles ETag-based caching for a HTTP request
func HandleFileETagCache ( req * http . Request , w http . ResponseWriter , fi os . FileInfo ) ( handled bool ) {
2020-11-17 23:44:52 +01:00
etag := generateETag ( fi )
2021-04-12 16:49:26 +02:00
return HandleGenericETagCache ( req , w , etag )
}
2020-11-17 23:44:52 +01:00
2021-04-12 16:49:26 +02:00
// HandleGenericETagCache handles ETag-based caching for a HTTP request.
// It returns true if the request was handled.
func HandleGenericETagCache ( req * http . Request , w http . ResponseWriter , etag string ) ( handled bool ) {
if len ( etag ) > 0 {
w . Header ( ) . Set ( "Etag" , etag )
if checkIfNoneMatchIsValid ( req , etag ) {
w . WriteHeader ( http . StatusNotModified )
return true
}
}
2020-11-17 23:44:52 +01:00
w . Header ( ) . Set ( "Cache-Control" , GetCacheControl ( ) )
2021-04-12 16:49:26 +02:00
return false
}
// checkIfNoneMatchIsValid tests if the header If-None-Match matches the ETag
func checkIfNoneMatchIsValid ( req * http . Request , etag string ) bool {
ifNoneMatch := req . Header . Get ( "If-None-Match" )
if len ( ifNoneMatch ) > 0 {
for _ , item := range strings . Split ( ifNoneMatch , "," ) {
item = strings . TrimSpace ( item )
if item == etag {
return true
}
}
}
2020-11-17 23:44:52 +01:00
return false
}