2016-11-29 21:56:36 +05:30
// Copyright 2016 The Gitea Authors. All rights reserved.
2022-11-27 23:50:29 +05:30
// SPDX-License-Identifier: MIT
2016-11-29 21:56:36 +05:30
package public
2017-01-29 03:44:56 +05:30
import (
2023-04-12 15:46:45 +05:30
"bytes"
"io"
2018-02-04 04:07:05 +05:30
"net/http"
2021-05-30 15:55:11 +05:30
"os"
2023-04-12 15:46:45 +05:30
"path"
2018-02-04 04:07:05 +05:30
"strings"
2023-04-12 15:46:45 +05:30
"time"
2017-01-29 03:44:56 +05:30
2023-04-12 15:46:45 +05:30
"code.gitea.io/gitea/modules/assetfs"
2022-10-12 10:48:26 +05:30
"code.gitea.io/gitea/modules/container"
2020-11-18 04:14:52 +05:30
"code.gitea.io/gitea/modules/httpcache"
2021-05-30 15:55:11 +05:30
"code.gitea.io/gitea/modules/log"
2017-01-29 03:44:56 +05:30
"code.gitea.io/gitea/modules/setting"
2023-03-08 17:47:39 +05:30
"code.gitea.io/gitea/modules/util"
2017-01-29 03:44:56 +05:30
)
2023-04-12 15:46:45 +05:30
func CustomAssets ( ) * assetfs . Layer {
return assetfs . Local ( "custom" , setting . CustomPath , "public" )
2016-11-29 21:56:36 +05:30
}
2017-01-29 03:44:56 +05:30
2023-04-12 15:46:45 +05:30
func AssetFS ( ) * assetfs . LayeredFS {
return assetfs . Layered ( CustomAssets ( ) , BuiltinAssets ( ) )
}
2022-01-20 17:11:25 +05:30
// AssetsHandlerFunc implements the static handler for serving custom or original assets.
2023-04-12 15:46:45 +05:30
func AssetsHandlerFunc ( prefix string ) http . HandlerFunc {
assetFS := AssetFS ( )
prefix = strings . TrimSuffix ( prefix , "/" ) + "/"
2022-01-20 17:11:25 +05:30
return func ( resp http . ResponseWriter , req * http . Request ) {
2023-04-12 15:46:45 +05:30
subPath := req . URL . Path
if ! strings . HasPrefix ( subPath , prefix ) {
2022-01-20 17:11:25 +05:30
return
}
2023-04-12 15:46:45 +05:30
subPath = strings . TrimPrefix ( subPath , prefix )
2018-02-04 04:07:05 +05:30
2023-04-12 15:46:45 +05:30
if req . Method != "GET" && req . Method != "HEAD" {
resp . WriteHeader ( http . StatusNotFound )
2022-01-20 17:11:25 +05:30
return
}
2018-02-04 04:07:05 +05:30
2023-04-12 15:46:45 +05:30
if handleRequest ( resp , req , assetFS , subPath ) {
2022-01-20 17:11:25 +05:30
return
}
2021-05-30 15:55:11 +05:30
2022-01-20 17:11:25 +05:30
resp . WriteHeader ( http . StatusNotFound )
2018-02-04 04:07:05 +05:30
}
}
2020-12-24 09:55:17 +05:30
// parseAcceptEncoding parse Accept-Encoding: deflate, gzip;q=1.0, *;q=0.5 as compress methods
2022-10-12 10:48:26 +05:30
func parseAcceptEncoding ( val string ) container . Set [ string ] {
2020-12-24 09:55:17 +05:30
parts := strings . Split ( val , ";" )
2022-10-12 10:48:26 +05:30
types := make ( container . Set [ string ] )
2020-12-24 09:55:17 +05:30
for _ , v := range strings . Split ( parts [ 0 ] , "," ) {
2022-10-12 10:48:26 +05:30
types . Add ( strings . TrimSpace ( v ) )
2020-12-24 09:55:17 +05:30
}
return types
}
2022-01-23 17:49:49 +05:30
// setWellKnownContentType will set the Content-Type if the file is a well-known type.
// See the comments of detectWellKnownMimeType
func setWellKnownContentType ( w http . ResponseWriter , file string ) {
2023-04-12 15:46:45 +05:30
mimeType := detectWellKnownMimeType ( path . Ext ( file ) )
2022-01-23 17:49:49 +05:30
if mimeType != "" {
w . Header ( ) . Set ( "Content-Type" , mimeType )
}
}
2023-04-12 15:46:45 +05:30
func handleRequest ( w http . ResponseWriter , req * http . Request , fs http . FileSystem , file string ) bool {
2023-03-22 01:32:49 +05:30
// actually, fs (http.FileSystem) is designed to be a safe interface, relative paths won't bypass its parent directory, it's also fine to do a clean here
f , err := fs . Open ( util . PathJoinRelX ( file ) )
2018-02-04 04:07:05 +05:30
if err != nil {
2021-05-30 15:55:11 +05:30
if os . IsNotExist ( err ) {
return false
2020-04-19 02:31:06 +05:30
}
2021-05-30 15:55:11 +05:30
w . WriteHeader ( http . StatusInternalServerError )
log . Error ( "[Static] Open %q failed: %v" , file , err )
return true
2018-02-04 04:07:05 +05:30
}
defer f . Close ( )
fi , err := f . Stat ( )
if err != nil {
2021-05-30 15:55:11 +05:30
w . WriteHeader ( http . StatusInternalServerError )
log . Error ( "[Static] %q exists, but fails to open: %v" , file , err )
2018-02-04 04:07:05 +05:30
return true
}
// Try to serve index file
if fi . IsDir ( ) {
2021-05-30 15:55:11 +05:30
w . WriteHeader ( http . StatusNotFound )
return true
2018-02-04 04:07:05 +05:30
}
2021-05-30 15:55:11 +05:30
serveContent ( w , req , fi , fi . ModTime ( ) , f )
2018-02-04 04:07:05 +05:30
return true
}
2023-04-12 15:46:45 +05:30
type GzipBytesProvider interface {
GzipBytes ( ) [ ] byte
}
// serveContent serve http content
func serveContent ( w http . ResponseWriter , req * http . Request , fi os . FileInfo , modtime time . Time , content io . ReadSeeker ) {
setWellKnownContentType ( w , fi . Name ( ) )
encodings := parseAcceptEncoding ( req . Header . Get ( "Accept-Encoding" ) )
if encodings . Contains ( "gzip" ) {
// try to provide gzip content directly from bindata (provided by vfsgen۰ CompressedFileInfo)
if compressed , ok := fi . ( GzipBytesProvider ) ; ok {
rdGzip := bytes . NewReader ( compressed . GzipBytes ( ) )
// all gzipped static files (from bindata) are managed by Gitea, so we can make sure every file has the correct ext name
// then we can get the correct Content-Type, we do not need to do http.DetectContentType on the decompressed data
if w . Header ( ) . Get ( "Content-Type" ) == "" {
w . Header ( ) . Set ( "Content-Type" , "application/octet-stream" )
}
w . Header ( ) . Set ( "Content-Encoding" , "gzip" )
2023-05-13 19:34:57 +05:30
httpcache . ServeContentWithCacheControl ( w , req , fi . Name ( ) , modtime , rdGzip )
2023-04-12 15:46:45 +05:30
return
}
}
2023-05-13 19:34:57 +05:30
httpcache . ServeContentWithCacheControl ( w , req , fi . Name ( ) , modtime , content )
2023-04-12 15:46:45 +05:30
return
}