Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e99a12b6c3 | ||
|
|
5a7724d542 | ||
|
|
f7063651c2 | ||
|
|
42d1e077c9 | ||
|
|
b6200a88c0 | ||
|
|
1618440f4f | ||
|
|
5367b9ea94 | ||
|
|
8e275e27be | ||
|
|
56e184748f | ||
|
|
b40bf8101f | ||
|
|
70e364c57b |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -29,3 +29,5 @@ bindata_assetfs.go
|
||||
assets_vfsdata.go
|
||||
*.un~
|
||||
*.swp
|
||||
|
||||
dist/
|
||||
|
||||
@@ -15,6 +15,7 @@ builds:
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
- "386"
|
||||
goarm:
|
||||
- "6"
|
||||
|
||||
10
README.md
10
README.md
@@ -1,6 +1,6 @@
|
||||
# gohttpserver
|
||||
[](https://travis-ci.org/codeskyblue/gohttpserver)
|
||||
[](https://microbadger.com/images/codeskyblue/gohttpserver "Get your own image badge on microbadger.com")
|
||||
[](https://hub.docker.com/repository/docker/codeskyblue/gohttpserver)
|
||||
|
||||
- Goal: Make the best HTTP File Server.
|
||||
- Features: Human-friendly UI, file uploading support, direct QR-code generation for Apple & Android install package.
|
||||
@@ -13,7 +13,7 @@
|
||||
**Binaries** can be downloaded from [this repo releases](https://github.com/codeskyblue/gohttpserver/releases/)
|
||||
|
||||
## Requirements
|
||||
Tested with go-1.10, go-1.11
|
||||
Tested with go-1.16
|
||||
|
||||
## Screenshots
|
||||

|
||||
@@ -66,6 +66,12 @@ go build && ./gohttpserver
|
||||
|
||||
Or download binaries from [github releases](https://github.com/codeskyblue/gohttpserver/releases)
|
||||
|
||||
If you are using Mac, simply run command
|
||||
|
||||
```bash
|
||||
brew install codeskyblue/tap/gohttpserver
|
||||
```
|
||||
|
||||
## Usage
|
||||
Listen on port 8000 of all interfaces, and enable file uploading.
|
||||
|
||||
|
||||
@@ -4,15 +4,16 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<title>[[.Title]]</title>
|
||||
<link rel="shortcut icon" type="image/png" href="/-/assets/favicon.png" />
|
||||
<link rel="stylesheet" type="text/css" href="/-/assets/bootstrap-3.3.5/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/assets/font-awesome-4.6.3/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/assets/css/github-markdown.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/assets/css/dropzone.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/assets/css/scrollUp-image.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/assets/css/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/assets/themes/[[.Theme]].css">
|
||||
<link rel="shortcut icon" type="image/png" href="[[.Prefix]]/-/assets/favicon.png" />
|
||||
<link rel="stylesheet" type="text/css" href="[[.Prefix]]/-/assets/bootstrap-3.3.5/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="[[.Prefix]]/-/assets/font-awesome-4.6.3/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="[[.Prefix]]/-/assets/css/github-markdown.css">
|
||||
<link rel="stylesheet" type="text/css" href="[[.Prefix]]/-/assets/css/dropzone.css">
|
||||
<link rel="stylesheet" type="text/css" href="[[.Prefix]]/-/assets/css/scrollUp-image.css">
|
||||
<link rel="stylesheet" type="text/css" href="[[.Prefix]]/-/assets/css/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="[[.Prefix]]/-/assets/themes/[[.Theme]].css">
|
||||
</head>
|
||||
|
||||
<body id="app">
|
||||
@@ -26,7 +27,7 @@
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="/">[[.Title]]</a>
|
||||
<a class="navbar-brand" href="[[.Prefix]]/">[[.Title]]</a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-2">
|
||||
<ul class="nav navbar-nav">
|
||||
@@ -256,23 +257,31 @@
|
||||
<div class="col-md-12">
|
||||
<div id="footer" class="pull-right" style="margin: 2em 1em">
|
||||
<a href="https://github.com/codeskyblue/gohttpserver">gohttpserver (ver:{{version}})</a>, written by <a href="https://github.com/codeskyblue">codeskyblue</a>.
|
||||
Copyright 2016-2018. go1.10
|
||||
Copyright 2016-2021. go1.16
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/-/assets/js/jquery-3.1.0.min.js"></script>
|
||||
<script src="/-/assets/js/jquery.qrcode.js"></script>
|
||||
<script src="/-/assets/js/jquery.scrollUp.min.js"></script>
|
||||
<script src="/-/assets/js/qrcode.js"></script>
|
||||
<script src="/-/assets/js/vue-1.0.min.js"></script>
|
||||
<script src="/-/assets/js/showdown-1.6.4.min.js"></script>
|
||||
<script src="/-/assets/js/moment.min.js"></script>
|
||||
<script src="/-/assets/js/dropzone.js"></script>
|
||||
<script src="/-/assets/js/underscore-min.js"></script>
|
||||
<script src="/-/assets/js/clipboard-1.5.12.min.js"></script>
|
||||
<script src="/-/assets/bootstrap-3.3.5/js/bootstrap.min.js"></script>
|
||||
<script src='/-/assets/[["js/index.js" | urlhash ]]'></script>
|
||||
<!-- <script src="/-/assets/js/index.js"></script> -->
|
||||
<script>
|
||||
window.URL_PFEFIX = "[[.Prefix]]"
|
||||
</script>
|
||||
<script src="[[.Prefix]]/-/assets/js/jquery-3.1.0.min.js"></script>
|
||||
<script src="[[.Prefix]]/-/assets/js/jquery.qrcode.js"></script>
|
||||
<script src="[[.Prefix]]/-/assets/js/jquery.scrollUp.min.js"></script>
|
||||
<script src="[[.Prefix]]/-/assets/js/qrcode.js"></script>
|
||||
<script src="[[.Prefix]]/-/assets/js/vue-1.0.min.js"></script>
|
||||
<script src="[[.Prefix]]/-/assets/js/showdown-1.6.4.min.js"></script>
|
||||
<script src="[[.Prefix]]/-/assets/js/moment.min.js"></script>
|
||||
<script src="[[.Prefix]]/-/assets/js/dropzone.js"></script>
|
||||
<script src="[[.Prefix]]/-/assets/js/underscore-min.js"></script>
|
||||
<script src="[[.Prefix]]/-/assets/js/clipboard-1.5.12.min.js"></script>
|
||||
<script src="[[.Prefix]]/-/assets/bootstrap-3.3.5/js/bootstrap.min.js"></script>
|
||||
<script src='[[.Prefix]]/-/assets/[["js/index.js" | urlhash ]]'></script>
|
||||
<!-- <script src="[[.Prefix]]/-/assets/js/index.js"></script> -->
|
||||
<!--Sync status bar color with border-color on mobile platforms.-->
|
||||
<script>
|
||||
var META = document.getElementsByTagName("meta");
|
||||
META[2]["content"]=$('.navbar').css('border-color');
|
||||
</script>
|
||||
[[if .GoogleTrackerID ]]
|
||||
<script>
|
||||
(function (i, s, o, g, r, a, m) {
|
||||
@@ -292,4 +301,4 @@
|
||||
</script> [[ end ]]
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -434,7 +434,7 @@ $(function () {
|
||||
loadFileList(location.pathname + location.search)
|
||||
|
||||
// update version
|
||||
$.getJSON("/-/sysinfo", function (res) {
|
||||
$.getJSON(URL_PREFIX + "/-/sysinfo", function (res) {
|
||||
vm.version = res.version;
|
||||
})
|
||||
|
||||
|
||||
31
assets/themes/cyan.css
Normal file
31
assets/themes/cyan.css
Normal file
@@ -0,0 +1,31 @@
|
||||
body {}
|
||||
|
||||
td>a:hover {
|
||||
color: #4cc0cf;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
td>a {
|
||||
color: rgb(51, 51, 51);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background-color: #00BCD4;
|
||||
border-color: #0097A7;
|
||||
}
|
||||
|
||||
.navbar .navbar-brand {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
.navbar-default ul.navbar-nav>li>a {
|
||||
color: white;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.13
|
||||
FROM golang:1.16
|
||||
WORKDIR /app/gohttpserver
|
||||
ADD . /app/gohttpserver
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags '-X main.VERSION=docker' -o gohttpserver
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.13
|
||||
FROM golang:1.16
|
||||
WORKDIR /appsrc/gohttpserver
|
||||
ADD . /appsrc/gohttpserver
|
||||
RUN GOOS=linux GOARCH=arm go build -ldflags '-X main.VERSION=docker' -o gohttpserver .
|
||||
|
||||
@@ -9,8 +9,12 @@ fi
|
||||
|
||||
set -ex
|
||||
|
||||
sed -i '/experimental/d' $HOME/.docker/config.json
|
||||
sed -i '1a"experimental": "enabled",' $HOME/.docker/config.json
|
||||
if test $(uname) = "Linux"
|
||||
then
|
||||
sed -i '/experimental/d' $HOME/.docker/config.json
|
||||
sed -i '1a"experimental": "enabled",' $HOME/.docker/config.json
|
||||
fi
|
||||
|
||||
docker manifest create codeskyblue/gohttpserver \
|
||||
codeskyblue/gohttpserver:latest \
|
||||
codeskyblue/gohttpserver:armhf
|
||||
@@ -21,4 +25,4 @@ docker manifest annotate codeskyblue/gohttpserver \
|
||||
docker manifest push codeskyblue/gohttpserver
|
||||
|
||||
# check again
|
||||
docker run mplatform/mquery codeskyblue/gohttpserver
|
||||
docker run mplatform/mquery codeskyblue/gohttpserver
|
||||
|
||||
4
go.mod
4
go.mod
@@ -1,5 +1,7 @@
|
||||
module github.com/codeskyblue/gohttpserver
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/alecthomas/kingpin v2.2.6+incompatible
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect
|
||||
@@ -23,5 +25,3 @@ require (
|
||||
golang.org/x/tools v0.1.0 // indirect
|
||||
howett.net/plist v0.0.0-20201203080718-1454fab16a06 // indirect
|
||||
)
|
||||
|
||||
go 1.13
|
||||
|
||||
@@ -43,6 +43,7 @@ type IndexFileItem struct {
|
||||
|
||||
type HTTPStaticServer struct {
|
||||
Root string
|
||||
Prefix string
|
||||
Upload bool
|
||||
Delete bool
|
||||
Title string
|
||||
@@ -56,10 +57,11 @@ type HTTPStaticServer struct {
|
||||
}
|
||||
|
||||
func NewHTTPStaticServer(root string) *HTTPStaticServer {
|
||||
if root == "" {
|
||||
root = "./"
|
||||
}
|
||||
root = filepath.ToSlash(root)
|
||||
// if root == "" {
|
||||
// root = "./"
|
||||
// }
|
||||
// root = filepath.ToSlash(root)
|
||||
root = filepath.ToSlash(filepath.Clean(root))
|
||||
if !strings.HasSuffix(root, "/") {
|
||||
root = root + "/"
|
||||
}
|
||||
@@ -97,9 +99,24 @@ func (s *HTTPStaticServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.m.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// Return real path with Seperator(/)
|
||||
func (s *HTTPStaticServer) getRealPath(r *http.Request) string {
|
||||
path := mux.Vars(r)["path"]
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
path = filepath.Clean(path) // prevent .. for safe issues
|
||||
relativePath, err := filepath.Rel(s.Prefix, path)
|
||||
if err != nil {
|
||||
relativePath = path
|
||||
}
|
||||
realPath := filepath.Join(s.Root, relativePath)
|
||||
return filepath.ToSlash(realPath)
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) hIndex(w http.ResponseWriter, r *http.Request) {
|
||||
path := mux.Vars(r)["path"]
|
||||
relPath := filepath.Join(s.Root, path)
|
||||
realPath := s.getRealPath(r)
|
||||
if r.FormValue("json") == "true" {
|
||||
s.hJSONList(w, r)
|
||||
return
|
||||
@@ -115,15 +132,15 @@ func (s *HTTPStaticServer) hIndex(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("GET", path, relPath)
|
||||
if r.FormValue("raw") == "false" || isDir(relPath) {
|
||||
log.Println("GET", path, realPath)
|
||||
if r.FormValue("raw") == "false" || isDir(realPath) {
|
||||
if r.Method == "HEAD" {
|
||||
return
|
||||
}
|
||||
renderHTML(w, "index.html", s)
|
||||
} else {
|
||||
if filepath.Base(path) == YAMLCONF {
|
||||
auth := s.readAccessConf(path)
|
||||
auth := s.readAccessConf(realPath)
|
||||
if !auth.Delete {
|
||||
http.Error(w, "Security warning, not allowed to read", http.StatusForbidden)
|
||||
return
|
||||
@@ -132,41 +149,22 @@ func (s *HTTPStaticServer) hIndex(w http.ResponseWriter, r *http.Request) {
|
||||
if r.FormValue("download") == "true" {
|
||||
w.Header().Set("Content-Disposition", "attachment; filename="+strconv.Quote(filepath.Base(path)))
|
||||
}
|
||||
http.ServeFile(w, r, relPath)
|
||||
http.ServeFile(w, r, realPath)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) hMkdir(w http.ResponseWriter, req *http.Request) {
|
||||
path := filepath.Dir(mux.Vars(req)["path"])
|
||||
auth := s.readAccessConf(path)
|
||||
if !auth.canDelete(req) {
|
||||
http.Error(w, "Mkdir forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
name := filepath.Base(mux.Vars(req)["path"])
|
||||
if err := checkFilename(name); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
err := os.Mkdir(filepath.Join(s.Root, path, name), 0755)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
w.Write([]byte("Success"))
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) hDelete(w http.ResponseWriter, req *http.Request) {
|
||||
// only can delete file now
|
||||
path := mux.Vars(req)["path"]
|
||||
auth := s.readAccessConf(path)
|
||||
realPath := s.getRealPath(req)
|
||||
// path = filepath.Clean(path) // for safe reason, prevent path contain ..
|
||||
auth := s.readAccessConf(realPath)
|
||||
if !auth.canDelete(req) {
|
||||
http.Error(w, "Delete forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
err := os.Remove(filepath.Join(s.Root, path))
|
||||
// TODO: path safe check
|
||||
err := os.RemoveAll(realPath)
|
||||
if err != nil {
|
||||
pathErr, ok := err.(*os.PathError)
|
||||
if ok {
|
||||
@@ -180,11 +178,10 @@ func (s *HTTPStaticServer) hDelete(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) hUploadOrMkdir(w http.ResponseWriter, req *http.Request) {
|
||||
path := mux.Vars(req)["path"]
|
||||
dirpath := filepath.Join(s.Root, path)
|
||||
dirpath := s.getRealPath(req)
|
||||
|
||||
// check auth
|
||||
auth := s.readAccessConf(path)
|
||||
auth := s.readAccessConf(dirpath)
|
||||
if !auth.canUpload(req) {
|
||||
http.Error(w, "Upload forbidden", http.StatusForbidden)
|
||||
return
|
||||
@@ -232,7 +229,7 @@ func (s *HTTPStaticServer) hUploadOrMkdir(w http.ResponseWriter, req *http.Reque
|
||||
|
||||
// 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
|
||||
// Note: it seems not working well, os.Rename might be failed
|
||||
|
||||
var copyErr error
|
||||
// if osFile, ok := file.(*os.File); ok && fileExists(osFile.Name()) {
|
||||
@@ -308,7 +305,7 @@ func parseApkInfo(path string) (ai *ApkInfo) {
|
||||
|
||||
func (s *HTTPStaticServer) hInfo(w http.ResponseWriter, r *http.Request) {
|
||||
path := mux.Vars(r)["path"]
|
||||
relPath := filepath.Join(s.Root, path)
|
||||
relPath := s.getRealPath(r)
|
||||
|
||||
fi, err := os.Stat(relPath)
|
||||
if err != nil {
|
||||
@@ -339,8 +336,7 @@ func (s *HTTPStaticServer) hInfo(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) hZip(w http.ResponseWriter, r *http.Request) {
|
||||
path := mux.Vars(r)["path"]
|
||||
CompressToZip(w, filepath.Join(s.Root, path))
|
||||
CompressToZip(w, s.getRealPath(r))
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) hUnzip(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -372,7 +368,7 @@ func (s *HTTPStaticServer) hPlist(w http.ResponseWriter, r *http.Request) {
|
||||
path = path[0:len(path)-6] + ".ipa"
|
||||
}
|
||||
|
||||
relPath := filepath.Join(s.Root, path)
|
||||
relPath := s.getRealPath(r)
|
||||
plinfo, err := parseIPA(relPath)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
@@ -452,8 +448,7 @@ func (s *HTTPStaticServer) genPlistLink(httpPlistLink string) (plistUrl string,
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) hFileOrDirectory(w http.ResponseWriter, r *http.Request) {
|
||||
path := mux.Vars(r)["path"]
|
||||
http.ServeFile(w, r, filepath.Join(s.Root, path))
|
||||
http.ServeFile(w, r, s.getRealPath(r))
|
||||
}
|
||||
|
||||
type HTTPFileInfo struct {
|
||||
@@ -556,9 +551,9 @@ func (c *AccessConf) canUpload(r *http.Request) bool {
|
||||
|
||||
func (s *HTTPStaticServer) hJSONList(w http.ResponseWriter, r *http.Request) {
|
||||
requestPath := mux.Vars(r)["path"]
|
||||
localPath := filepath.Join(s.Root, requestPath)
|
||||
realPath := s.getRealPath(r)
|
||||
search := r.FormValue("search")
|
||||
auth := s.readAccessConf(requestPath)
|
||||
auth := s.readAccessConf(realPath)
|
||||
auth.Upload = auth.canUpload(r)
|
||||
auth.Delete = auth.canDelete(r)
|
||||
|
||||
@@ -576,7 +571,7 @@ func (s *HTTPStaticServer) hJSONList(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
infos, err := ioutil.ReadDir(localPath)
|
||||
infos, err := ioutil.ReadDir(realPath)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
@@ -605,7 +600,7 @@ func (s *HTTPStaticServer) hJSONList(w http.ResponseWriter, r *http.Request) {
|
||||
lr.Name = filepath.ToSlash(name) // fix for windows
|
||||
}
|
||||
if info.IsDir() {
|
||||
name := deepPath(localPath, info.Name())
|
||||
name := deepPath(realPath, info.Name())
|
||||
lr.Name = name
|
||||
lr.Path = filepath.Join(filepath.Dir(path), name)
|
||||
lr.Type = "dir"
|
||||
@@ -696,19 +691,19 @@ func (s *HTTPStaticServer) defaultAccessConf() AccessConf {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) readAccessConf(requestPath string) (ac AccessConf) {
|
||||
requestPath = filepath.Clean(requestPath)
|
||||
if requestPath == "/" || requestPath == "" || requestPath == "." {
|
||||
func (s *HTTPStaticServer) readAccessConf(realPath string) (ac AccessConf) {
|
||||
relativePath, err := filepath.Rel(s.Root, realPath)
|
||||
if err != nil || relativePath == "." || relativePath == "" { // actually relativePath is always "." if root == realPath
|
||||
ac = s.defaultAccessConf()
|
||||
realPath = s.Root
|
||||
} else {
|
||||
parentPath := filepath.Dir(requestPath)
|
||||
parentPath := filepath.Dir(realPath)
|
||||
ac = s.readAccessConf(parentPath)
|
||||
}
|
||||
relPath := filepath.Join(s.Root, requestPath)
|
||||
if isFile(relPath) {
|
||||
relPath = filepath.Dir(relPath)
|
||||
if isFile(realPath) {
|
||||
realPath = filepath.Dir(realPath)
|
||||
}
|
||||
cfgFile := filepath.Join(relPath, YAMLCONF)
|
||||
cfgFile := filepath.Join(realPath, YAMLCONF)
|
||||
data, err := ioutil.ReadFile(cfgFile)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
@@ -741,16 +736,6 @@ func deepPath(basedir, name string) string {
|
||||
return name
|
||||
}
|
||||
|
||||
func isFile(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
return err == nil && info.Mode().IsRegular()
|
||||
}
|
||||
|
||||
func isDir(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
return err == nil && info.Mode().IsDir()
|
||||
}
|
||||
|
||||
func assetsContent(name string) string {
|
||||
fd, err := Assets.Open(name)
|
||||
if err != nil {
|
||||
|
||||
52
main.go
52
main.go
@@ -10,6 +10,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -20,6 +21,7 @@ import (
|
||||
"github.com/go-yaml/yaml"
|
||||
"github.com/goji/httpauth"
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
_ "github.com/shurcooL/vfsgen"
|
||||
)
|
||||
|
||||
@@ -28,6 +30,7 @@ type Configure struct {
|
||||
Addr string `yaml:"addr"`
|
||||
Port int `yaml:"port"`
|
||||
Root string `yaml:"root"`
|
||||
Prefix string `yaml:"prefix"`
|
||||
HTTPAuth string `yaml:"httpauth"`
|
||||
Cert string `yaml:"cert"`
|
||||
Key string `yaml:"key"`
|
||||
@@ -102,6 +105,7 @@ func parseFlags() error {
|
||||
kingpin.Version(versionMessage())
|
||||
kingpin.Flag("conf", "config file path, yaml format").FileVar(&gcfg.Conf)
|
||||
kingpin.Flag("root", "root directory, default ./").Short('r').StringVar(&gcfg.Root)
|
||||
kingpin.Flag("prefix", "url prefix, eg /foo").StringVar(&gcfg.Prefix)
|
||||
kingpin.Flag("port", "listen port, default 8000").IntVar(&gcfg.Port)
|
||||
kingpin.Flag("addr", "listen address, eg 127.0.0.1:8000").Short('a').StringVar(&gcfg.Addr)
|
||||
kingpin.Flag("cert", "tls cert.pem path").StringVar(&gcfg.Cert)
|
||||
@@ -134,6 +138,17 @@ func parseFlags() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func fixPrefix(prefix string) string {
|
||||
prefix = regexp.MustCompile(`/*$`).ReplaceAllString(prefix, "")
|
||||
if !strings.HasPrefix(prefix, "/") {
|
||||
prefix = "/" + prefix
|
||||
}
|
||||
if prefix == "/" {
|
||||
prefix = ""
|
||||
}
|
||||
return prefix
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := parseFlags(); err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -144,7 +159,14 @@ func main() {
|
||||
}
|
||||
log.SetFlags(log.Lshortfile | log.LstdFlags)
|
||||
|
||||
// make sure prefix matches: ^/.*[^/]$
|
||||
gcfg.Prefix = fixPrefix(gcfg.Prefix)
|
||||
if gcfg.Prefix != "" {
|
||||
log.Printf("url prefix: %s", gcfg.Prefix)
|
||||
}
|
||||
|
||||
ss := NewHTTPStaticServer(gcfg.Root)
|
||||
ss.Prefix = gcfg.Prefix
|
||||
ss.Theme = gcfg.Theme
|
||||
ss.Title = gcfg.Title
|
||||
ss.GoogleTrackerID = gcfg.GoogleTrackerID
|
||||
@@ -163,7 +185,7 @@ func main() {
|
||||
if ss.PlistProxy != "" {
|
||||
log.Printf("plistproxy: %s", strconv.Quote(ss.PlistProxy))
|
||||
}
|
||||
|
||||
|
||||
var hdlr http.Handler = ss
|
||||
|
||||
hdlr = accesslog.NewLoggingHandler(hdlr, logger)
|
||||
@@ -192,15 +214,26 @@ func main() {
|
||||
hdlr = handlers.ProxyHeaders(hdlr)
|
||||
}
|
||||
|
||||
http.Handle("/", hdlr)
|
||||
http.Handle("/-/assets/", http.StripPrefix("/-/assets/", http.FileServer(Assets)))
|
||||
http.HandleFunc("/-/sysinfo", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
mainRooter := mux.NewRouter()
|
||||
rooter := mainRooter
|
||||
if gcfg.Prefix != "" {
|
||||
rooter = mainRooter.PathPrefix(gcfg.Prefix).Subrouter()
|
||||
mainRooter.Handle(gcfg.Prefix, hdlr)
|
||||
mainRooter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, gcfg.Prefix, http.StatusTemporaryRedirect)
|
||||
})
|
||||
}
|
||||
|
||||
rooter.PathPrefix("/-/assets/").Handler(http.StripPrefix(gcfg.Prefix+"/-/assets/", http.FileServer(Assets)))
|
||||
rooter.HandleFunc("/-/sysinfo", func(w http.ResponseWriter, r *http.Request) {
|
||||
data, _ := json.Marshal(map[string]interface{}{
|
||||
"version": VERSION,
|
||||
})
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(data)))
|
||||
w.Write(data)
|
||||
})
|
||||
rooter.PathPrefix("/").Handler(hdlr)
|
||||
|
||||
if gcfg.Addr == "" {
|
||||
gcfg.Addr = fmt.Sprintf(":%d", gcfg.Port)
|
||||
@@ -211,11 +244,16 @@ func main() {
|
||||
_, port, _ := net.SplitHostPort(gcfg.Addr)
|
||||
log.Printf("listening on %s, local address http://%s:%s\n", strconv.Quote(gcfg.Addr), getLocalIP(), port)
|
||||
|
||||
srv := &http.Server{
|
||||
Handler: mainRooter,
|
||||
Addr: gcfg.Addr,
|
||||
}
|
||||
|
||||
var err error
|
||||
if gcfg.Key != "" && gcfg.Cert != "" {
|
||||
err = http.ListenAndServeTLS(gcfg.Addr, gcfg.Cert, gcfg.Key, nil)
|
||||
err = srv.ListenAndServeTLS(gcfg.Cert, gcfg.Key)
|
||||
} else {
|
||||
err = http.ListenAndServe(gcfg.Addr, nil)
|
||||
err = srv.ListenAndServe()
|
||||
}
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
16
utils.go
16
utils.go
@@ -4,6 +4,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -80,3 +81,18 @@ func fileExists(path string) bool {
|
||||
}
|
||||
return !info.IsDir()
|
||||
}
|
||||
|
||||
// Convert path to normal paths
|
||||
func cleanPath(path string) string {
|
||||
return filepath.ToSlash(filepath.Clean(path))
|
||||
}
|
||||
|
||||
func isFile(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
return err == nil && info.Mode().IsRegular()
|
||||
}
|
||||
|
||||
func isDir(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
return err == nil && info.Mode().IsDir()
|
||||
}
|
||||
|
||||
@@ -25,3 +25,25 @@ func TestSublimeContains(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanPath(t *testing.T) {
|
||||
tests := []struct {
|
||||
orig string
|
||||
expect string
|
||||
}{
|
||||
// {"C:\\hello", "C:/hello"}, // Only works in windows
|
||||
{"", "."},
|
||||
{"//../foo", "/foo"},
|
||||
{"/../../", "/"},
|
||||
{"/hello/world/..", "/hello"},
|
||||
{"/..", "/"},
|
||||
{"/foo/..", "/"},
|
||||
{"/-/foo", "/-/foo"},
|
||||
}
|
||||
for _, v := range tests {
|
||||
res := cleanPath(v.orig)
|
||||
if res != v.expect {
|
||||
t.Fatalf("Clean path(%v) expect(%v) but got(%v)", v.orig, v.expect, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user