code.gitea.io/gitea@v1.19.3/modules/public/public.go (about)

     1  // Copyright 2016 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package public
     5  
     6  import (
     7  	"net/http"
     8  	"os"
     9  	"path"
    10  	"path/filepath"
    11  	"strings"
    12  
    13  	"code.gitea.io/gitea/modules/container"
    14  	"code.gitea.io/gitea/modules/httpcache"
    15  	"code.gitea.io/gitea/modules/log"
    16  	"code.gitea.io/gitea/modules/setting"
    17  )
    18  
    19  // Options represents the available options to configure the handler.
    20  type Options struct {
    21  	Directory   string
    22  	Prefix      string
    23  	CorsHandler func(http.Handler) http.Handler
    24  }
    25  
    26  // AssetsURLPathPrefix is the path prefix for static asset files
    27  const AssetsURLPathPrefix = "/assets/"
    28  
    29  // AssetsHandlerFunc implements the static handler for serving custom or original assets.
    30  func AssetsHandlerFunc(opts *Options) http.HandlerFunc {
    31  	custPath := filepath.Join(setting.CustomPath, "public")
    32  	if !filepath.IsAbs(custPath) {
    33  		custPath = filepath.Join(setting.AppWorkPath, custPath)
    34  	}
    35  	if !filepath.IsAbs(opts.Directory) {
    36  		opts.Directory = filepath.Join(setting.AppWorkPath, opts.Directory)
    37  	}
    38  	if !strings.HasSuffix(opts.Prefix, "/") {
    39  		opts.Prefix += "/"
    40  	}
    41  
    42  	return func(resp http.ResponseWriter, req *http.Request) {
    43  		if req.Method != "GET" && req.Method != "HEAD" {
    44  			resp.WriteHeader(http.StatusNotFound)
    45  			return
    46  		}
    47  
    48  		file := req.URL.Path
    49  		file = file[len(opts.Prefix):]
    50  		if len(file) == 0 {
    51  			resp.WriteHeader(http.StatusNotFound)
    52  			return
    53  		}
    54  		if strings.Contains(file, "\\") {
    55  			resp.WriteHeader(http.StatusBadRequest)
    56  			return
    57  		}
    58  		file = "/" + file
    59  
    60  		var written bool
    61  		if opts.CorsHandler != nil {
    62  			written = true
    63  			opts.CorsHandler(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
    64  				written = false
    65  			})).ServeHTTP(resp, req)
    66  		}
    67  		if written {
    68  			return
    69  		}
    70  
    71  		// custom files
    72  		if opts.handle(resp, req, http.Dir(custPath), file) {
    73  			return
    74  		}
    75  
    76  		// internal files
    77  		if opts.handle(resp, req, fileSystem(opts.Directory), file) {
    78  			return
    79  		}
    80  
    81  		resp.WriteHeader(http.StatusNotFound)
    82  	}
    83  }
    84  
    85  // parseAcceptEncoding parse Accept-Encoding: deflate, gzip;q=1.0, *;q=0.5 as compress methods
    86  func parseAcceptEncoding(val string) container.Set[string] {
    87  	parts := strings.Split(val, ";")
    88  	types := make(container.Set[string])
    89  	for _, v := range strings.Split(parts[0], ",") {
    90  		types.Add(strings.TrimSpace(v))
    91  	}
    92  	return types
    93  }
    94  
    95  // setWellKnownContentType will set the Content-Type if the file is a well-known type.
    96  // See the comments of detectWellKnownMimeType
    97  func setWellKnownContentType(w http.ResponseWriter, file string) {
    98  	mimeType := detectWellKnownMimeType(filepath.Ext(file))
    99  	if mimeType != "" {
   100  		w.Header().Set("Content-Type", mimeType)
   101  	}
   102  }
   103  
   104  func (opts *Options) handle(w http.ResponseWriter, req *http.Request, fs http.FileSystem, file string) bool {
   105  	// use clean to keep the file is a valid path with no . or ..
   106  	f, err := fs.Open(path.Clean(file))
   107  	if err != nil {
   108  		if os.IsNotExist(err) {
   109  			return false
   110  		}
   111  		w.WriteHeader(http.StatusInternalServerError)
   112  		log.Error("[Static] Open %q failed: %v", file, err)
   113  		return true
   114  	}
   115  	defer f.Close()
   116  
   117  	fi, err := f.Stat()
   118  	if err != nil {
   119  		w.WriteHeader(http.StatusInternalServerError)
   120  		log.Error("[Static] %q exists, but fails to open: %v", file, err)
   121  		return true
   122  	}
   123  
   124  	// Try to serve index file
   125  	if fi.IsDir() {
   126  		w.WriteHeader(http.StatusNotFound)
   127  		return true
   128  	}
   129  
   130  	if httpcache.HandleFileETagCache(req, w, fi) {
   131  		return true
   132  	}
   133  
   134  	setWellKnownContentType(w, file)
   135  
   136  	serveContent(w, req, fi, fi.ModTime(), f)
   137  	return true
   138  }