76 Commits

Author SHA1 Message Date
孙圣翔²⁰₂₁
fdd6ff9435 fix for goreleaser 2024-12-04 08:57:52 +08:00
codeskyblue
29bd44ac0c Merge pull request #212 from black-06/master
feat: support for multiple http users
2024-12-04 08:36:52 +08:00
codeskyblue
4b9be2fd12 Merge pull request #210 from NaturezzZ/master
[feat] directly play video
2024-12-04 08:33:57 +08:00
black-06
2d7a0f1aaf feat: support for multiple http users 2024-10-28 19:20:44 +08:00
NaturezzZ
23f70c844a [feat] directly play video 2024-10-19 14:36:26 +08:00
codeskyblue
6d8af0af8f Merge pull request #202 from codeskyblue/dependabot/go_modules/golang.org/x/net-0.23.0 2024-05-14 13:55:34 +08:00
dependabot[bot]
189100b3af Bump golang.org/x/net from 0.17.0 to 0.23.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.17.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.17.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-19 12:32:20 +00:00
codeskyblue
75eb27f9c9 merge pr 2023-12-19 11:43:27 +08:00
dependabot[bot]
e31775af89 Bump golang.org/x/net from 0.7.0 to 0.17.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.7.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.7.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-19 11:41:36 +08:00
Gary Marigliano
2a0e1f7400 Add no-index option to disable indexing and search 2023-12-19 11:41:18 +08:00
dependabot[bot]
dbd0e06c73 Bump golang.org/x/net from 0.0.0-20201021035429-f5854403a974 to 0.7.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.0.0-20201021035429-f5854403a974 to 0.7.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/commits/v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-19 11:38:34 +08:00
Kushal Azim Ekram
bd2d79953d Added alpine and upx based container build. Much smaller size ~14mb 2023-12-19 11:38:34 +08:00
Jiawen Geng
dd8692b656 fix docker build
3.612 E: Failed to fetch http://deb.debian.org/debian/dists/stretch-updates/main/binary-amd64/Packages  404  Not Found [IP: 151.101.202.132 80]
3.612 E: Some index files failed to download. They have been ignored, or old ones used instead.
------
Dockerfile:10
--------------------
   8 |     WORKDIR /app
   9 |     RUN mkdir -p /app/public
  10 | >>> RUN apt-get update && apt-get install -y ca-certificates
2023-12-19 11:38:34 +08:00
codeskyblue
eacf2d1dc3 cors enable by default 2023-12-19 11:38:34 +08:00
codeskyblue
08e2cf764d limit cors only get, head, options 2023-12-19 11:38:34 +08:00
codeskyblue
a400a028ff Merge pull request #193 from codeskyblue/dependabot/go_modules/golang.org/x/net-0.17.0
Bump golang.org/x/net from 0.7.0 to 0.17.0
2023-12-19 11:17:21 +08:00
codeskyblue
f2fb06ae18 Merge pull request #174 from krypty/add-option-to-disable-index
Add no-index option to disable indexing and search
2023-12-19 11:16:48 +08:00
dependabot[bot]
71957e4947 Bump golang.org/x/net from 0.7.0 to 0.17.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.7.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.7.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-19 03:15:17 +00:00
codeskyblue
5fab10ed10 Merge pull request #170 from codeskyblue/dependabot/go_modules/golang.org/x/net-0.7.0
Bump golang.org/x/net from 0.0.0-20201021035429-f5854403a974 to 0.7.0
2023-12-19 11:14:42 +08:00
codeskyblue
30e4b86595 Merge pull request #173 from kforeverisback/alpine-small-build
Smaller Build using Alpine and UPX
2023-12-19 11:12:10 +08:00
codeskyblue
5bcbcd0bac Merge pull request #185 from gengjiawen/patch-3
fix docker build
2023-12-19 11:11:14 +08:00
codeskyblue
2a5610bb74 Merge pull request #192 from codeskyblue/add-cors
cors enable by default
2023-12-19 11:07:02 +08:00
codeskyblue
b86e0e623e cors enable by default 2023-12-19 11:05:52 +08:00
codeskyblue
bca7b842f5 Merge pull request #191 from codeskyblue/limit-cors
limit cors only get, head, options
2023-12-19 10:38:43 +08:00
codeskyblue
ea7272437d limit cors only get, head, options 2023-12-19 10:37:39 +08:00
Jiawen Geng
6de7ca4676 fix docker build
3.612 E: Failed to fetch http://deb.debian.org/debian/dists/stretch-updates/main/binary-amd64/Packages  404  Not Found [IP: 151.101.202.132 80]
3.612 E: Some index files failed to download. They have been ignored, or old ones used instead.
------
Dockerfile:10
--------------------
   8 |     WORKDIR /app
   9 |     RUN mkdir -p /app/public
  10 | >>> RUN apt-get update && apt-get install -y ca-certificates
