55package public
66
77import (
8- "log"
98 "net/http"
9+ "os"
1010 "path"
1111 "path/filepath"
1212 "strings"
1313
1414 "code.gitea.io/gitea/modules/httpcache"
15+ "code.gitea.io/gitea/modules/log"
1516 "code.gitea.io/gitea/modules/setting"
1617)
1718
1819// Options represents the available options to configure the handler.
1920type Options struct {
2021 Directory string
21- IndexFile string
22- SkipLogging bool
23- FileSystem http.FileSystem
2422 Prefix string
23+ CorsHandler func (http.Handler ) http.Handler
2524}
2625
27- // KnownPublicEntries list all direct children in the `public` directory
28- var KnownPublicEntries = []string {
29- "css" ,
30- "fonts" ,
31- "img" ,
32- "js" ,
33- "serviceworker.js" ,
34- "vendor" ,
35- }
36-
37- // Custom implements the static handler for serving custom assets.
38- func Custom (opts * Options ) func (next http.Handler ) http.Handler {
39- return opts .staticHandler (path .Join (setting .CustomPath , "public" ))
40- }
41-
42- // staticFileSystem implements http.FileSystem interface.
43- type staticFileSystem struct {
44- dir * http.Dir
45- }
46-
47- func newStaticFileSystem (directory string ) staticFileSystem {
48- if ! filepath .IsAbs (directory ) {
49- directory = filepath .Join (setting .AppWorkPath , directory )
26+ // AssetsHandler implements the static handler for serving custom or original assets.
27+ func AssetsHandler (opts * Options ) func (next http.Handler ) http.Handler {
28+ var custPath = filepath .Join (setting .CustomPath , "public" )
29+ if ! filepath .IsAbs (custPath ) {
30+ custPath = filepath .Join (setting .AppWorkPath , custPath )
31+ }
32+ if ! filepath .IsAbs (opts .Directory ) {
33+ opts .Directory = filepath .Join (setting .AppWorkPath , opts .Directory )
34+ }
35+ if ! strings .HasSuffix (opts .Prefix , "/" ) {
36+ opts .Prefix += "/"
5037 }
51- dir := http .Dir (directory )
52- return staticFileSystem {& dir }
53- }
5438
55- func (fs staticFileSystem ) Open (name string ) (http.File , error ) {
56- return fs .dir .Open (name )
57- }
39+ return func (next http.Handler ) http.Handler {
40+ return http .HandlerFunc (func (resp http.ResponseWriter , req * http.Request ) {
41+ if ! strings .HasPrefix (req .URL .Path , opts .Prefix ) {
42+ next .ServeHTTP (resp , req )
43+ return
44+ }
45+ if req .Method != "GET" && req .Method != "HEAD" {
46+ resp .WriteHeader (http .StatusNotFound )
47+ return
48+ }
5849
59- // StaticHandler sets up a new middleware for serving static files in the
60- func StaticHandler (dir string , opts * Options ) func (next http.Handler ) http.Handler {
61- return opts .staticHandler (dir )
62- }
50+ file := req .URL .Path
51+ file = file [len (opts .Prefix ):]
52+ if len (file ) == 0 {
53+ resp .WriteHeader (http .StatusNotFound )
54+ return
55+ }
56+ if strings .Contains (file , "\\ " ) {
57+ resp .WriteHeader (http .StatusBadRequest )
58+ return
59+ }
60+ file = "/" + file
61+
62+ var written bool
63+ if opts .CorsHandler != nil {
64+ written = true
65+ opts .CorsHandler (http .HandlerFunc (func (http.ResponseWriter , * http.Request ) {
66+ written = false
67+ })).ServeHTTP (resp , req )
68+ }
69+ if written {
70+ return
71+ }
6372
64- func (opts * Options ) staticHandler (dir string ) func (next http.Handler ) http.Handler {
65- return func (next http.Handler ) http.Handler {
66- // Defaults
67- if len (opts .IndexFile ) == 0 {
68- opts .IndexFile = "index.html"
69- }
70- // Normalize the prefix if provided
71- if opts .Prefix != "" {
72- // Ensure we have a leading '/'
73- if opts .Prefix [0 ] != '/' {
74- opts .Prefix = "/" + opts .Prefix
73+ // custom files
74+ if opts .handle (resp , req , http .Dir (custPath ), file ) {
75+ return
7576 }
76- // Remove any trailing '/'
77- opts .Prefix = strings .TrimRight (opts .Prefix , "/" )
78- }
79- if opts .FileSystem == nil {
80- opts .FileSystem = newStaticFileSystem (dir )
81- }
8277
83- return http . HandlerFunc ( func ( w http. ResponseWriter , req * http. Request ) {
84- if ! opts .handle (w , req , opts ) {
85- next . ServeHTTP ( w , req )
78+ // internal files
79+ if opts .handle (resp , req , fileSystem ( opts . Directory ), file ) {
80+ return
8681 }
82+
83+ resp .WriteHeader (http .StatusNotFound )
8784 })
8885 }
8986}
@@ -98,76 +95,36 @@ func parseAcceptEncoding(val string) map[string]bool {
9895 return types
9996}
10097
101- func (opts * Options ) handle (w http.ResponseWriter , req * http.Request , opt * Options ) bool {
102- if req .Method != "GET" && req .Method != "HEAD" {
103- return false
104- }
105-
106- file := req .URL .Path
107- // if we have a prefix, filter requests by stripping the prefix
108- if opt .Prefix != "" {
109- if ! strings .HasPrefix (file , opt .Prefix ) {
110- return false
111- }
112- file = file [len (opt .Prefix ):]
113- if file != "" && file [0 ] != '/' {
114- return false
115- }
116- }
117-
118- f , err := opt .FileSystem .Open (file )
98+ func (opts * Options ) handle (w http.ResponseWriter , req * http.Request , fs http.FileSystem , file string ) bool {
99+ // use clean to keep the file is a valid path with no . or ..
100+ f , err := fs .Open (path .Clean (file ))
119101 if err != nil {
120- // 404 requests to any known entries in `public`
121- if path .Base (opts .Directory ) == "public" {
122- parts := strings .Split (file , "/" )
123- if len (parts ) < 2 {
124- return false
125- }
126- for _ , entry := range KnownPublicEntries {
127- if entry == parts [1 ] {
128- w .WriteHeader (404 )
129- return true
130- }
131- }
102+ if os .IsNotExist (err ) {
103+ return false
132104 }
133- return false
105+ w .WriteHeader (http .StatusInternalServerError )
106+ log .Error ("[Static] Open %q failed: %v" , file , err )
107+ return true
134108 }
135109 defer f .Close ()
136110
137111 fi , err := f .Stat ()
138112 if err != nil {
139- log .Printf ("[Static] %q exists, but fails to open: %v" , file , err )
113+ w .WriteHeader (http .StatusInternalServerError )
114+ log .Error ("[Static] %q exists, but fails to open: %v" , file , err )
140115 return true
141116 }
142117
143118 // Try to serve index file
144119 if fi .IsDir () {
145- // Redirect if missing trailing slash.
146- if ! strings .HasSuffix (req .URL .Path , "/" ) {
147- http .Redirect (w , req , path .Clean (req .URL .Path + "/" ), http .StatusFound )
148- return true
149- }
150-
151- f , err = opt .FileSystem .Open (file )
152- if err != nil {
153- return false // Discard error.
154- }
155- defer f .Close ()
156-
157- fi , err = f .Stat ()
158- if err != nil || fi .IsDir () {
159- return false
160- }
161- }
162-
163- if ! opt .SkipLogging {
164- log .Println ("[Static] Serving " + file )
120+ w .WriteHeader (http .StatusNotFound )
121+ return true
165122 }
166123
167124 if httpcache .HandleFileETagCache (req , w , fi ) {
168125 return true
169126 }
170127
171- ServeContent (w , req , fi , fi .ModTime (), f )
128+ serveContent (w , req , fi , fi .ModTime (), f )
172129 return true
173130}
0 commit comments