2016-11-29 17:26:36 +01:00
// Copyright 2016 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2016-11-29 17:26:36 +01:00
package public
2017-01-28 23:14:56 +01:00
import (
2023-04-12 18:16:45 +08:00
"bytes"
"io"
2018-02-03 23:37:05 +01:00
"net/http"
2021-05-30 18:25:11 +08:00
"os"
2023-04-12 18:16:45 +08:00
"path"
2018-02-03 23:37:05 +01:00
"strings"
2023-04-12 18:16:45 +08:00
"time"
2017-01-28 23:14:56 +01:00
2023-04-12 18:16:45 +08:00
"code.gitea.io/gitea/modules/assetfs"
2022-10-12 07:18:26 +02:00
"code.gitea.io/gitea/modules/container"
2020-11-17 23:44:52 +01:00
"code.gitea.io/gitea/modules/httpcache"
2021-05-30 18:25:11 +08:00
"code.gitea.io/gitea/modules/log"
2017-01-28 23:14:56 +01:00
"code.gitea.io/gitea/modules/setting"
2023-03-08 20:17:39 +08:00
"code.gitea.io/gitea/modules/util"
2026-04-05 21:13:34 +02:00
"github.com/go-chi/cors"
2017-01-28 23:14:56 +01:00
)
2023-04-12 18:16:45 +08:00
func CustomAssets ( ) * assetfs . Layer {
return assetfs . Local ( "custom" , setting . CustomPath , "public" )
2016-11-29 17:26:36 +01:00
}
2017-01-28 23:14:56 +01:00
2023-04-12 18:16:45 +08:00
func AssetFS ( ) * assetfs . LayeredFS {
return assetfs . Layered ( CustomAssets ( ) , BuiltinAssets ( ) )
}
2022-01-20 19:41:25 +08:00
2026-04-05 21:13:34 +02:00
func AssetsCors ( ) func ( next http . Handler ) http . Handler {
// static assets need to be served for external renders (sandboxed)
return cors . Handler ( cors . Options {
AllowedOrigins : [ ] string { "*" } ,
AllowedMethods : [ ] string { "HEAD" , "GET" } ,
MaxAge : 3600 * 24 ,
} )
}
2023-07-21 20:14:20 +08:00
// FileHandlerFunc implements the static handler for serving files in "public" assets
func FileHandlerFunc ( ) http . HandlerFunc {
2023-04-12 18:16:45 +08:00
assetFS := AssetFS ( )
2022-01-20 19:41:25 +08:00
return func ( resp http . ResponseWriter , req * http . Request ) {
2023-04-12 18:16:45 +08:00
if req . Method != "GET" && req . Method != "HEAD" {
2023-12-25 20:13:18 +08:00
resp . WriteHeader ( http . StatusMethodNotAllowed )
2022-01-20 19:41:25 +08:00
return
}
2026-01-24 13:11:49 +08:00
handleRequest ( resp , req , http . FS ( assetFS ) , req . URL . Path )
2018-02-03 23:37:05 +01:00
}
}
2020-12-24 12:25:17 +08:00
// parseAcceptEncoding parse Accept-Encoding: deflate, gzip;q=1.0, *;q=0.5 as compress methods
2022-10-12 07:18:26 +02:00
func parseAcceptEncoding ( val string ) container . Set [ string ] {
2020-12-24 12:25:17 +08:00
parts := strings . Split ( val , ";" )
2022-10-12 07:18:26 +02:00
types := make ( container . Set [ string ] )
2025-06-18 03:48:09 +02:00
for v := range strings . SplitSeq ( parts [ 0 ] , "," ) {
2022-10-12 07:18:26 +02:00
types . Add ( strings . TrimSpace ( v ) )
2020-12-24 12:25:17 +08:00
}
return types
}
2022-01-23 20:19:49 +08:00
// setWellKnownContentType will set the Content-Type if the file is a well-known type.
2026-03-22 05:26:13 +01:00
// See the comments of DetectWellKnownMimeType
2022-01-23 20:19:49 +08:00
func setWellKnownContentType ( w http . ResponseWriter , file string ) {
2026-03-22 05:26:13 +01:00
mimeType := DetectWellKnownMimeType ( path . Ext ( file ) )
2022-01-23 20:19:49 +08:00
if mimeType != "" {
w . Header ( ) . Set ( "Content-Type" , mimeType )
}
}
2023-07-21 20:14:20 +08:00
func handleRequest ( w http . ResponseWriter , req * http . Request , fs http . FileSystem , file string ) {
2023-03-22 04:02:49 +08:00
// 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
2023-07-21 20:14:20 +08:00
f , err := fs . Open ( util . PathJoinRelX ( file ) )
2018-02-03 23:37:05 +01:00
if err != nil {
2021-05-30 18:25:11 +08:00
if os . IsNotExist ( err ) {
2023-07-21 20:14:20 +08:00
w . WriteHeader ( http . StatusNotFound )
return
2020-04-18 23:01:06 +02:00
}
2021-05-30 18:25:11 +08:00
w . WriteHeader ( http . StatusInternalServerError )
log . Error ( "[Static] Open %q failed: %v" , file , err )
2023-07-21 20:14:20 +08:00
return
2018-02-03 23:37:05 +01:00
}
defer f . Close ( )
fi , err := f . Stat ( )
if err != nil {
2021-05-30 18:25:11 +08:00
w . WriteHeader ( http . StatusInternalServerError )
log . Error ( "[Static] %q exists, but fails to open: %v" , file , err )
2023-07-21 20:14:20 +08:00
return
2018-02-03 23:37:05 +01:00
}
2023-07-21 20:14:20 +08:00
// need to serve index file? (no at the moment)
2018-02-03 23:37:05 +01:00
if fi . IsDir ( ) {
2021-05-30 18:25:11 +08:00
w . WriteHeader ( http . StatusNotFound )
2023-07-21 20:14:20 +08:00
return
2018-02-03 23:37:05 +01:00
}
2025-03-13 07:04:50 +08:00
servePublicAsset ( w , req , fi , fi . ModTime ( ) , f )
2018-02-03 23:37:05 +01:00
}
2023-04-12 18:16:45 +08:00
2025-03-13 07:04:50 +08:00
// servePublicAsset serve http content
func servePublicAsset ( w http . ResponseWriter , req * http . Request , fi os . FileInfo , modtime time . Time , content io . ReadSeeker ) {
2023-04-12 18:16:45 +08:00
setWellKnownContentType ( w , fi . Name ( ) )
2025-03-13 07:04:50 +08:00
httpcache . SetCacheControlInHeader ( w . Header ( ) , httpcache . CacheControlForPublicStatic ( ) )
2023-04-12 18:16:45 +08:00
encodings := parseAcceptEncoding ( req . Header . Get ( "Accept-Encoding" ) )
2025-06-12 11:59:33 +08:00
fiEmbedded , _ := fi . ( assetfs . EmbeddedFileInfo )
if encodings . Contains ( "gzip" ) && fiEmbedded != nil {
// try to provide gzip content directly from bindata
if gzipBytes , ok := fiEmbedded . GetGzipContent ( ) ; ok {
rdGzip := bytes . NewReader ( gzipBytes )
2023-04-12 18:16:45 +08:00
// 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" )
2025-03-13 07:04:50 +08:00
http . ServeContent ( w , req , fi . Name ( ) , modtime , rdGzip )
2023-04-12 18:16:45 +08:00
return
}
}
2025-03-13 07:04:50 +08:00
http . ServeContent ( w , req , fi . Name ( ) , modtime , content )
2023-04-12 18:16:45 +08:00
}