2023-10-07 16:54:56 +08:00
Gary Marigliano
88433ea55e Add no-index option to disable indexing and search 2023-03-13 10:17:42 +01:00
Kushal Azim Ekram
d874ba59cf Added alpine and upx based container build. Much smaller size ~14mb 2023-03-11 21:16:26 -08:00
Gary Marigliano
5dc0040375 Make deep path depth configurable
Allow to configure with --deep-path-max-depth the depth of paths to combine
when listing the folders. The default is still 5.
2023-03-09 11:48:20 +01:00
dependabot[bot]
034215a031 Bump golang.org/x/net from 0.0.0-20201021035429-f5854403a974 to 0.7.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.0.0-20201021035429-f5854403a974 to 0.7.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/commits/v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-25 01:35:46 +00:00
codeskyblue
80c9e79869 update again 2022-07-26 19:23:30 +08:00
codeskyblue
2adc57be6c change ci from travis to github actions 2022-07-26 18:44:49 +08:00
codeskyblue
6829892be4 update go build version 2022-07-26 18:33:24 +08:00
codeskyblue
26545513de fix #137 2022-05-23 18:19:46 +08:00
codeskyblue
342d9cc14c Merge pull request #125 from maxunof/master
Migration to go:embed and some other changes
2021-09-14 14:22:54 +08:00
codeskyblue
ed85a9687b Merge pull request #126 from miguelnv/issue#117-error-fetching-large-folders
issue#117-error-fetching-large-folders
2021-09-13 22:29:55 +08:00
Miguel Vieira
fe8dcd76c1 thread-safe concurrent file size info map 2021-09-05 03:34:26 +01:00
Max
230bc3d182 so now we can use go install 2021-08-20 11:34:30 +00:00
Max
28a90b90dd fix version load 2021-08-20 11:26:15 +00:00
Max
e607da8ec2 make footer more pretty 2021-08-20 11:26:00 +00:00
Max
e47bdd1272 migrate from vfs to go:embed 2021-08-20 11:24:42 +00:00
Max
ff821dcb63 fix typo in readme (#122) 2021-08-20 13:18:03 +03:00
codeskyblue
8887f4ef69 Merge pull request #124 from maxunof/master
fix readme
2021-08-20 16:36:54 +08:00
Max
bd1a35541a fix readme x2 2021-08-19 17:47:15 +03:00
Max
56045c6c8e fix readme 2021-08-19 16:48:45 +03:00
codeskyblue
b5bd6779f9 fix #105 2021-04-06 10:52:25 +08:00
codeskyblue
8665999fc0 Merge branch 'prefix' 2021-04-06 10:43:01 +08:00
codeskyblue
677c5a7cec update names 2021-04-06 10:42:49 +08:00
codeskyblue
41981a3d8a update readme 2021-04-01 11:16:51 +08:00
codeskyblue
47789eec68 Merge branch 'aeoluswing-master' 2021-03-30 17:04:43 +08:00
codeskyblue
4c880193ce merge pull request 2021-03-30 17:04:23 +08:00
codeskyblue
86aeecfac2 Merge branch 'master' of https://github.com/aeoluswing/gohttpserver into aeoluswing-master 2021-03-30 16:47:34 +08:00
codeskyblue
e99a12b6c3 fix some prefix error 2021-03-30 13:37:30 +08:00
aeolus
e8af97872c Merge pull request #2 from aeoluswing/fix-uplimit-20210326
Fix the assignment of sync buf
2021-03-26 18:58:16 +08:00
aeoluswing
d7590729f1 Fix the assignment of sync buf 2021-03-26 18:50:06 +08:00
codeskyblue
5a7724d542 support prefix, need more test 2021-03-26 18:11:33 +08:00
codeskyblue
f7063651c2 update docker badge 2021-03-26 15:57:47 +08:00
codeskyblue
42d1e077c9 merge theme 2021-03-26 15:41:28 +08:00
aeolus
1258ac759f Merge pull request #1 from aeoluswing/fix-uplimit-20210326
Optimize the performance of the copy phase during the upload process
2021-03-26 14:18:29 +08:00
aeoluswing
a60a9c4811 Optimize the performance of the copy phase during the upload process 2021-03-26 14:03:54 +08:00
codeskyblue
b6200a88c0 support delete non-empty folder, close #97 2021-03-24 22:23:09 +08:00
codeskyblue
1618440f4f update readme 2021-03-24 15:02:18 +08:00
codeskyblue
f676d25787 fix goreleaser again 2021-03-24 11:18:55 +08:00
codeskyblue
e24ff056f7 fix goreleaser again 2021-03-24 11:09:37 +08:00
codeskyblue
833443dfaf fix goreleaser 2021-03-24 11:06:45 +08:00
codeskyblue
b6f8563d18 Merge branch 'mahuiplus-master' 2021-03-24 10:38:52 +08:00
codeskyblue
20ed231c53 Merge branch 'master' of https://github.com/mahuiplus/gohttpserver into mahuiplus-master 2021-03-24 10:36:52 +08:00
codeskyblue
b4470cf4bd fix upload large file >32M, close #98 2021-03-23 22:26:42 +08:00
hathlife
5367b9ea94 Update travis.yml 2020-04-27 15:53:43 +08:00
hathlife
8e275e27be Edit README. 2020-04-27 15:40:54 +08:00
hathlife
56e184748f Add Cyan theme. 2020-04-27 15:40:54 +08:00
hathlife
b40bf8101f Sync status bar color with border-color on mobile platforms. 2020-04-27 15:40:30 +08:00
hathlife
70e364c57b Use Go 1.14 & Update copyright string. 2020-03-25 21:21:36 +08:00
MaHui
98b09cb27f fix: 修复问题 2019-08-10 12:21:51 +08:00
MaHui
aad95ae813 fix: 修复问题 2019-08-08 08:38:53 +08:00
MaHui
2bd7ec5cd0 fix: issue#49 2019-08-05 19:09:14 +08:00
24 changed files with 725 additions and 348 deletions

34
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: goreleaser
on:
push:
# run only against tags
tags:
- '*'
permissions:
contents: write
# packages: write
# issues: write
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Fetch all tags
run: git fetch --force --tags
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.18
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
# either 'goreleaser' (default) or 'goreleaser-pro'
distribution: goreleaser
version: latest
args: release
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}

3
.gitignore vendored
View File

@@ -29,3 +29,6 @@ bindata_assetfs.go
assets_vfsdata.go
*.un~
*.swp
dist/
.DS_Store

