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
builds:
- goos:
- linux
- darwin
- windows
goarch:
- amd64
- "386"
goarm:
- "6"
main: .
ldflags: -s -w -X main.VERSION={{.Version}}
flags: -tags vfs
binary: gohttpserver
version: 2
before:
hooks:
pre: go generate .
archive:
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'
- go mod tidy
builds:
- 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"
archives:
- format: tar.gz
# use zip for windows archives
format_overrides:
- goos: windows
format: zip
# 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) {
@@ -292,4 +303,4 @@
</script> [[ end ]]
</body>
</html>
</html>

View File

@@ -30,13 +30,12 @@ function checkPathNameLegal(name) {
function showErrorMessage(jqXHR) {
let errMsg = jqXHR.getResponseHeader("x-auth-authentication-message")
if (errMsg == null) {
errMsg = jqXHR.responseText
errMsg = jqXHR.responseText
}
alert(String(jqXHR.status).concat(":", errMsg));
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()
@@ -333,23 +344,23 @@ var vm = new Vue({
}
var that = this;
$.getJSON(pathJoin(['/-/info', location.pathname]))
.then(function (res) {
console.log(res);
that.preview.filename = res.name;
that.preview.filesize = res.size;
return $.ajax({
url: '/' + res.path,
dataType: 'text',
.then(function (res) {
console.log(res);
that.preview.filename = res.name;
that.preview.filesize = res.size;
return $.ajax({
url: '/' + res.path,
dataType: 'text',
});
})
.then(function (res) {
console.log(res)
that.preview.contentHTML = '<pre>' + res + '</pre>';
console.log("Finally")
})
.done(function (res) {
console.log("done", res)
});
})
.then(function (res) {
console.log(res)
that.preview.contentHTML = '<pre>' + res + '</pre>';
console.log("Finally")
})
.done(function (res) {
console.log("done", res)
});
},
loadAll: function () {
// TODO: move loadFileList here
@@ -440,10 +451,10 @@ $(function () {
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
$(e.trigger)
.tooltip('show')
.mouseleave(function () {
$(this).tooltip('hide');
})
.tooltip('show')
.mouseleave(function () {
$(this).tooltip('hide');
})
e.clearSelection();
});

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

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,25 +42,35 @@ type IndexFileItem struct {
Info os.FileInfo
}
type Directory struct {
size map[string]int64
mutex *sync.RWMutex
}
type HTTPStaticServer struct {
Root string
Upload bool
Delete bool
Title string
Theme string
PlistProxy string
GoogleTrackerID string
AuthType string
Root string
Prefix string
Upload bool
Delete bool
Title string
Theme string
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,23 +80,30 @@ func NewHTTPStaticServer(root string) *HTTPStaticServer {
Root: root,
Theme: "black",
m: m,
bufPool: sync.Pool{
New: func() interface{} { return make([]byte, 32*1024) },
},
NoIndex: noIndex,
}
go func() {
time.Sleep(1 * time.Second)
for {
startTime := time.Now()
log.Println("Started making search index")
s.makeIndex()
log.Printf("Completed search index in %v", time.Since(startTime))
//time.Sleep(time.Second * 1)
time.Sleep(time.Minute * 10)
}
}()
if !noIndex {
go func() {
time.Sleep(1 * time.Second)
for {
startTime := time.Now()
log.Println("Started making search index")
s.makeIndex()
log.Printf("Completed search index in %v", time.Since(startTime))
//time.Sleep(time.Second * 1)
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 {
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)
dst.Close()
// 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
}
// 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,
})
}

124
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"`
@@ -41,12 +44,14 @@ type Configure struct {
Debug bool `yaml:"debug"`
GoogleTrackerID string `yaml:"google-tracker-id"`
Auth struct {
Type string `yaml:"type"` // openid|http|github
OpenID string `yaml:"openid"`
HTTP string `yaml:"http"`
ID string `yaml:"id"` // for oauth2
Secret string `yaml:"secret"` // for oauth2
Type string `yaml:"type"` // openid|http|github
OpenID string `yaml:"openid"`
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)
@@ -163,19 +226,15 @@ func main() {
if ss.PlistProxy != "" {
log.Printf("plistproxy: %s", strconv.Quote(ss.PlistProxy))
}
var hdlr http.Handler = ss
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)
}
}
}