gohttpserver/httpstaticserver.go

828 lines
19 KiB
Go
Raw Permalink Normal View History

2016-07-23 17:48:46 +02:00
package main
import (
2016-08-01 08:02:24 +02:00
"bytes"
2016-07-24 10:51:57 +02:00
"encoding/json"
2018-09-26 14:06:09 +02:00
"errors"
2018-09-25 09:59:38 +02:00
"fmt"
2018-09-14 15:03:27 +02:00
"html/template"
2016-07-27 16:22:16 +02:00
"io"
2016-07-26 13:30:59 +02:00
"io/ioutil"
2016-07-24 08:22:44 +02:00
"log"
2016-07-28 09:24:41 +02:00
"mime"
2016-07-23 17:48:46 +02:00
"net/http"
2016-07-27 08:10:03 +02:00
"net/url"
"os"
2016-07-24 08:22:44 +02:00
"path/filepath"
"strconv"
2016-07-28 09:24:41 +02:00
"strings"
"sync"
"time"
2016-07-23 17:48:46 +02:00
2016-12-21 08:47:03 +01:00
"regexp"
"github.com/go-yaml/yaml"
2016-07-23 17:48:46 +02:00
"github.com/gorilla/mux"
2017-05-16 15:34:30 +02:00
"github.com/shogo82148/androidbinary/apk"
2016-07-23 17:48:46 +02:00
)
const YAMLCONF = ".ghs.yml"
2017-05-16 15:34:30 +02:00
type ApkInfo struct {
PackageName string `json:"packageName"`
MainActivity string `json:"mainActivity"`
2017-05-17 04:28:00 +02:00
Version struct {
Code int `json:"code"`
Name string `json:"name"`
} `json:"version"`
2017-05-16 15:34:30 +02:00
}
type IndexFileItem struct {
Path string
Info os.FileInfo
}
2016-07-23 17:48:46 +02:00
type HTTPStaticServer struct {
2020-12-04 21:44:03 +01:00
Root string
Upload bool
Delete bool
Title string
Theme string
PlistProxy string
AuthType string
2016-07-26 16:00:22 +02:00
indexes []IndexFileItem
m *mux.Router
2021-03-30 11:04:23 +02:00
bufPool sync.Pool // use sync.Pool caching buf to reduce gc ratio
2016-07-23 17:48:46 +02:00
}
2016-07-27 16:22:16 +02:00
func NewHTTPStaticServer(root string) *HTTPStaticServer {
2016-07-24 08:22:44 +02:00
if root == "" {
2016-08-01 09:07:13 +02:00
root = "./"
2016-07-24 08:22:44 +02:00
}
root = filepath.ToSlash(root)
if !strings.HasSuffix(root, "/") {
root = root + "/"
}
2016-08-02 07:34:02 +02:00
log.Printf("root path: %s\n", root)
2016-07-23 17:48:46 +02:00
m := mux.NewRouter()
s := &HTTPStaticServer{
2016-07-26 08:38:42 +02:00
Root: root,
2016-07-27 16:22:16 +02:00
Theme: "black",
2016-07-26 08:38:42 +02:00
m: m,
2021-03-30 11:04:23 +02:00
bufPool: sync.Pool{
New: func() interface{} { return make([]byte, 32*1024) },
},
2016-07-23 17:48:46 +02:00
}
go func() {
2016-08-02 07:34:02 +02:00
time.Sleep(1 * time.Second)
for {
2016-08-02 07:34:02 +02:00
startTime := time.Now()
log.Println("Started making search index")
s.makeIndex()
2016-08-02 07:34:02 +02:00
log.Printf("Completed search index in %v", time.Since(startTime))
2016-07-31 16:28:09 +02:00
//time.Sleep(time.Second * 1)
time.Sleep(time.Minute * 10)
}
}()
2016-07-27 08:10:03 +02:00
// routers for Apple *.ipa
m.HandleFunc("/-/ipa/plist/{path:.*}", s.hPlist)
m.HandleFunc("/-/ipa/link/{path:.*}", s.hIpaLink)
2017-05-09 10:36:40 +02:00
2019-02-21 13:23:29 +01:00
m.HandleFunc("/{path:.*}", s.hIndex).Methods("GET", "HEAD")
m.HandleFunc("/{path:.*}", s.hUploadOrMkdir).Methods("POST")
m.HandleFunc("/{path:.*}", s.hDelete).Methods("DELETE")
2016-07-23 17:48:46 +02:00
return s
}
func (s *HTTPStaticServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.m.ServeHTTP(w, r)
}
2016-07-31 05:47:02 +02:00
func (s *HTTPStaticServer) hIndex(w http.ResponseWriter, r *http.Request) {
path := mux.Vars(r)["path"]
relPath := filepath.Join(s.Root, path)
2019-01-24 11:08:15 +01:00
if r.FormValue("json") == "true" {
s.hJSONList(w, r)
return
}
2019-02-21 13:23:29 +01:00
if r.FormValue("op") == "info" {
s.hInfo(w, r)
return
}
2019-02-22 10:23:32 +01:00
if r.FormValue("op") == "archive" {
s.hZip(w, r)
return
}
log.Println("GET", path, relPath)
2016-08-04 13:59:00 +02:00
if r.FormValue("raw") == "false" || isDir(relPath) {
2016-09-21 14:20:13 +02:00
if r.Method == "HEAD" {
return
}
2018-09-14 15:03:27 +02:00
renderHTML(w, "index.html", s)
2016-07-31 05:47:02 +02:00
} else {
if filepath.Base(path) == YAMLCONF {
auth := s.readAccessConf(path)
if !auth.Delete {
http.Error(w, "Security warning, not allowed to read", http.StatusForbidden)
return
}
}
if r.FormValue("download") == "true" {
w.Header().Set("Content-Disposition", "attachment; filename="+strconv.Quote(filepath.Base(path)))
}
2016-07-31 05:47:02 +02:00
http.ServeFile(w, r, relPath)
}
}
2018-09-14 15:03:27 +02:00
func (s *HTTPStaticServer) hMkdir(w http.ResponseWriter, req *http.Request) {
2019-02-21 08:41:09 +01:00
path := filepath.Dir(mux.Vars(req)["path"])
2018-09-14 15:03:27 +02:00
auth := s.readAccessConf(path)
if !auth.canDelete(req) {
http.Error(w, "Mkdir forbidden", http.StatusForbidden)
return
}
2019-02-21 08:41:09 +01:00
name := filepath.Base(mux.Vars(req)["path"])
2018-09-26 14:06:09 +02:00
if err := checkFilename(name); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
2018-09-14 15:03:27 +02:00
err := os.Mkdir(filepath.Join(s.Root, path, name), 0755)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
w.Write([]byte("Success"))
}
2016-08-03 08:54:26 +02:00
func (s *HTTPStaticServer) hDelete(w http.ResponseWriter, req *http.Request) {
path := mux.Vars(req)["path"]
path = filepath.Clean(path) // for safe reason, prevent path contain ..
2016-08-03 08:54:26 +02:00
auth := s.readAccessConf(path)
2016-12-22 04:46:29 +01:00
if !auth.canDelete(req) {
2016-08-03 08:54:26 +02:00
http.Error(w, "Delete forbidden", http.StatusForbidden)
return
}
// TODO: path safe check
err := os.RemoveAll(filepath.Join(s.Root, path))
2016-08-03 08:54:26 +02:00
if err != nil {
2019-02-22 10:23:32 +01:00
pathErr, ok := err.(*os.PathError)
if ok {
http.Error(w, pathErr.Op+" "+path+": "+pathErr.Err.Error(), 500)
} else {
http.Error(w, err.Error(), 500)
}
2016-08-03 08:54:26 +02:00
return
}
w.Write([]byte("Success"))
}
2019-02-21 13:23:29 +01:00
func (s *HTTPStaticServer) hUploadOrMkdir(w http.ResponseWriter, req *http.Request) {
2016-08-02 07:34:02 +02:00
path := mux.Vars(req)["path"]
2017-05-11 10:20:37 +02:00
dirpath := filepath.Join(s.Root, path)
2016-08-02 07:34:02 +02:00
// check auth
auth := s.readAccessConf(path)
2016-12-22 04:46:29 +01:00
if !auth.canUpload(req) {
2016-08-02 07:34:02 +02:00
http.Error(w, "Upload forbidden", http.StatusForbidden)
return
}
2017-05-11 10:20:37 +02:00
file, header, err := req.FormFile("file")
if _, err := os.Stat(dirpath); os.IsNotExist(err) {
if err := os.MkdirAll(dirpath, os.ModePerm); err != nil {
log.Println("Create directory:", err)
http.Error(w, "Directory create "+err.Error(), http.StatusInternalServerError)
return
}
2019-02-21 13:23:29 +01:00
}
if file == nil { // only mkdir
w.Header().Set("Content-Type", "application/json;charset=utf-8")
json.NewEncoder(w).Encode(map[string]interface{}{
"success": true,
"destination": dirpath,
})
return
}
2016-07-27 16:22:16 +02:00
if err != nil {
2017-05-11 10:47:17 +02:00
log.Println("Parse form file:", err)
2016-07-27 16:22:16 +02:00
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
2017-06-14 17:06:24 +02:00
defer func() {
file.Close()
req.MultipartForm.RemoveAll() // Seen from go source code, req.MultipartForm not nil after call FormFile(..)
}()
2018-09-18 07:38:41 +02:00
2018-09-26 14:06:09 +02:00
filename := req.FormValue("filename")
if filename == "" {
filename = header.Filename
}
if err := checkFilename(filename); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
dstPath := filepath.Join(dirpath, filename)
// Large file (>32MB) will store in tmp directory
// The quickest operation is call os.Move instead of os.Copy
// Note: it seems not working well, os.Rename might be failed
2021-03-23 15:26:42 +01:00
var copyErr error
2021-03-23 15:26:42 +01:00
// if osFile, ok := file.(*os.File); ok && fileExists(osFile.Name()) {
// tmpUploadPath := osFile.Name()
// osFile.Close() // Windows can not rename opened file
// log.Printf("Move %s -> %s", tmpUploadPath, dstPath)
// copyErr = os.Rename(tmpUploadPath, dstPath)
// } else {
dst, err := os.Create(dstPath)
if err != nil {
log.Println("Create file:", err)
http.Error(w, "File create "+err.Error(), http.StatusInternalServerError)
return
2016-07-27 16:22:16 +02:00
}
2021-03-30 11:04:23 +02:00
// Note: very large size file might cause poor performance
// _, copyErr = io.Copy(dst, file)
2021-03-30 11:04:23 +02:00
buf := s.bufPool.Get().([]byte)
defer s.bufPool.Put(buf)
2021-03-26 11:50:06 +01:00
_, copyErr = io.CopyBuffer(dst, file, buf)
2021-03-23 15:26:42 +01:00
dst.Close()
// }
if copyErr != nil {
2017-05-11 10:20:37 +02:00
log.Println("Handle upload file:", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
2016-07-27 16:22:16 +02:00
}
2019-02-14 10:58:22 +01:00
2017-06-14 17:06:24 +02:00
w.Header().Set("Content-Type", "application/json;charset=utf-8")
2019-02-14 10:58:22 +01:00
if req.FormValue("unzip") == "true" {
2019-02-15 10:28:46 +01:00
err = unzipFile(dstPath, dirpath)
os.Remove(dstPath)
message := "success"
if err != nil {
message = err.Error()
}
2019-02-14 10:58:22 +01:00
json.NewEncoder(w).Encode(map[string]interface{}{
"success": err == nil,
2019-02-15 10:28:46 +01:00
"description": message,
2019-02-14 10:58:22 +01:00
})
return
}
2019-02-15 10:28:46 +01:00
2017-06-14 17:06:24 +02:00
json.NewEncoder(w).Encode(map[string]interface{}{
"success": true,
"destination": dstPath,
})
2016-07-27 13:31:51 +02:00
}
2016-08-04 13:59:00 +02:00
type FileJSONInfo struct {
2017-05-16 15:41:41 +02:00
Name string `json:"name"`
Type string `json:"type"`
Size int64 `json:"size"`
Path string `json:"path"`
ModTime int64 `json:"mtime"`
Extra interface{} `json:"extra,omitempty"`
2016-08-04 13:59:00 +02:00
}
2017-05-17 08:59:32 +02:00
// path should be absolute
func parseApkInfo(path string) (ai *ApkInfo) {
defer func() {
if err := recover(); err != nil {
log.Println("parse-apk-info panic:", err)
}
}()
apkf, err := apk.OpenFile(path)
if err != nil {
return
}
ai = &ApkInfo{}
2018-09-25 08:35:41 +02:00
ai.MainActivity, _ = apkf.MainActivity()
2017-05-17 08:59:32 +02:00
ai.PackageName = apkf.PackageName()
ai.Version.Code = apkf.Manifest().VersionCode
ai.Version.Name = apkf.Manifest().VersionName
return
}
2016-08-04 13:59:00 +02:00
func (s *HTTPStaticServer) hInfo(w http.ResponseWriter, r *http.Request) {
path := mux.Vars(r)["path"]
relPath := filepath.Join(s.Root, path)
2019-02-21 08:41:09 +01:00
2016-08-04 13:59:00 +02:00
fi, err := os.Stat(relPath)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
fji := &FileJSONInfo{
Name: fi.Name(),
Size: fi.Size(),
Path: path,
ModTime: fi.ModTime().UnixNano() / 1e6,
}
2017-05-16 15:41:41 +02:00
ext := filepath.Ext(path)
switch ext {
case ".md":
2016-08-04 13:59:00 +02:00
fji.Type = "markdown"
2017-05-16 15:41:41 +02:00
case ".apk":
fji.Type = "apk"
2017-05-17 08:59:32 +02:00
fji.Extra = parseApkInfo(relPath)
2019-02-21 08:41:09 +01:00
case "":
2019-02-21 13:23:29 +01:00
fji.Type = "dir"
2017-05-16 15:41:41 +02:00
default:
2016-08-04 13:59:00 +02:00
fji.Type = "text"
}
data, _ := json.Marshal(fji)
w.Header().Set("Content-Type", "application/json")
w.Write(data)
}
2016-07-26 13:30:59 +02:00
func (s *HTTPStaticServer) hZip(w http.ResponseWriter, r *http.Request) {
path := mux.Vars(r)["path"]
2016-07-27 08:10:03 +02:00
CompressToZip(w, filepath.Join(s.Root, path))
}
2016-07-28 09:24:41 +02:00
func (s *HTTPStaticServer) hUnzip(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
zipPath, path := vars["zip_path"], vars["path"]
ctype := mime.TypeByExtension(filepath.Ext(path))
if ctype != "" {
w.Header().Set("Content-Type", ctype)
}
err := ExtractFromZip(filepath.Join(s.Root, zipPath), path, w)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
}
func combineURL(r *http.Request, path string) *url.URL {
2016-07-28 09:24:41 +02:00
return &url.URL{
Scheme: r.URL.Scheme,
2016-07-28 09:24:41 +02:00
Host: r.Host,
Path: path,
}
}
2016-07-27 08:10:03 +02:00
func (s *HTTPStaticServer) hPlist(w http.ResponseWriter, r *http.Request) {
path := mux.Vars(r)["path"]
// rename *.plist to *.ipa
if filepath.Ext(path) == ".plist" {
path = path[0:len(path)-6] + ".ipa"
}
relPath := filepath.Join(s.Root, path)
plinfo, err := parseIPA(relPath)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
2016-07-28 09:24:41 +02:00
2016-07-27 08:10:03 +02:00
scheme := "http"
if r.TLS != nil {
scheme = "https"
}
2016-07-28 09:24:41 +02:00
baseURL := &url.URL{
2016-07-27 08:10:03 +02:00
Scheme: scheme,
Host: r.Host,
}
2016-07-28 09:24:41 +02:00
data, err := generateDownloadPlist(baseURL, path, plinfo)
2016-07-27 08:10:03 +02:00
if err != nil {
http.Error(w, err.Error(), 500)
return
}
w.Header().Set("Content-Type", "text/xml")
w.Write(data)
2016-07-26 13:30:59 +02:00
}
2016-07-27 17:26:52 +02:00
func (s *HTTPStaticServer) hIpaLink(w http.ResponseWriter, r *http.Request) {
2016-07-28 09:24:41 +02:00
path := mux.Vars(r)["path"]
var plistUrl string
if r.URL.Scheme == "https" {
plistUrl = combineURL(r, "/-/ipa/plist/"+path).String()
} else if s.PlistProxy != "" {
2016-08-01 08:02:24 +02:00
httpPlistLink := "http://" + r.Host + "/-/ipa/plist/" + path
url, err := s.genPlistLink(httpPlistLink)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
plistUrl = url
} else {
http.Error(w, "500: Server should be https:// or provide valid plistproxy", 500)
return
2016-07-28 09:24:41 +02:00
}
2016-07-27 17:26:52 +02:00
2016-07-28 09:24:41 +02:00
w.Header().Set("Content-Type", "text/html")
2018-09-25 09:59:38 +02:00
log.Println("PlistURL:", plistUrl)
2018-09-14 15:03:27 +02:00
renderHTML(w, "ipa-install.html", map[string]string{
2016-07-28 09:24:41 +02:00
"Name": filepath.Base(path),
"PlistLink": plistUrl,
})
2016-07-27 17:26:52 +02:00
}
2016-08-01 08:02:24 +02:00
func (s *HTTPStaticServer) genPlistLink(httpPlistLink string) (plistUrl string, err error) {
// Maybe need a proxy, a little slowly now.
pp := s.PlistProxy
if pp == "" {
pp = defaultPlistProxy
}
resp, err := http.Get(httpPlistLink)
if err != nil {
return
}
defer resp.Body.Close()
data, _ := ioutil.ReadAll(resp.Body)
retData, err := http.Post(pp, "text/xml", bytes.NewBuffer(data))
if err != nil {
return
}
defer retData.Body.Close()
jsonData, _ := ioutil.ReadAll(retData.Body)
var ret map[string]string
if err = json.Unmarshal(jsonData, &ret); err != nil {
return
}
plistUrl = pp + "/" + ret["key"]
return
}
2016-07-24 08:22:44 +02:00
func (s *HTTPStaticServer) hFileOrDirectory(w http.ResponseWriter, r *http.Request) {
path := mux.Vars(r)["path"]
http.ServeFile(w, r, filepath.Join(s.Root, path))
2016-07-23 17:48:46 +02:00
}
2016-07-24 10:51:57 +02:00
2016-08-06 16:25:08 +02:00
type HTTPFileInfo struct {
Name string `json:"name"`
Path string `json:"path"`
Type string `json:"type"`
2016-08-05 16:15:57 +02:00
Size int64 `json:"size"`
ModTime int64 `json:"mtime"`
2016-07-24 10:51:57 +02:00
}
2016-12-21 08:47:03 +01:00
type AccessTable struct {
Regex string `yaml:"regex"`
Allow bool `yaml:"allow"`
}
2016-12-22 04:46:29 +01:00
type UserControl struct {
Email string
// Access bool
Upload bool
Delete bool
Token string
2016-12-22 04:46:29 +01:00
}
2016-08-02 07:34:02 +02:00
type AccessConf struct {
2016-12-21 08:47:03 +01:00
Upload bool `yaml:"upload" json:"upload"`
Delete bool `yaml:"delete" json:"delete"`
2016-12-22 04:46:29 +01:00
Users []UserControl `yaml:"users" json:"users"`
2016-12-21 08:47:03 +01:00
AccessTables []AccessTable `yaml:"accessTables"`
}
var reCache = make(map[string]*regexp.Regexp)
2016-12-22 04:46:29 +01:00
func (c *AccessConf) canAccess(fileName string) bool {
2016-12-21 08:47:03 +01:00
for _, table := range c.AccessTables {
pattern, ok := reCache[table.Regex]
if !ok {
pattern, _ = regexp.Compile(table.Regex)
reCache[table.Regex] = pattern
}
// skip wrong format regex
if pattern == nil {
continue
}
if pattern.MatchString(fileName) {
return table.Allow
}
}
return true
2016-08-02 07:34:02 +02:00
}
2016-12-22 04:46:29 +01:00
func (c *AccessConf) canDelete(r *http.Request) bool {
session, err := store.Get(r, defaultSessionName)
if err != nil {
return c.Delete
}
val := session.Values["user"]
if val == nil {
return c.Delete
}
userInfo := val.(*UserInfo)
for _, rule := range c.Users {
if rule.Email == userInfo.Email {
return rule.Delete
}
}
return c.Delete
}
func (c *AccessConf) canUploadByToken(token string) bool {
for _, rule := range c.Users {
if rule.Token == token {
return rule.Upload
}
}
return c.Upload
}
2016-12-22 04:46:29 +01:00
func (c *AccessConf) canUpload(r *http.Request) bool {
token := r.FormValue("token")
if token != "" {
return c.canUploadByToken(token)
}
2016-12-22 04:46:29 +01:00
session, err := store.Get(r, defaultSessionName)
if err != nil {
return c.Upload
}
val := session.Values["user"]
if val == nil {
return c.Upload
}
userInfo := val.(*UserInfo)
2016-12-22 04:46:29 +01:00
for _, rule := range c.Users {
if rule.Email == userInfo.Email {
return rule.Upload
}
}
return c.Upload
}
2016-07-24 10:51:57 +02:00
func (s *HTTPStaticServer) hJSONList(w http.ResponseWriter, r *http.Request) {
2016-07-31 05:47:02 +02:00
requestPath := mux.Vars(r)["path"]
localPath := filepath.Join(s.Root, requestPath)
search := r.FormValue("search")
2016-12-21 08:47:03 +01:00
auth := s.readAccessConf(requestPath)
2016-12-22 04:46:29 +01:00
auth.Upload = auth.canUpload(r)
auth.Delete = auth.canDelete(r)
2016-07-31 05:47:02 +02:00
// path string -> info os.FileInfo
fileInfoMap := make(map[string]os.FileInfo, 0)
if search != "" {
results := s.findIndex(search)
if len(results) > 50 { // max 50
results = results[:50]
}
for _, item := range results {
if filepath.HasPrefix(item.Path, requestPath) {
fileInfoMap[item.Path] = item.Info
2016-07-31 05:47:02 +02:00
}
}
} else {
2016-08-01 09:07:13 +02:00
infos, err := ioutil.ReadDir(localPath)
2016-07-31 05:47:02 +02:00
if err != nil {
http.Error(w, err.Error(), 500)
return
}
for _, info := range infos {
fileInfoMap[filepath.Join(requestPath, info.Name())] = info
}
}
2016-07-31 05:47:02 +02:00
2016-08-02 07:34:02 +02:00
// turn file list -> json
2016-08-06 16:25:08 +02:00
lrs := make([]HTTPFileInfo, 0)
2016-07-31 05:47:02 +02:00
for path, info := range fileInfoMap {
2016-12-22 04:46:29 +01:00
if !auth.canAccess(info.Name()) {
2016-12-21 08:47:03 +01:00
continue
}
2016-08-06 16:25:08 +02:00
lr := HTTPFileInfo{
Name: info.Name(),
Path: path,
ModTime: info.ModTime().UnixNano() / 1e6,
2016-07-31 05:47:02 +02:00
}
if search != "" {
name, err := filepath.Rel(requestPath, path)
if err != nil {
log.Println(requestPath, path, err)
}
lr.Name = filepath.ToSlash(name) // fix for windows
}
2016-07-31 05:47:02 +02:00
if info.IsDir() {
name := deepPath(localPath, info.Name())
lr.Name = name
lr.Path = filepath.Join(filepath.Dir(path), name)
lr.Type = "dir"
2017-05-17 04:28:00 +02:00
lr.Size = s.historyDirSize(lr.Path)
} else {
lr.Type = "file"
2016-08-05 16:15:57 +02:00
lr.Size = info.Size() // formatSize(info)
}
lrs = append(lrs, lr)
}
2016-08-02 07:34:02 +02:00
data, _ := json.Marshal(map[string]interface{}{
"files": lrs,
2016-12-21 08:47:03 +01:00
"auth": auth,
2016-08-02 07:34:02 +02:00
})
2016-07-24 10:51:57 +02:00
w.Header().Set("Content-Type", "application/json")
w.Write(data)
}
2016-07-26 13:30:59 +02:00
2017-05-17 04:28:00 +02:00
var dirSizeMap = make(map[string]int64)
func (s *HTTPStaticServer) makeIndex() error {
2016-07-31 16:28:09 +02:00
var indexes = make([]IndexFileItem, 0)
var err = filepath.Walk(s.Root, func(path string, info os.FileInfo, err error) error {
2016-08-24 05:26:10 +02:00
if err != nil {
log.Printf("WARN: Visit path: %s error: %v", strconv.Quote(path), err)
return filepath.SkipDir
// return err
2016-08-24 05:26:10 +02:00
}
if info.IsDir() {
return nil
}
2016-08-12 05:02:54 +02:00
path, _ = filepath.Rel(s.Root, path)
path = filepath.ToSlash(path)
2016-07-31 16:28:09 +02:00
indexes = append(indexes, IndexFileItem{path, info})
return nil
})
2016-07-31 16:28:09 +02:00
s.indexes = indexes
2017-05-17 04:28:00 +02:00
dirSizeMap = make(map[string]int64)
2016-07-31 16:28:09 +02:00
return err
}
2017-05-17 04:28:00 +02:00
func (s *HTTPStaticServer) historyDirSize(dir string) int64 {
var size int64
if size, ok := dirSizeMap[dir]; ok {
return size
}
for _, fitem := range s.indexes {
if filepath.HasPrefix(fitem.Path, dir) {
size += fitem.Info.Size()
}
}
dirSizeMap[dir] = size
return size
}
func (s *HTTPStaticServer) findIndex(text string) []IndexFileItem {
ret := make([]IndexFileItem, 0)
for _, item := range s.indexes {
ok := true
// search algorithm, space for AND
for _, keyword := range strings.Fields(text) {
2016-08-01 03:37:59 +02:00
needContains := true
if strings.HasPrefix(keyword, "-") {
needContains = false
keyword = keyword[1:]
}
if keyword == "" {
continue
}
ok = (needContains == strings.Contains(strings.ToLower(item.Path), strings.ToLower(keyword)))
if !ok {
break
}
}
if ok {
ret = append(ret, item)
}
}
return ret
}
2016-08-02 07:34:02 +02:00
func (s *HTTPStaticServer) defaultAccessConf() AccessConf {
return AccessConf{
Upload: s.Upload,
2017-05-17 08:59:32 +02:00
Delete: s.Delete,
2016-08-02 07:34:02 +02:00
}
}
func (s *HTTPStaticServer) readAccessConf(requestPath string) (ac AccessConf) {
2016-12-22 07:42:24 +01:00
requestPath = filepath.Clean(requestPath)
if requestPath == "/" || requestPath == "" || requestPath == "." {
ac = s.defaultAccessConf()
} else {
parentPath := filepath.Dir(requestPath)
ac = s.readAccessConf(parentPath)
}
2016-08-03 08:54:26 +02:00
relPath := filepath.Join(s.Root, requestPath)
if isFile(relPath) {
relPath = filepath.Dir(relPath)
}
cfgFile := filepath.Join(relPath, YAMLCONF)
2016-08-02 07:34:02 +02:00
data, err := ioutil.ReadFile(cfgFile)
if err != nil {
if os.IsNotExist(err) {
return
}
log.Printf("Err read .ghs.yml: %v", err)
}
err = yaml.Unmarshal(data, &ac)
if err != nil {
log.Printf("Err format .ghs.yml: %v", err)
}
return
}
2016-07-26 13:30:59 +02:00
func deepPath(basedir, name string) string {
isDir := true
// loop max 5, incase of for loop not finished
maxDepth := 5
for depth := 0; depth <= maxDepth && isDir; depth += 1 {
finfos, err := ioutil.ReadDir(filepath.Join(basedir, name))
if err != nil || len(finfos) != 1 {
break
}
if finfos[0].IsDir() {
name = filepath.ToSlash(filepath.Join(name, finfos[0].Name()))
2016-07-26 13:30:59 +02:00
} else {
break
}
}
return name
}
2016-08-03 08:54:26 +02:00
func isFile(path string) bool {
info, err := os.Stat(path)
return err == nil && info.Mode().IsRegular()
}
2016-08-04 13:59:00 +02:00
func isDir(path string) bool {
info, err := os.Stat(path)
return err == nil && info.Mode().IsDir()
}
2018-09-14 15:03:27 +02:00
func assetsContent(name string) string {
fd, err := Assets.Open(name)
if err != nil {
panic(err)
}
data, err := ioutil.ReadAll(fd)
if err != nil {
panic(err)
}
return string(data)
}
// TODO: I need to read more abouthtml/template
var (
2018-09-25 09:59:38 +02:00
funcMap template.FuncMap
)
2018-09-14 15:03:27 +02:00
2018-09-25 09:59:38 +02:00
func init() {
funcMap = template.FuncMap{
"title": strings.Title,
"urlhash": func(path string) string {
httpFile, err := Assets.Open(path)
if err != nil {
return path + "#no-such-file"
}
info, err := httpFile.Stat()
if err != nil {
return path + "#stat-error"
}
return fmt.Sprintf("%s?t=%d", path, info.ModTime().Unix())
},
2018-09-14 15:03:27 +02:00
}
2018-09-25 09:59:38 +02:00
}
var (
_tmpls = make(map[string]*template.Template)
)
func executeTemplate(w http.ResponseWriter, name string, v interface{}) {
if t, ok := _tmpls[name]; ok {
t.Execute(w, v)
return
2018-09-14 15:03:27 +02:00
}
2018-09-25 09:59:38 +02:00
t := template.Must(template.New(name).Funcs(funcMap).Delims("[[", "]]").Parse(assetsContent(name)))
_tmpls[name] = t
t.Execute(w, v)
2018-09-14 15:03:27 +02:00
}
func renderHTML(w http.ResponseWriter, name string, v interface{}) {
if _, ok := Assets.(http.Dir); ok {
log.Println("Hot load", name)
2018-09-25 09:59:38 +02:00
t := template.Must(template.New(name).Funcs(funcMap).Delims("[[", "]]").Parse(assetsContent(name)))
t.Execute(w, v)
} else {
2018-09-25 09:59:38 +02:00
executeTemplate(w, name, v)
}
2018-09-14 15:03:27 +02:00
}
2018-09-26 14:06:09 +02:00
func checkFilename(name string) error {
if strings.ContainsAny(name, "\\/:*<>|") {
return errors.New("Name should not contains \\/:*<>|")
}
return nil
}