View File

@@ -1,44 +1,32 @@
project_name: gohttpserver
release:
github:
owner: codeskyblue
name: gohttpserver
brew:
github:
owner: codeskyblue
name: homebrew-tap
homepage: https://github.com/codeskyblue/gohttpserver
version: 2
before:
hooks:
- go mod tidy
builds:
- goos:
- env:
- CGO_ENABLED=0
main: .
ldflags: -s -w -X main.VERSION={{.Version}} -X main.BUILDTIME={{.Date}} -X main.GITCOMMIT={{.Commit}}
goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm64
- "386"
goarm:
- "6"
main: .
ldflags: -s -w -X main.VERSION={{.Version}}
flags: -tags vfs
binary: gohttpserver
hooks:
pre: go generate .
archive:
archives:
- format: tar.gz
# use zip for windows archives
format_overrides:
- goos: windows
format: zip
name_template: '{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{
.Arm }}{{ end }}'
files:
- licence*
- LICENCE*
- license*
- LICENSE*
- readme*
- README*
- changelog*
- CHANGELOG*
- .ghs.yml
snapshot:
name_template: SNAPSHOT-{{ .Commit }}
checksum:
name_template: '{{ .ProjectName }}_{{ .Version }}_checksums.txt'
# brews:
# - tap:
# owner: codeskyblue
# name: homebrew-tap
# folder: Formula

View File

@@ -1,26 +0,0 @@
sudo: required
services:
- docker
language: go
go:
- "1.13"
env:
- GO111MODULE=on
script:
- go test -v
addons:
apt:
packages:
- docker-ce
deploy:
- provider: script
skip_cleanup: true
script: curl -sL https://git.io/goreleaser | bash
on:
tags: true
condition: $TRAVIS_OS_NAME = linux
- provider: script
skip_cleanup: true
script: bash docker/push_images && bash docker/push_manifest
on:
branch: master

View File

