@ -5,85 +5,82 @@
package public
import (
"log"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
// Options represents the available options to configure the handler.
type Options struct {
Directory string
IndexFile string
SkipLogging bool
FileSystem http . FileSystem
Prefix string
CorsHandler func ( http . Handler ) http . Handler
}
// KnownPublicEntries list all direct children in the `public` directory
var KnownPublicEntries = [ ] string {
"css" ,
"fonts" ,
"img" ,
"js" ,
"serviceworker.js" ,
"vendor" ,
// AssetsHandler implements the static handler for serving custom or original assets.
func AssetsHandler ( opts * Options ) func ( next http . Handler ) http . Handler {
var custPath = filepath . Join ( setting . CustomPath , "public" )
if ! filepath . IsAbs ( custPath ) {
custPath = filepath . Join ( setting . AppWorkPath , custPath )
}
// Custom implements the static handler for serving custom assets.
func Custom ( opts * Options ) func ( next http . Handler ) http . Handler {
return opts . staticHandler ( path . Join ( setting . CustomPath , "public" ) )
if ! filepath . IsAbs ( opts . Directory ) {
opts . Directory = filepath . Join ( setting . AppWorkPath , opts . Directory )
}
// staticFileSystem implements http.FileSystem interface.
type staticFileSystem struct {
dir * http . Dir
if ! strings . HasSuffix ( opts . Prefix , "/" ) {
opts . Prefix += "/"
}
func newStaticFileSystem ( directory string ) staticFileSystem {
if ! filepath . IsAbs ( directory ) {
directory = filepath . Join ( setting . AppWorkPath , directory )
return func ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( resp http . ResponseWriter , req * http . Request ) {
if ! strings . HasPrefix ( req . URL . Path , opts . Prefix ) {
next . ServeHTTP ( resp , req )
return
}
dir := http . Dir ( directory )
return staticFileSystem { & dir }
if req . Method != "GET" && req . Method != "HEAD" {
resp . WriteHeader ( http . StatusNotFound )
return
}
func ( fs staticFileSystem ) Open ( name string ) ( http . File , error ) {
return fs . dir . Open ( name )
file := req . URL . Path
file = file [ len ( opts . Prefix ) : ]
if len ( file ) == 0 {
resp . WriteHeader ( http . StatusNotFound )
return
}
// StaticHandler sets up a new middleware for serving static files in the
func StaticHandler ( dir string , opts * Options ) func ( next http . Handler ) http . Handler {
return opts . staticHandler ( dir )
if strings . Contains ( file , "\\" ) {
resp . WriteHeader ( http . StatusBadRequest )
return
}
file = "/" + file
func ( opts * Options ) staticHandler ( dir string ) func ( next http . Handler ) http . Handler {
return func ( next http . Handler ) http . Handler {
// Defaults
if len ( opts . IndexFile ) == 0 {
opts . IndexFile = "index.html"
var written bool
if opts . CorsHandler != nil {
written = true
opts . CorsHandler ( http . HandlerFunc ( func ( http . ResponseWriter , * http . Request ) {
written = false
} ) ) . ServeHTTP ( resp , req )
}
// Normalize the prefix if provided
if opts . Prefix != "" {
// Ensure we have a leading '/'
if opts . Prefix [ 0 ] != '/' {
opts . Prefix = "/" + opts . Prefix
if written {
return
}
// Remove any trailing '/'
opts . Prefix = strings . TrimRight ( opts . Prefix , "/" )
}
if opts . FileSystem == nil {
opts . FileSystem = newStaticFileSystem ( dir )
// custom files
if opts . handle ( resp , req , http . Dir ( custPath ) , file ) {
return
}
return http . HandlerFunc ( func ( w http . ResponseWriter , req * http . Request ) {
if ! opts . handle ( w , req , opts ) {
next . ServeHTTP ( w , req )
// internal files
if opts . handle ( resp , req , fileSystem ( opts . Directory ) , file ) {
return
}
resp . WriteHeader ( http . StatusNotFound )
} )
}
}
@ -98,76 +95,36 @@ func parseAcceptEncoding(val string) map[string]bool {
return types
}
func ( opts * Options ) handle ( w http . ResponseWriter , req * http . Request , opt * Options ) bool {
if req . Method != "GET" && req . Method != "HEAD" {
return false
}
file := req . URL . Path
// if we have a prefix, filter requests by stripping the prefix
if opt . Prefix != "" {
if ! strings . HasPrefix ( file , opt . Prefix ) {
return false
}
file = file [ len ( opt . Prefix ) : ]
if file != "" && file [ 0 ] != '/' {
return false
}
}
f , err := opt . FileSystem . Open ( file )
func ( opts * Options ) handle ( w http . ResponseWriter , req * http . Request , fs http . FileSystem , file string ) bool {
// use clean to keep the file is a valid path with no . or ..
f , err := fs . Open ( path . Clean ( file ) )
if err != nil {
// 404 requests to any known entries in `public`
if path . Base ( opts . Directory ) == "public" {
parts := strings . Split ( file , "/" )
if len ( parts ) < 2 {
if os . IsNotExist ( err ) {
return false
}
for _ , entry := range KnownPublicEntries {
if entry == parts [ 1 ] {
w . WriteHeader ( 404 )
w . WriteHeader ( http . StatusInternalServerError )
log . Error ( "[Static] Open %q failed: %v" , file , err )
return true
}
}
}
return false
}
defer f . Close ( )
fi , err := f . Stat ( )
if err != nil {
log . Printf ( "[Static] %q exists, but fails to open: %v" , file , err )
w . WriteHeader ( http . StatusInternalServerError )
log . Error ( "[Static] %q exists, but fails to open: %v" , file , err )
return true
}
// Try to serve index file
if fi . IsDir ( ) {
// Redirect if missing trailing slash.
if ! strings . HasSuffix ( req . URL . Path , "/" ) {
http . Redirect ( w , req , path . Clean ( req . URL . Path + "/" ) , http . StatusFound )
w . WriteHeader ( http . StatusNotFound )
return true
}
f , err = opt . FileSystem . Open ( file )
if err != nil {
return false // Discard error.
}
defer f . Close ( )
fi , err = f . Stat ( )
if err != nil || fi . IsDir ( ) {
return false
}
}
if ! opt . SkipLogging {
log . Println ( "[Static] Serving " + file )
}
if httpcache . HandleFileETagCache ( req , w , fi ) {
return true
}
S erveContent( w , req , fi , fi . ModTime ( ) , f )
serveContent ( w , req , fi , fi . ModTime ( ) , f )
return true
}