2016-11-29 11:26:36 -05:00
// Copyright 2016 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2016-11-29 11:26:36 -05:00
package public
2017-01-28 17:14:56 -05:00
import (
2018-02-03 17:37:05 -05:00
"net/http"
2021-05-30 06:25:11 -04:00
"os"
2018-02-03 17:37:05 -05:00
"path/filepath"
"strings"
2017-01-28 17:14:56 -05:00
2022-10-12 01:18:26 -04:00
"code.gitea.io/gitea/modules/container"
2020-11-17 17:44:52 -05:00
"code.gitea.io/gitea/modules/httpcache"
2021-05-30 06:25:11 -04:00
"code.gitea.io/gitea/modules/log"
2017-01-28 17:14:56 -05:00
"code.gitea.io/gitea/modules/setting"
2023-03-08 07:17:39 -05:00
"code.gitea.io/gitea/modules/util"
2017-01-28 17:14:56 -05:00
)
2021-01-29 10:35:30 -05:00
// Options represents the available options to configure the handler.
2016-11-29 11:26:36 -05:00
type Options struct {
Directory string
2020-11-17 17:44:52 -05:00
Prefix string
2021-05-30 06:25:11 -04:00
CorsHandler func ( http . Handler ) http . Handler
2016-11-29 11:26:36 -05:00
}
2017-01-28 17:14:56 -05:00
2022-01-20 06:41:25 -05:00
// AssetsURLPathPrefix is the path prefix for static asset files
const AssetsURLPathPrefix = "/assets/"
// AssetsHandlerFunc implements the static handler for serving custom or original assets.
func AssetsHandlerFunc ( opts * Options ) http . HandlerFunc {
2022-01-20 12:46:10 -05:00
custPath := filepath . Join ( setting . CustomPath , "public" )
2021-05-30 06:25:11 -04:00
if ! filepath . IsAbs ( custPath ) {
custPath = filepath . Join ( setting . AppWorkPath , custPath )
}
if ! filepath . IsAbs ( opts . Directory ) {
opts . Directory = filepath . Join ( setting . AppWorkPath , opts . Directory )
}
if ! strings . HasSuffix ( opts . Prefix , "/" ) {
opts . Prefix += "/"
2018-02-03 17:37:05 -05:00
}
2022-01-20 06:41:25 -05:00
return func ( resp http . ResponseWriter , req * http . Request ) {
if req . Method != "GET" && req . Method != "HEAD" {
resp . WriteHeader ( http . StatusNotFound )
return
}
2018-02-03 17:37:05 -05:00
2023-03-21 16:02:49 -04:00
var corsSent bool
2022-01-20 06:41:25 -05:00
if opts . CorsHandler != nil {
opts . CorsHandler ( http . HandlerFunc ( func ( http . ResponseWriter , * http . Request ) {
2023-03-21 16:02:49 -04:00
corsSent = true
2022-01-20 06:41:25 -05:00
} ) ) . ServeHTTP ( resp , req )
}
2023-03-21 16:02:49 -04:00
// If CORS is not sent, the response must have been written by other handlers
if ! corsSent {
2022-01-20 06:41:25 -05:00
return
}
2018-02-03 17:37:05 -05:00
2023-03-21 16:02:49 -04:00
file := req . URL . Path [ len ( opts . Prefix ) : ]
2022-01-20 06:41:25 -05:00
// custom files
if opts . handle ( resp , req , http . Dir ( custPath ) , file ) {
return
}
2018-02-03 17:37:05 -05:00
2022-01-20 06:41:25 -05:00
// internal files
if opts . handle ( resp , req , fileSystem ( opts . Directory ) , file ) {
return
}
2021-05-30 06:25:11 -04:00
2022-01-20 06:41:25 -05:00
resp . WriteHeader ( http . StatusNotFound )
2018-02-03 17:37:05 -05:00
}
}
2020-12-23 23:25:17 -05:00
// parseAcceptEncoding parse Accept-Encoding: deflate, gzip;q=1.0, *;q=0.5 as compress methods
2022-10-12 01:18:26 -04:00
func parseAcceptEncoding ( val string ) container . Set [ string ] {
2020-12-23 23:25:17 -05:00
parts := strings . Split ( val , ";" )
2022-10-12 01:18:26 -04:00
types := make ( container . Set [ string ] )
2020-12-23 23:25:17 -05:00
for _ , v := range strings . Split ( parts [ 0 ] , "," ) {
2022-10-12 01:18:26 -04:00
types . Add ( strings . TrimSpace ( v ) )
2020-12-23 23:25:17 -05:00
}
return types
}
2022-01-23 07:19:49 -05:00
// 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 ) {
mimeType := detectWellKnownMimeType ( filepath . Ext ( file ) )
if mimeType != "" {
w . Header ( ) . Set ( "Content-Type" , mimeType )
}
}
2021-05-30 06:25:11 -04:00
func ( opts * Options ) handle ( w http . ResponseWriter , req * http . Request , fs http . FileSystem , file string ) bool {
2023-03-21 16:02:49 -04: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
f , err := fs . Open ( util . PathJoinRelX ( file ) )
2018-02-03 17:37:05 -05:00
if err != nil {
2021-05-30 06:25:11 -04:00
if os . IsNotExist ( err ) {
return false
2020-04-18 17:01:06 -04:00
}
2021-05-30 06:25:11 -04:00
w . WriteHeader ( http . StatusInternalServerError )
log . Error ( "[Static] Open %q failed: %v" , file , err )
return true
2018-02-03 17:37:05 -05:00
}
defer f . Close ( )
fi , err := f . Stat ( )
if err != nil {
2021-05-30 06:25:11 -04:00
w . WriteHeader ( http . StatusInternalServerError )
log . Error ( "[Static] %q exists, but fails to open: %v" , file , err )
2018-02-03 17:37:05 -05:00
return true
}
// Try to serve index file
if fi . IsDir ( ) {
2021-05-30 06:25:11 -04:00
w . WriteHeader ( http . StatusNotFound )
return true
2018-02-03 17:37:05 -05:00
}
2021-04-12 10:49:26 -04:00
if httpcache . HandleFileETagCache ( req , w , fi ) {
2020-11-17 17:44:52 -05:00
return true
2018-02-03 17:37:05 -05:00
}
2022-01-23 07:19:49 -05:00
setWellKnownContentType ( w , file )
2021-05-30 06:25:11 -04:00
serveContent ( w , req , fi , fi . ModTime ( ) , f )
2018-02-03 17:37:05 -05:00
return true
}