@@ -1,6 +1,6 @@
# gohttpserver
[![Build Status](https://travis-ci.org/codeskyblue/gohttpserver.svg?branch=master)](https://travis-ci.org/codeskyblue/gohttpserver)
[![](https://images.microbadger.com/badges/image/codeskyblue/gohttpserver.svg)](https://microbadger.com/images/codeskyblue/gohttpserver "Get your own image badge on microbadger.com")
[![Docker Automated build](https://img.shields.io/docker/automated/codeskyblue/gohttpserver)](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
![screen](testdata/filetypes/gohttpserver.gif)
@@ -58,19 +58,23 @@ Tested with go-1.10, go-1.11
1. [x] Support unzip zip file when upload(with form: unzip=true)
## Installation
```
go get -v github.com/codeskyblue/gohttpserver
cd $GOPATH/src/github.com/codeskyblue/gohttpserver
go build && ./gohttpserver
```bash
$ go install github.com/codeskyblue/gohttpserver@latest
```
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.
```
./gohttpserver -r ./ --port 8000 --upload
$ gohttpserver -r ./ --port 8000 --upload
```
Use command `gohttpserver --help` to see more usage.
@@ -79,21 +83,21 @@ Use command `gohttpserver --help` to see more usage.
share current directory
```bash
docker run -it --rm -p 8000:8000 -v $PWD:/app/public --name gohttpserver codeskyblue/gohttpserver
$ docker run -it --rm -p 8000:8000 -v $PWD:/app/public --name gohttpserver codeskyblue/gohttpserver
```
Share current directory with http basic auth
```bash
docker run -it --rm -p 8000:8000 -v $PWD:/app/public --name gohttpserver \
$ docker run -it --rm -p 8000:8000 -v $PWD:/app/public --name gohttpserver \
codeskyblue/gohttpserver \
--auth-type http --auth-http username:password
--auth-type http --auth-http username1:password1 --auth-http username2:password2
```
Share current directory with openid auth. (Works only in netease company.)
```bash
docker run -it --rm -p 8000:8000 -v $PWD:/app/public --name gohttpserver \
$ docker run -it --rm -p 8000:8000 -v $PWD:/app/public --name gohttpserver \
codeskyblue/gohttpserver \
--auth-type openid
```
@@ -101,15 +105,15 @@ docker run -it --rm -p 8000:8000 -v $PWD:/app/public --name gohttpserver \
To build image yourself, please change the PWD to the root of this repo.
```bash
cd gohttpserver/
docker build -t codeskyblue/gohttpserver -f docker/Dockerfile .
$ cd gohttpserver/
$ docker build -t codeskyblue/gohttpserver -f docker/Dockerfile .
```
## Authentication options
- Enable basic http authentication
```sh
$ gohttpserver --auth-type http --auth-http username:password
$ gohttpserver --auth-type http --auth-http username1:password1 --auth-http username2:password2
```
- Use openid auth
@@ -126,7 +130,7 @@ docker build -t codeskyblue/gohttpserver -f docker/Dockerfile .
You can configure to let a http reverse proxy handling authentication.
When using oauth2-proxy, the backend will use identification info from request headers `X-Auth-Request-Email` as userId and `X-Auth-Request-Fullname` as user's display name.
Please config your oauth2 reverse proxy yourself.
More about [oauth2-proxy](https://github.com/bitly/oauth2_proxy).
More about [oauth2-proxy](https://github.com/oauth2-proxy/oauth2-proxy).
All required headers list as following.
@@ -192,8 +196,8 @@ accessTables:
### ipa plist proxy
This is used for server on which https is enabled. default use <https://plistproxy.herokuapp.com/plist>
```
./gohttpserver --plistproxy=https://someproxyhost.com/
```bash
$ gohttpserver --plistproxy=https://someproxyhost.com/
```
Test if proxy works:
@@ -258,6 +262,35 @@ gohttpserver should started with `--xheaders` argument when behide nginx.
Refs: <http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size>
gohttpserver also support `--prefix` flag which will help to when meet `/` is occupied by other service. relative issue <https://github.com/codeskyblue/gohttpserver/issues/105>
Usage example:
```bash
# for gohttpserver
$ gohttpserver --prefix /foo --addr :8200 --xheaders
```
**Nginx settigns**
```
server {
listen 80;
server_name your-domain-name.com;
location /foo {
proxy_pass http://127.0.0.1:8200; # here need to change
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 0; # disable upload limit
}
}
```
## FAQ
- [How to generate self signed certificate with openssl](http://stackoverflow.com/questions/10175812/how-to-create-a-self-signed-certificate-with-openssl)
@@ -273,14 +306,13 @@ Depdencies are managed by [govendor](https://github.com/kardianos/govendor)
1. Build develop version. **assets** directory must exists
```sh
go build
./gohttpserver
$ go build
$ ./gohttpserver
```
2. Build single binary release
```sh
go generate .
go build -tags vfs
$ go build
```
Theme are defined in [assets/themes](assets/themes) directory. Now only two themes are available, "black" and "green".
@@ -300,7 +332,7 @@ Theme are defined in [assets/themes](assets/themes) directory. Now only two them
**Go Libraries**
* [vfsgen](https://github.com/shurcooL/vfsgen)
* [vfsgen](https://github.com/shurcooL/vfsgen) Not using now
* [go-bindata-assetfs](https://github.com/elazarl/go-bindata-assetfs) Not using now
* <http://www.gorillatoolkit.org/pkg/handlers>

12
assets.go Normal file
View File

@@ -0,0 +1,12 @@
package main
import (
"embed"
"net/http"
)
//go:embed assets
var assetsFS embed.FS
// Assets contains project assets.
var Assets = http.FS(assetsFS)

View File

@@ -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">
@@ -63,6 +64,8 @@
</template>
[[end]]
</ul>
[[if not .NoIndex ]]
<form class="navbar-form navbar-right">
<div class="input-group">
<input type="text" name="search" class="form-control" placeholder="Search text" v-bind:value="search"
@@ -74,6 +77,7 @@
</span>
</div>
</form>
[[end]]
<ul id="nav-right-bar" class="nav navbar-nav navbar-right">
</ul>
</div>
@@ -128,7 +132,7 @@
<tbody>
<tr v-for="f in computedFiles">
<td>
<a v-on:click='clickFileOrDir(f, $event)' href="/{{f.path + (f.type == 'dir' ? '' : '')}}">
<a v-on:click='clickFileOrDir(f, $event)' href="{{getEncodePath(f.name)}}">
<!-- ?raw=false -->
<i style="padding-right: 0.5em" class="fa" v-bind:class='genFileClass(f)'></i> {{f.name}}
</a>
@@ -141,7 +145,7 @@
<td class="hidden-xs">{{formatTime(f.mtime)}}</td>
<td style="text-align: left">
<template v-if="f.type == 'dir'">
<a class="btn btn-default btn-xs" href="/{{f.path}}/?op=archive">
<a class="btn btn-default btn-xs" href="{{getEncodePath(f.name)}}/?op=archive">
<span class="hidden-xs">Archive</span> Zip
<span class="glyphicon glyphicon-download-alt"></span>
</a>
@@ -255,24 +259,31 @@
</div>
<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
<a href="https://github.com/codeskyblue/gohttpserver">gohttpserver ({{version}})</a>, by <a href="https://github.com/codeskyblue">codeskyblue</a>.
</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) {

View File

@@ -36,7 +36,6 @@ function showErrorMessage(jqXHR) {
console.error(errMsg)
}
var vm = new Vue({
el: "#app",
data: {
@@ -141,6 +140,9 @@ var vm = new Vue({
});
},
methods: {
getEncodePath: function (filepath) {
return pathJoin([location.pathname].concat(filepath.split("/").map(v => encodeURIComponent(v))))
},
formatTime: function (timestamp) {
var m = moment(timestamp);
if (this.mtimeTypeFromNow) {
@@ -167,7 +169,7 @@ var vm = new Vue({
if (!name) {
parts.push(pathname);
} else if (getExtention(name) == "ipa") {
parts.push("/-/ipa/link", pathname, name);
parts.push("/-/ipa/link", pathname, encodeURIComponent(name));
} else {
parts.push(pathname, name);
}
@@ -188,7 +190,7 @@ var vm = new Vue({
genDownloadURL: function (f) {
var search = location.search;
var sep = search == "" ? "?" : "&"
return location.origin + "/" + f.path + location.search + sep + "download=true";
return location.origin + this.getEncodePath(f.name) + location.search + sep + "download=true";
},
shouldHaveQrcode: function (name) {
return ['apk', 'ipa'].indexOf(getExtention(name)) !== -1;
@@ -234,11 +236,20 @@ var vm = new Vue({
return "fa-file-text-o"
},
clickFileOrDir: function (f, e) {
var reqPath = this.getEncodePath(f.name)
// TODO: fix here tomorrow
if (f.type == "file") {
return true;
// check whether the file is video
var videoExtensions = ['mp4', 'webm', 'ogg', 'mov', 'avi', 'mkv'];
var fileExtension = getExtention(f.name).toLowerCase();
if (videoExtensions.includes(fileExtension)) {
window.location.href = '/-/video-player' + reqPath;
} else {
window.location.href = reqPath;
}
e.preventDefault()
return;
}
var reqPath = pathJoin([location.pathname, f.name]);
loadFileOrDir(reqPath);
e.preventDefault()
},
@@ -249,7 +260,7 @@ var vm = new Vue({
showInfo: function (f) {
console.log(f);
$.ajax({
url: pathJoin(["/", location.pathname, f.name]),
url: this.getEncodePath(f.name),
data: {
op: "info",
},
@@ -276,7 +287,7 @@ var vm = new Vue({
return
}
$.ajax({
url: pathJoin(["/", location.pathname, "/", name]),
url: this.getEncodePath(name),
method: "POST",
success: function (res) {
console.log(res)
@@ -290,12 +301,12 @@ var vm = new Vue({
deletePathConfirm: function (f, e) {
e.preventDefault();
if (!e.altKey) { // skip confirm when alt pressed
if (!window.confirm("Delete " + location.pathname + "/" + f.name + " ?")) {
if (!window.confirm("Delete " + f.name + " ?")) {
return;
}
}
$.ajax({
url: pathJoin([location.pathname, f.name]),
url: this.getEncodePath(f.name),
method: 'DELETE',
success: function (res) {
loadFileList()

31
assets/themes/cyan.css Normal file
View 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;
}

49
assets/video-player.html Normal file
View File

@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Video Player - [[.FileName]]</title>
<style>
body, html {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
overflow: hidden;
background-color: #000;
}
.video-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100%;
}
video {
max-width: 100%;
max-height: 100%;
}
h1 {
color: #fff;
font-size: 24px;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="video-container">
<video id="videoPlayer" controls autoplay>
<source src="[[.VideoURL]]" type="video/[[.Extension]]">
Your browser does not support the video tag.
</video>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
var video = document.getElementById('videoPlayer');
video.focus();
});
</script>
</body>
</html>

View File

@@ -1,9 +0,0 @@
// +build !vfs
//go:generate go run assets_generate.go
package main
import "net/http"
// Assets contains project assets.
var Assets http.FileSystem = http.Dir("assets")

View File

@@ -1,23 +0,0 @@
// +build ignore
package main
import (
"log"
"net/http"
"github.com/shurcooL/vfsgen"
)
func main() {
var fs http.FileSystem = http.Dir("assets")
err := vfsgen.Generate(fs, vfsgen.Options{
PackageName: "main",
BuildTags: "vfs",
VariableName: "Assets",
})
if err != nil {
log.Fatalln(err)
}
}

View File

@@ -23,13 +23,10 @@ fi
build() {
echo "$1 $2 ..."
GOOS=$1 GOARCH=$2 go build \
-tags vfs \
-ldflags "$LDFLAGS" \
-o dist/gohttpserver-${3:-""}
}
go generate .
build linux arm linux-arm
build darwin amd64 mac-amd64
build linux amd64 linux-amd64

View File

@@ -1,9 +1,9 @@
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
FROM debian:stretch
FROM debian:stable
WORKDIR /app
RUN mkdir -p /app/public
RUN apt-get update && apt-get install -y ca-certificates

19
docker/Dockerfile.alpine Normal file
View File

@@ -0,0 +1,19 @@
FROM golang:1.16.0-alpine as build
WORKDIR /app
COPY go.mod .
COPY go.sum .
RUN go mod download
# Binary Compression
RUN apk add upx --no-cache
# Build app and pack using upx
ADD . /app
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags '-s -w -X main.VERSION=docker' -o gohttpserver && upx gohttpserver
FROM alpine:3
WORKDIR /app
ADD assets /usr/local/bin/assets
COPY --from=build /app/gohttpserver /usr/local/bin/gohttpserver
EXPOSE 8000
ENTRYPOINT [ "/usr/local/bin/gohttpserver" ]

View File

@@ -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 .

View File

@@ -9,8 +9,12 @@ fi
set -ex
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

23
go.mod
View File

@@ -1,30 +1,25 @@
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
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect
github.com/codeskyblue/dockerignore v0.0.0-20151214070507-de82dee623d9
github.com/codeskyblue/go-accesslog v0.0.0-20171215023101-6188d3bd9371
github.com/codeskyblue/openid-go v0.0.0-20160923065855-0d30842b2fb4
github.com/dsnet/compress v0.0.0-20171208185109-cc9eb1d7ad76 // indirect
github.com/fork2fix/go-plist v0.0.0-20181126021357-36960be5e636
github.com/go-yaml/yaml v2.1.0+incompatible
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
github.com/gorilla/handlers v1.4.0
github.com/gorilla/mux v1.6.2
github.com/gorilla/sessions v1.1.3
github.com/nwaples/rardecode v1.0.0 // indirect
github.com/pkg/errors v0.8.0
github.com/pkg/errors v0.8.0 // indirect
github.com/shogo82148/androidbinary v0.0.0-20180627093851-01c4bfa8b3b5
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371
github.com/shurcooL/vfsgen v0.0.0-20181020040650-a97a25d856ca
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/stretchr/testify v1.3.0
github.com/ulikunitz/xz v0.5.5 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a
golang.org/x/text v0.3.0
golang.org/x/net v0.23.0 // indirect
golang.org/x/text v0.14.0
howett.net/plist v0.0.0-20201203080718-1454fab16a06 // indirect
)
go 1.13

107
go.sum Normal file
View File

@@ -0,0 +1,107 @@
github.com/alecthomas/kingpin v2.2.6+incompatible h1:5svnBTFgJjZvGKyYBtMB0+m5wvrbUHiqye8wRJMlnYI=
github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/codeskyblue/dockerignore v0.0.0-20151214070507-de82dee623d9 h1:c9axcChJwkLuSl9AvwTHi8jiBa6+VX4gGgERhABgv2E=
github.com/codeskyblue/dockerignore v0.0.0-20151214070507-de82dee623d9/go.mod h1:XNZkUhPf+qgRnhY/ecS3B73ODJ2NXCzDMJHXM069IMg=
github.com/codeskyblue/go-accesslog v0.0.0-20171215023101-6188d3bd9371 h1:dEBIvaVFaP2Uc9QA6J41qWxE5NfEnDWEBk+kWv5nK5k=
github.com/codeskyblue/go-accesslog v0.0.0-20171215023101-6188d3bd9371/go.mod h1:sgXnVxxZ1u72GAzc9s1SzpuPMxBDKfTg6F2PvDrPSJU=
github.com/codeskyblue/openid-go v0.0.0-20160923065855-0d30842b2fb4 h1:66lzN78lwccK+BPztRgBiWCYzhlerQEVOh2oeBksu5I=
github.com/codeskyblue/openid-go v0.0.0-20160923065855-0d30842b2fb4/go.mod h1:K/hSCtAHvnE9aM+LsYgVmgzPNFuWFdx6i9t6/3jNrZQ=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fork2fix/go-plist v0.0.0-20181126021357-36960be5e636 h1:ESUdS2eb8LyDQfboYyFBwAL+rqYhnTZ15ntw8BLsd9g=
github.com/fork2fix/go-plist v0.0.0-20181126021357-36960be5e636/go.mod h1:v6KRhgoO1QKamoeuZ7yHqZIP8p6j9k41Tb0jCyOEmr4=
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d h1:lBXNCxVENCipq4D1Is42JVOP4eQjlB8TQ6H69Yx5J9Q=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA=
github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9RU=
github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/shogo82148/androidbinary v0.0.0-20180627093851-01c4bfa8b3b5 h1:bXRaUWl3Afe3F9YR5NU1U3UB5zjCHlu4im5p3J/LUYk=
github.com/shogo82148/androidbinary v0.0.0-20180627093851-01c4bfa8b3b5/go.mod h1:05AjXWPWLdTIl9+REKhSmTeoJ6Wz5e9ir0Q0NRxCIKo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
howett.net/plist v0.0.0-20201203080718-1454fab16a06 h1:QDxUo/w2COstK1wIBYpzQlHX/NqaQTcf9jyz347nI58=
howett.net/plist v0.0.0-20201203080718-1454fab16a06/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=

View File

@@ -16,6 +16,7 @@ import (
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"regexp"
@@ -41,8 +42,14 @@ type IndexFileItem struct {
Info os.FileInfo
}
type Directory struct {
size map[string]int64
mutex *sync.RWMutex
}
type HTTPStaticServer struct {
Root string
Prefix string
Upload bool
Delete bool
Title string
@@ -50,16 +57,20 @@ type HTTPStaticServer struct {
PlistProxy string
GoogleTrackerID string
AuthType string
DeepPathMaxDepth int
NoIndex bool
indexes []IndexFileItem
m *mux.Router
bufPool sync.Pool // use sync.Pool caching buf to reduce gc ratio
}
func NewHTTPStaticServer(root string) *HTTPStaticServer {
if root == "" {
root = "./"
}
root = filepath.ToSlash(root)
func NewHTTPStaticServer(root string, noIndex bool) *HTTPStaticServer {
// if root == "" {
// root = "./"
// }
// root = filepath.ToSlash(root)
root = filepath.ToSlash(filepath.Clean(root))
if !strings.HasSuffix(root, "/") {
root = root + "/"
}
@@ -69,8 +80,13 @@ func NewHTTPStaticServer(root string) *HTTPStaticServer {
Root: root,
Theme: "black",
m: m,
bufPool: sync.Pool{
New: func() interface{} { return make([]byte, 32*1024) },
},
NoIndex: noIndex,
}
if !noIndex {
go func() {
time.Sleep(1 * time.Second)
for {
@@ -82,10 +98,12 @@ func NewHTTPStaticServer(root string) *HTTPStaticServer {
time.Sleep(time.Minute * 10)
}
}()
}
// routers for Apple *.ipa
m.HandleFunc("/-/ipa/plist/{path:.*}", s.hPlist)
m.HandleFunc("/-/ipa/link/{path:.*}", s.hIpaLink)
m.HandleFunc("/-/video-player/{path:.*}", s.hVideoPlayer)
m.HandleFunc("/{path:.*}", s.hIndex).Methods("GET", "HEAD")
m.HandleFunc("/{path:.*}", s.hUploadOrMkdir).Methods("POST")
@@ -97,9 +115,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 +148,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)
renderHTML(w, "assets/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 +165,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 +194,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,22 +245,29 @@ 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, os.Rename might be failed
var copyErr error
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 {
// 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
}
_, copyErr = io.Copy(dst, file)
// Note: very large size file might cause poor performance
// _, copyErr = io.Copy(dst, file)
buf := s.bufPool.Get().([]byte)
defer s.bufPool.Put(buf)
_, copyErr = io.CopyBuffer(dst, file, buf)
dst.Close()
}
// }
if copyErr != nil {
log.Println("Handle upload file:", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -306,7 +326,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 {
@@ -337,8 +357,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) {
@@ -370,7 +389,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)
@@ -415,7 +434,7 @@ func (s *HTTPStaticServer) hIpaLink(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
log.Println("PlistURL:", plistUrl)
renderHTML(w, "ipa-install.html", map[string]string{
renderHTML(w, "assets/ipa-install.html", map[string]string{
"Name": filepath.Base(path),
"PlistLink": plistUrl,
})
@@ -450,8 +469,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 {
@@ -554,11 +572,12 @@ 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)
maxDepth := s.DeepPathMaxDepth
// path string -> info os.FileInfo
fileInfoMap := make(map[string]os.FileInfo, 0)
@@ -574,7 +593,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
@@ -603,7 +622,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(), maxDepth)
lr.Name = name
lr.Path = filepath.Join(filepath.Dir(path), name)
lr.Type = "dir"
@@ -623,7 +642,7 @@ func (s *HTTPStaticServer) hJSONList(w http.ResponseWriter, r *http.Request) {
w.Write(data)
}
var dirSizeMap = make(map[string]int64)
var dirInfoSize = Directory{size: make(map[string]int64), mutex: &sync.RWMutex{}}
func (s *HTTPStaticServer) makeIndex() error {
var indexes = make([]IndexFileItem, 0)
@@ -643,21 +662,28 @@ func (s *HTTPStaticServer) makeIndex() error {
return nil
})
s.indexes = indexes
dirSizeMap = make(map[string]int64)
return err
}
func (s *HTTPStaticServer) historyDirSize(dir string) int64 {
var size int64
if size, ok := dirSizeMap[dir]; ok {
dirInfoSize.mutex.RLock()
size, ok := dirInfoSize.size[dir]
dirInfoSize.mutex.RUnlock()
if ok {
return size
}
for _, fitem := range s.indexes {
if filepath.HasPrefix(fitem.Path, dir) {
size += fitem.Info.Size()
}
}
dirSizeMap[dir] = size
dirInfoSize.mutex.Lock()
dirInfoSize.size[dir] = size
dirInfoSize.mutex.Unlock()
return size
}
@@ -694,19 +720,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) {
@@ -721,11 +747,9 @@ func (s *HTTPStaticServer) readAccessConf(requestPath string) (ac AccessConf) {
return
}
func deepPath(basedir, name string) string {
isDir := true
func deepPath(basedir, name string, maxDepth int) string {
// loop max 5, incase of for loop not finished
maxDepth := 5
for depth := 0; depth <= maxDepth && isDir; depth += 1 {
for depth := 0; depth <= maxDepth; depth += 1 {
finfos, err := ioutil.ReadDir(filepath.Join(basedir, name))
if err != nil || len(finfos) != 1 {
break
@@ -739,16 +763,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 {
@@ -787,7 +801,7 @@ var (
_tmpls = make(map[string]*template.Template)
)
func executeTemplate(w http.ResponseWriter, name string, v interface{}) {
func renderHTML(w http.ResponseWriter, name string, v interface{}) {
if t, ok := _tmpls[name]; ok {
t.Execute(w, v)
return
@@ -797,19 +811,34 @@ func executeTemplate(w http.ResponseWriter, name string, v interface{}) {
t.Execute(w, v)
}
func renderHTML(w http.ResponseWriter, name string, v interface{}) {
if _, ok := Assets.(http.Dir); ok {
log.Println("Hot load", name)
t := template.Must(template.New(name).Funcs(funcMap).Delims("[[", "]]").Parse(assetsContent(name)))
t.Execute(w, v)
} else {
executeTemplate(w, name, v)
}
}
func checkFilename(name string) error {
if strings.ContainsAny(name, "\\/:*<>|") {
return errors.New("Name should not contains \\/:*<>|")
}
return nil
}
func (s *HTTPStaticServer) hVideoPlayer(w http.ResponseWriter, r *http.Request) {
path := mux.Vars(r)["path"]
realPath := s.getRealPath(r)
extension := strings.ToLower(strings.TrimPrefix(filepath.Ext(path), "."))
if _, err := os.Stat(realPath); os.IsNotExist(err) {
http.Error(w, "File not found", http.StatusNotFound)
return
}
fileName := filepath.Base(path)
scheme := "http"
if r.TLS != nil {
scheme = "https"
}
videoURL := fmt.Sprintf("%s://%s/%s", scheme, r.Host, path)
renderHTML(w, "assets/video-player.html", map[string]interface{}{
"FileName": fileName,
"VideoURL": videoURL,
"Extension": extension,
})
}

114
main.go
View File

@@ -2,6 +2,8 @@ package main
import (
"bytes"
"crypto/sha256"
"crypto/subtle"
"encoding/json"
"fmt"
"io/ioutil"
@@ -10,6 +12,7 @@ import (
"net/http"
"net/url"
"os"
"regexp"
"runtime"
"strconv"
"strings"
@@ -20,7 +23,7 @@ import (
"github.com/go-yaml/yaml"
"github.com/goji/httpauth"
"github.com/gorilla/handlers"
_ "github.com/shurcooL/vfsgen"
"github.com/gorilla/mux"
)
type Configure struct {
@@ -28,10 +31,10 @@ 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"`
Cors bool `yaml:"cors"`
Theme string `yaml:"theme"`
XHeaders bool `yaml:"xheaders"`
Upload bool `yaml:"upload"`
@@ -43,10 +46,12 @@ type Configure struct {
Auth struct {
Type string `yaml:"type"` // openid|http|github
OpenID string `yaml:"openid"`
HTTP string `yaml:"http"`
HTTP []string `yaml:"http"`
ID string `yaml:"id"` // for oauth2
Secret string `yaml:"secret"` // for oauth2
} `yaml:"auth"`
DeepPathMaxDepth int `yaml:"deep-path-max-depth"`
NoIndex bool `yaml:"no-index"`
}
type httpLogger struct{}
@@ -97,27 +102,31 @@ func parseFlags() error {
gcfg.Auth.OpenID = defaultOpenID
gcfg.GoogleTrackerID = "UA-81205425-2"
gcfg.Title = "Go HTTP File Server"
gcfg.DeepPathMaxDepth = 5
gcfg.NoIndex = false
kingpin.HelpFlag.Short('h')
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)
kingpin.Flag("key", "tls key.pem path").StringVar(&gcfg.Key)
kingpin.Flag("auth-type", "Auth type <http|openid>").StringVar(&gcfg.Auth.Type)
kingpin.Flag("auth-http", "HTTP basic auth (ex: user:pass)").StringVar(&gcfg.Auth.HTTP)
kingpin.Flag("auth-http", "HTTP basic auth (ex: user:pass)").StringsVar(&gcfg.Auth.HTTP)
kingpin.Flag("auth-openid", "OpenID auth identity url").StringVar(&gcfg.Auth.OpenID)
kingpin.Flag("theme", "web theme, one of <black|green>").StringVar(&gcfg.Theme)
kingpin.Flag("upload", "enable upload support").BoolVar(&gcfg.Upload)
kingpin.Flag("delete", "enable delete support").BoolVar(&gcfg.Delete)
kingpin.Flag("xheaders", "used when behide nginx").BoolVar(&gcfg.XHeaders)
kingpin.Flag("cors", "enable cross-site HTTP request").BoolVar(&gcfg.Cors)
kingpin.Flag("debug", "enable debug mode").BoolVar(&gcfg.Debug)
kingpin.Flag("plistproxy", "plist proxy when server is not https").Short('p').StringVar(&gcfg.PlistProxy)
kingpin.Flag("title", "server title").StringVar(&gcfg.Title)
kingpin.Flag("google-tracker-id", "set to empty to disable it").StringVar(&gcfg.GoogleTrackerID)
kingpin.Flag("deep-path-max-depth", "set to -1 to not combine dirs").IntVar(&gcfg.DeepPathMaxDepth)
kingpin.Flag("no-index", "disable indexing").BoolVar(&gcfg.NoIndex)
kingpin.Parse() // first parse conf
@@ -134,6 +143,52 @@ 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 cors(next http.Handler) http.Handler {
// access control and CORS middleware
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "*")
w.Header().Set("Access-Control-Allow-Headers", "*")
if r.Method == "OPTIONS" {
return
}
next.ServeHTTP(w, r)
})
}
func multiBasicAuth(auths []string) func(http.Handler) http.Handler {
userPassMap := make(map[string]string)
for _, auth := range auths {
userpass := strings.SplitN(auth, ":", 2)
if len(userpass) == 2 {
userPassMap[userpass[0]] = userpass[1]
}
}
return httpauth.BasicAuth(httpauth.AuthOptions{
Realm: "Restricted",
AuthFunc: func(user, pass string, request *http.Request) bool {
password, ok := userPassMap[user]
if !ok {
return false
}
givenPass := sha256.Sum256([]byte(pass))
requiredPass := sha256.Sum256([]byte(password))
return subtle.ConstantTimeCompare(givenPass[:], requiredPass[:]) == 1
},
})
}
func main() {
if err := parseFlags(); err != nil {
log.Fatal(err)
@@ -144,13 +199,21 @@ func main() {
}
log.SetFlags(log.Lshortfile | log.LstdFlags)
ss := NewHTTPStaticServer(gcfg.Root)
// make sure prefix matches: ^/.*[^/]$
gcfg.Prefix = fixPrefix(gcfg.Prefix)
if gcfg.Prefix != "" {
log.Printf("url prefix: %s", gcfg.Prefix)
}
ss := NewHTTPStaticServer(gcfg.Root, gcfg.NoIndex)
ss.Prefix = gcfg.Prefix
ss.Theme = gcfg.Theme
ss.Title = gcfg.Title
ss.GoogleTrackerID = gcfg.GoogleTrackerID
ss.Upload = gcfg.Upload
ss.Delete = gcfg.Delete
ss.AuthType = gcfg.Auth.Type
ss.DeepPathMaxDepth = gcfg.DeepPathMaxDepth
if gcfg.PlistProxy != "" {
u, err := url.Parse(gcfg.PlistProxy)
@@ -169,13 +232,9 @@ func main() {
hdlr = accesslog.NewLoggingHandler(hdlr, logger)
// HTTP Basic Authentication
userpass := strings.SplitN(gcfg.Auth.HTTP, ":", 2)
switch gcfg.Auth.Type {
case "http":
if len(userpass) == 2 {
user, pass := userpass[0], userpass[1]
hdlr = httpauth.SimpleBasicAuth(user, pass)(hdlr)
}
hdlr = multiBasicAuth(gcfg.Auth.HTTP)(hdlr)
case "openid":
handleOpenID(gcfg.Auth.OpenID, false) // FIXME(ssx): set secure default to false
// case "github":
@@ -185,22 +244,32 @@ func main() {
}
// CORS
if gcfg.Cors {
hdlr = handlers.CORS()(hdlr)
}
hdlr = cors(hdlr)
if gcfg.XHeaders {
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")
mainRouter := mux.NewRouter()
router := mainRouter
if gcfg.Prefix != "" {
router = mainRouter.PathPrefix(gcfg.Prefix).Subrouter()
mainRouter.Handle(gcfg.Prefix, hdlr)
mainRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, gcfg.Prefix, http.StatusTemporaryRedirect)
})
}
router.PathPrefix("/-/assets/").Handler(http.StripPrefix(gcfg.Prefix+"/-/", http.FileServer(Assets)))
router.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)
})
router.PathPrefix("/").Handler(hdlr)
if gcfg.Addr == "" {
gcfg.Addr = fmt.Sprintf(":%d", gcfg.Port)
@@ -211,11 +280,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: mainRouter,
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)
}

1
testdata/filetypes/script.js vendored Normal file
View File

@@ -0,0 +1 @@
document.write("Hello world!")

View File

@@ -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()
}

View File

@@ -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)
}
}
}