6 Commits

Author SHA1 Message Date
codeskyblue
9a950b98db modify part 2018-11-28 09:58:54 +08:00
gzliuxin
033f0a9271 fix dldcounter bug 2018-11-26 18:24:44 +08:00
gzliuxin
1f68fcb45f add dldcounter to valid ip address 2018-11-26 15:05:59 +08:00
gzliuxin
b81894b0cf add autosave interval; add column for downlowads in index.html 2018-11-26 13:32:26 +08:00
gzliuxin
cfaa27b981 add db.go 2018-11-26 11:59:28 +08:00
gzliuxin
ffcf9cc742 add download count 2018-11-26 11:54:09 +08:00
20 changed files with 297 additions and 546 deletions

3
.gitignore vendored
View File

@@ -29,5 +29,4 @@ bindata_assetfs.go
assets_vfsdata.go
*.un~
*.swp
dist/
ghs-stats.json

View File

@@ -1,26 +1,44 @@
before:
hooks:
- go mod tidy
- go generate .
project_name: gohttpserver
release:
github:
owner: codeskyblue
name: gohttpserver
brew:
github:
owner: codeskyblue
name: homebrew-tap
homepage: https://github.com/codeskyblue/gohttpserver
builds:
- env:
- CGO_ENABLED=0
main: .
flags:
- -tags=vfs
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"
brews:
- tap:
owner: codeskyblue
name: homebrew-tap
folder: Formula
- goos:
- linux
- darwin
- windows
goarch:
- amd64
- "386"
goarm:
- "6"
main: .
ldflags: -s -w -X main.VERSION={{.Version}}
flags: -tags vfs
binary: gohttpserver
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'

View File

@@ -1,8 +1,9 @@
sudo: required
services:
- docker
language: go
go:
- "1.16"
- "1.11"
env:
- GO111MODULE=on
script:
@@ -18,8 +19,8 @@ deploy:
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
- provider: script
skip_cleanup: true
script: bash docker/push_images && bash docker/push_manifest
on:
branch: master

View File

@@ -1,6 +1,5 @@
# 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")
- 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 +12,7 @@
**Binaries** can be downloaded from [this repo releases](https://github.com/codeskyblue/gohttpserver/releases/)
## Requirements
Tested with go-1.16
Tested with go-1.10, go-1.11
## Screenshots
![screen](testdata/filetypes/gohttpserver.gif)
@@ -55,7 +54,6 @@ Tested with go-1.16
1. [x] Show folder size
1. [x] Create folder
1. [x] Skip delete confirm when alt pressed
1. [x] Support unzip zip file when upload(with form: unzip=true)
## Installation
```
@@ -66,12 +64,6 @@ go build && ./gohttpserver
Or download binaries from [github releases](https://github.com/codeskyblue/gohttpserver/releases)
If you are using Mac, simply run command
```bash
brew install codeskyblue/tap/gohttpserver
```
## Usage
Listen on port 8000 of all interfaces, and enable file uploading.
@@ -104,13 +96,6 @@ docker run -it --rm -p 8000:8000 -v $PWD:/app/public --name gohttpserver \
--auth-type openid
```
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 .
```
## Authentication options
- Enable basic http authentication
@@ -124,24 +109,6 @@ docker build -t codeskyblue/gohttpserver -f docker/Dockerfile .
$ gohttpserver --auth-type openid --auth-openid https://login.example-hostname.com/openid/
```
- Use oauth2-proxy with
```sh
$ gohttpserver --auth-type oauth2-proxy
```
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).
All required headers list as following.
|header|value|
|---|---|
|X-Auth-Request-Email| userId |
|X-Auth-Request-Fullname| user's display name(urlencoded) |
|X-Auth-Request-User| user's nickname (mostly email prefix) |
- Enable upload
```sh
@@ -213,8 +180,6 @@ $ http GET https://someproxyhost.com/plist/18f99211
# show the app.plist content
```
If your ghs running behide nginx server and have https configed. plistproxy will be disabled automaticly.
### Upload with CURL
For example, upload a file named `foo.txt` to directory `somedir`
@@ -230,13 +195,6 @@ $ curl -F file=@foo.txt -F filename=hi.txt localhost:8000/somedir
{"destination":"somedir/hi.txt","success":true}
```
Upload zip file and unzip it (zip file will be delete when finished unzip)
```
$ curl -F file=@pkg.zip -F unzip=true localhost:8000/somedir
{"success": true}
```
Note: `\/:*<>|` are not allowed in filenames.
### Deploy with nginx
@@ -260,8 +218,6 @@ server {
}
```
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>
## FAQ

View File

@@ -4,8 +4,7 @@
<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>
<title>gohttp server</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">
@@ -50,26 +49,13 @@
</a>
</template>
[[end]]
[[if eq .AuthType "oauth2-proxy"]]
<template v-if="!user.email">
<a href="#" class="btn btn-sm btn-default navbar-btn">
Guest <span class="glyphicon glyphicon-user"></span>
</a>
</template>
<template v-else>
<a href="/-/logout" class="btn btn-sm btn-default navbar-btn">
<span v-text="user.name"></span>
<i class="fa fa-sign-out"></i>
</a>
</template>
[[end]]
</ul>
<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"
autofocus>
<span class="input-group-btn">
<button class="btn btn-default" type="submit">
<button class="btn btn-default" type="button">
<span class="glyphicon glyphicon-search"></span>
</button>
</span>
@@ -123,13 +109,14 @@
<th class="hidden-xs">
<span style="cursor: pointer" v-on:click='mtimeTypeFromNow = !mtimeTypeFromNow'>ModTime</span>
</th>
<th>Downloads</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="f in computedFiles">
<td>
<a v-on:click='clickFileOrDir(f, $event)' href="{{getEncodePath(f)}}">
<a v-on:click='clickFileOrDir(f, $event)' href="/{{f.path + (f.type == 'dir' ? '' : '')}}">
<!-- ?raw=false -->
<i style="padding-right: 0.5em" class="fa" v-bind:class='genFileClass(f)'></i> {{f.name}}
</a>
@@ -140,21 +127,19 @@
</td>
<td><span v-if="f.type == 'dir'">~</span> {{f.size | formatBytes}}</td>
<td class="hidden-xs">{{formatTime(f.mtime)}}</td>
<td class="hidden-xs">{{f.dldcnt}}</td>
<td style="text-align: left">
<template v-if="f.type == 'dir'">
<a class="btn btn-default btn-xs" href="{{getEncodePath(f)}}/?op=archive">
<a class="btn btn-default btn-xs" href="/-/zip/{{f.path}}">
<span class="hidden-xs">Archive</span> Zip
<span class="glyphicon glyphicon-download-alt"></span>
</a>
<button class="btn btn-default btn-xs" v-on:click="showInfo(f)">
<span class="glyphicon glyphicon-info-sign"></span>
</button>
<button class="btn btn-default btn-xs" v-if="auth.delete" v-on:click="deletePathConfirm(f, $event)">
<span style="color:#CC3300" class="glyphicon glyphicon-trash"></span>
</button>
</template>
<template v-if="f.type == 'file'">
<a class="btn btn-default btn-xs hidden-xs" href="{{genDownloadURL(f)}}">
<a class="btn btn-default btn-xs hidden-xs" href="/{{f.path}}?download=true">
<span class="hidden-xs">Download</span>
<span class="glyphicon glyphicon-download-alt"></span>
</a>
@@ -257,7 +242,7 @@
<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-2021. go1.16
Copyright 2016-2018. go1.10
</div>
</div>
</div>
@@ -274,11 +259,6 @@
<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> -->
<!--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) {
@@ -298,4 +278,4 @@
</script> [[ end ]]
</body>
</html>
</html>

View File

@@ -21,22 +21,6 @@ function getQueryString(name) {
return null;
}
function checkPathNameLegal(name) {
var reg = new RegExp("[\\/:*<>|]");
var r = name.match(reg)
return r == null;
}
function showErrorMessage(jqXHR) {
let errMsg = jqXHR.getResponseHeader("x-auth-authentication-message")
if (errMsg == null) {
errMsg = jqXHR.responseText
}
alert(String(jqXHR.status).concat(":", errMsg));
console.error(errMsg)
}
var vm = new Vue({
el: "#app",
data: {
@@ -141,9 +125,6 @@ var vm = new Vue({
});
},
methods: {
getEncodePath: function (f) {
return pathJoin([location.pathname, encodeURIComponent(f.name)]);
},
formatTime: function (timestamp) {
var m = moment(timestamp);
if (this.mtimeTypeFromNow) {
@@ -170,7 +151,7 @@ var vm = new Vue({
if (!name) {
parts.push(pathname);
} else if (getExtention(name) == "ipa") {
parts.push("/-/ipa/link", pathname, encodeURIComponent(name));
parts.push("/-/ipa/link", pathname, name);
} else {
parts.push(pathname, name);
}
@@ -189,9 +170,7 @@ var vm = new Vue({
$("#qrcode-modal").modal("show");
},
genDownloadURL: function (f) {
var search = location.search;
var sep = search == "" ? "?" : "&"
return location.origin + this.getEncodePath(f) + location.search + sep + "download=true";
return location.origin + "/" + f.path;
},
shouldHaveQrcode: function (name) {
return ['apk', 'ipa'].indexOf(getExtention(name)) !== -1;
@@ -237,12 +216,11 @@ var vm = new Vue({
return "fa-file-text-o"
},
clickFileOrDir: function (f, e) {
var reqPath = pathJoin([location.pathname, encodeURIComponent(f.name)]);
// TODO: fix here tomorrow
if (f.type == "file") {
window.location.href = reqPath;
return;
return true;
}
var reqPath = pathJoin([location.pathname, f.name]);
loadFileOrDir(reqPath);
e.preventDefault()
},
@@ -253,65 +231,57 @@ var vm = new Vue({
showInfo: function (f) {
console.log(f);
$.ajax({
url: pathJoin(["/", location.pathname, encodeURIComponent(f.name)]),
data: {
op: "info",
},
url: pathJoin(["/-/info", location.pathname, f.name]),
method: "GET",
success: function (res) {
$("#file-info-title").text(f.name);
$("#file-info-content").text(JSON.stringify(res, null, 4));
$("#file-info-modal").modal("show");
// console.log(JSON.stringify(res, null, 4));
},
error: function (jqXHR, textStatus, errorThrown) {
showErrorMessage(jqXHR)
}
})
},
makeDirectory: function () {
var name = window.prompt("current path: " + location.pathname + "\nplease enter the new directory name", "")
var name = window.prompt("Directory name?")
console.log(name)
if (!name) {
return
}
if(!checkPathNameLegal(name)) {
alert("Name should not contains any of \\/:*<>|")
return
}
$.ajax({
url: pathJoin(["/", location.pathname, "/", encodeURIComponent(name)]),
url: pathJoin(["/-/mkdir", location.pathname]),
data: {
name: name,
},
method: "POST",
success: function (res) {
console.log(res)
loadFileList()
},
error: function (jqXHR, textStatus, errorThrown) {
showErrorMessage(jqXHR)
error: function (err) {
alert(err.responseText);
}
})
},
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, encodeURIComponent(f.name)]),
url: pathJoin([location.pathname, f.name]),
method: 'DELETE',
success: function (res) {
loadFileList()
},
error: function (jqXHR, textStatus, errorThrown) {
showErrorMessage(jqXHR)
error: function (err) {
alert(err.responseText);
}
});
},
updateBreadcrumb: function (pathname) {
var pathname = decodeURI(pathname || location.pathname || "/");
pathname = pathname.split('?')[0]
updateBreadcrumb: function () {
var pathname = decodeURI(location.pathname || "/");
var parts = pathname.split('/');
this.breadcrumb = [];
if (pathname == "/") {
@@ -337,23 +307,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.contentHTML = '<pre>' + res + '</pre>';
console.log("Finally")
})
.done(function (res) {
console.log("done", res)
.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)
});
},
loadAll: function () {
// TODO: move loadFileList here
@@ -370,23 +340,16 @@ window.onpopstate = function (event) {
}
function loadFileOrDir(reqPath) {
let requestUri = reqPath + location.search
var retObj = loadFileList(requestUri)
if (retObj !== null) {
retObj.done(function () {
window.history.pushState({}, "", requestUri);
});
}
window.history.pushState({}, "", reqPath);
loadFileList(reqPath)
}
function loadFileList(pathname) {
var pathname = pathname || location.pathname + location.search;
var retObj = null
var pathname = pathname || location.pathname;
// console.log("load filelist:", pathname)
if (getQueryString("raw") !== "false") { // not a file preview
var sep = pathname.indexOf("?") === -1 ? "?" : "&"
retObj = $.ajax({
url: pathname + sep + "json=true",
$.ajax({
url: pathJoin(["/-/json", pathname]),
dataType: "json",
cache: false,
success: function (res) {
@@ -394,22 +357,22 @@ function loadFileList(pathname) {
var weight = f.type == 'dir' ? 1000 : 1;
return -weight * f.mtime;
})
vm.files = res.files;
vm.auth = res.auth;
vm.updateBreadcrumb(pathname);
},
error: function (jqXHR, textStatus, errorThrown) {
showErrorMessage(jqXHR)
error: function (err) {
console.error(err)
},
});
}
vm.updateBreadcrumb();
vm.previewMode = getQueryString("raw") == "false";
if (vm.previewMode) {
vm.loadPreviewFile();
}
return retObj
}
Vue.filter('fromNow', function (value) {
@@ -444,10 +407,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();
});

View File

@@ -1,31 +0,0 @@
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;
}

View File

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

107
db.go Normal file
View File

@@ -0,0 +1,107 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"time"
)
func panicErr(e error) {
if e != nil {
panic(e)
}
}
type Model struct {
Filename string
Data *map[string]int
LastSave time.Time
}
var DBModel Model = Model{}
// var SaveInterval float64 = 60.0 // seconds
const SaveInterval = 60.0
func (model *Model) DBRead(filename string) *map[string]int {
data := make(map[string]int)
model.Filename = filename
model.Data = &data
// create if file not exist
if _, err := os.Stat(filename); os.IsNotExist(err) {
f, cerr := os.Create(filename)
panicErr(cerr)
s, jerr := json.Marshal(data)
panicErr(jerr)
f.Write(s)
defer f.Close()
return &data
}
dat, err := ioutil.ReadFile(filename)
panicErr(err)
err = json.Unmarshal(dat, &data)
panicErr(err)
fmt.Println(data)
return &data
}
func (model *Model) DBWrite() error {
fmt.Println("db writing")
dat, err := json.Marshal(model.Data)
if err != nil {
return err
}
tmp := model.Filename + ".un~"
err = ioutil.WriteFile(tmp, dat, 0644)
if err != nil {
return err
}
return os.Rename(tmp, model.Filename)
}
func (model *Model) Incre(key string) {
(*model.Data)[key]++
fmt.Println(model.Data)
go model.AutoSave()
}
func (model *Model) AutoSave() bool {
if time.Since(model.LastSave).Seconds() > SaveInterval {
model.DBWrite()
model.LastSave = time.Now()
return true
}
return false
}
type dldEntry struct {
ip string
path string
}
// DldCounter is used to validate duplicated download requests
type DldCounter struct {
IPHistory map[dldEntry]time.Time
}
var DCounter DldCounter = DldCounter{}
var DldInterval float64 = 60.0 // seconds
func (counter *DldCounter) Validate(ip string, path string) bool {
if counter.IPHistory == nil {
counter.IPHistory = make(map[dldEntry]time.Time)
}
entry := dldEntry{ip, path}
if time.Since(counter.IPHistory[entry]).Seconds() > DldInterval {
counter.IPHistory[entry] = time.Now()
return true
}
return false
}

View File

@@ -1,6 +1,7 @@
FROM golang:1.13
WORKDIR /app/gohttpserver
ADD . /app/gohttpserver
FROM golang:1.10
WORKDIR /go/src/github.com/codeskyblue/gohttpserver
ADD . /go/src/github.com/codeskyblue/gohttpserver/
RUN go get -v
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags '-X main.VERSION=docker' -o gohttpserver
FROM debian:stretch
@@ -9,7 +10,7 @@ RUN mkdir -p /app/public
RUN apt-get update && apt-get install -y ca-certificates
VOLUME /app/public
ADD assets ./assets
COPY --from=0 /app/gohttpserver/gohttpserver .
COPY --from=0 /go/src/github.com/codeskyblue/gohttpserver/gohttpserver .
EXPOSE 8000
ENTRYPOINT [ "/app/gohttpserver", "--root=/app/public" ]
CMD []

View File

@@ -1,6 +1,7 @@
FROM golang:1.13
WORKDIR /appsrc/gohttpserver
ADD . /appsrc/gohttpserver
FROM golang:1.10
WORKDIR /go/src/github.com/codeskyblue/gohttpserver
ADD . /go/src/github.com/codeskyblue/gohttpserver/
RUN go get -v
RUN GOOS=linux GOARCH=arm go build -ldflags '-X main.VERSION=docker' -o gohttpserver .
FROM multiarch/debian-debootstrap:armhf-stretch
@@ -9,7 +10,7 @@ RUN mkdir -p /app/public
RUN apt-get update && apt-get install -y ca-certificates
VOLUME /app/public
ADD assets ./assets
COPY --from=0 /appsrc/gohttpserver/gohttpserver .
COPY --from=0 /go/src/github.com/codeskyblue/gohttpserver/gohttpserver .
EXPOSE 8000
ENTRYPOINT [ "/app/gohttpserver", "--root=/app/public" ]
CMD []

View File

@@ -9,12 +9,8 @@ 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
sed -i '/experimental/d' $HOME/.docker/config.json
sed -i '1a"experimental": "enabled",' $HOME/.docker/config.json
docker manifest create codeskyblue/gohttpserver \
codeskyblue/gohttpserver:latest \
codeskyblue/gohttpserver:armhf
@@ -25,4 +21,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

16
go.mod
View File

@@ -1,11 +1,9 @@
module github.com/codeskyblue/gohttpserver
go 1.16
require (
github.com/alecthomas/kingpin v2.2.6+incompatible
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf
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
@@ -15,13 +13,9 @@ require (
github.com/gorilla/handlers v1.4.0
github.com/gorilla/mux v1.6.2
github.com/gorilla/sessions v1.1.3
github.com/pkg/errors v0.8.0 // indirect
github.com/pkg/errors v0.8.0
github.com/shogo82148/androidbinary v0.0.0-20180627093851-01c4bfa8b3b5
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371 // indirect
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
golang.org/x/text v0.3.3
golang.org/x/tools v0.1.0 // indirect
howett.net/plist v0.0.0-20201203080718-1454fab16a06 // indirect
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a
)

91
go.sum
View File

@@ -1,91 +0,0 @@
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/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371 h1:SWV2fHctRpRrp49VXJ6UZja7gU9QLHwRpIPBN89SKEo=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/vfsgen v0.0.0-20181020040650-a97a25d856ca h1:3fECS8atRjByijiI8yYiuwLwQ2ZxXobW7ua/8GRB3pI=
github.com/shurcooL/vfsgen v0.0.0-20181020040650-a97a25d856ca/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
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.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/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-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
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.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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

@@ -10,6 +10,7 @@ import (
"io/ioutil"
"log"
"mime"
"net"
"net/http"
"net/url"
"os"
@@ -50,12 +51,13 @@ type HTTPStaticServer struct {
PlistProxy string
GoogleTrackerID string
AuthType string
DBModel Model
indexes []IndexFileItem
m *mux.Router
}
func NewHTTPStaticServer(root string) *HTTPStaticServer {
func NewHTTPStaticServer(root string, dbmodel Model) *HTTPStaticServer {
if root == "" {
root = "./"
}
@@ -66,9 +68,10 @@ func NewHTTPStaticServer(root string) *HTTPStaticServer {
log.Printf("root path: %s\n", root)
m := mux.NewRouter()
s := &HTTPStaticServer{
Root: root,
Theme: "black",
m: m,
Root: root,
Theme: "black",
m: m,
DBModel: dbmodel,
}
go func() {
@@ -83,12 +86,20 @@ func NewHTTPStaticServer(root string) *HTTPStaticServer {
}
}()
m.HandleFunc("/-/status", s.hStatus)
m.HandleFunc("/-/zip/{path:.*}", s.hZip)
m.HandleFunc("/-/unzip/{zip_path:.*}/-/{path:.*}", s.hUnzip)
m.HandleFunc("/-/json/{path:.*}", s.hJSONList)
// routers for Apple *.ipa
m.HandleFunc("/-/ipa/plist/{path:.*}", s.hPlist)
m.HandleFunc("/-/ipa/link/{path:.*}", s.hIpaLink)
// TODO: /ipa/info
m.HandleFunc("/-/info/{path:.*}", s.hInfo)
m.HandleFunc("/-/mkdir/{path:.*}", s.hMkdir)
m.HandleFunc("/{path:.*}", s.hIndex).Methods("GET", "HEAD")
m.HandleFunc("/{path:.*}", s.hUploadOrMkdir).Methods("POST")
m.HandleFunc("/{path:.*}", s.hUpload).Methods("POST")
m.HandleFunc("/{path:.*}", s.hDelete).Methods("DELETE")
return s
}
@@ -100,21 +111,6 @@ func (s *HTTPStaticServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (s *HTTPStaticServer) hIndex(w http.ResponseWriter, r *http.Request) {
path := mux.Vars(r)["path"]
relPath := filepath.Join(s.Root, path)
if r.FormValue("json") == "true" {
s.hJSONList(w, r)
return
}
if r.FormValue("op") == "info" {
s.hInfo(w, r)
return
}
if r.FormValue("op") == "archive" {
s.hZip(w, r)
return
}
log.Println("GET", path, relPath)
if r.FormValue("raw") == "false" || isDir(relPath) {
if r.Method == "HEAD" {
@@ -131,20 +127,26 @@ 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)))
DldIncre(s.DBModel, r.RemoteAddr, path)
}
http.ServeFile(w, r, relPath)
}
}
func (s *HTTPStaticServer) hStatus(w http.ResponseWriter, r *http.Request) {
data, _ := json.MarshalIndent(s, "", " ")
w.Header().Set("Content-Type", "application/json")
w.Write(data)
}
func (s *HTTPStaticServer) hMkdir(w http.ResponseWriter, req *http.Request) {
path := filepath.Dir(mux.Vars(req)["path"])
path := 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"])
name := req.FormValue("name")
if err := checkFilename(name); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
@@ -158,29 +160,23 @@ func (s *HTTPStaticServer) hMkdir(w http.ResponseWriter, req *http.Request) {
}
func (s *HTTPStaticServer) hDelete(w http.ResponseWriter, req *http.Request) {
// only can delete file now
path := mux.Vars(req)["path"]
path = filepath.Clean(path) // for safe reason, prevent path contain ..
auth := s.readAccessConf(path)
log.Printf("%#v", auth)
if !auth.canDelete(req) {
http.Error(w, "Delete forbidden", http.StatusForbidden)
return
}
// TODO: path safe check
err := os.RemoveAll(filepath.Join(s.Root, path))
err := os.Remove(filepath.Join(s.Root, path))
if err != nil {
pathErr, ok := err.(*os.PathError)
if ok {
http.Error(w, pathErr.Op+" "+path+": "+pathErr.Err.Error(), 500)
} else {
http.Error(w, err.Error(), 500)
}
http.Error(w, err.Error(), 500)
return
}
w.Write([]byte("Success"))
}
func (s *HTTPStaticServer) hUploadOrMkdir(w http.ResponseWriter, req *http.Request) {
func (s *HTTPStaticServer) hUpload(w http.ResponseWriter, req *http.Request) {
path := mux.Vars(req)["path"]
dirpath := filepath.Join(s.Root, path)
@@ -192,24 +188,6 @@ func (s *HTTPStaticServer) hUploadOrMkdir(w http.ResponseWriter, req *http.Reque
}
file, header, err := req.FormFile("file")
if _, err := os.Stat(dirpath); os.IsNotExist(err) {
if err := os.MkdirAll(dirpath, os.ModePerm); err != nil {
log.Println("Create directory:", err)
http.Error(w, "Directory create "+err.Error(), http.StatusInternalServerError)
return
}
}
if file == nil { // only mkdir
w.Header().Set("Content-Type", "application/json;charset=utf-8")
json.NewEncoder(w).Encode(map[string]interface{}{
"success": true,
"destination": dirpath,
})
return
}
if err != nil {
log.Println("Parse form file:", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -230,49 +208,19 @@ func (s *HTTPStaticServer) hUploadOrMkdir(w http.ResponseWriter, req *http.Reque
}
dstPath := filepath.Join(dirpath, filename)
// Large file (>32MB) will store in tmp directory
// The quickest operation is call os.Move instead of os.Copy
// Note: it seems not working well, os.Rename might be failed
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 copyErr != nil {
defer dst.Close()
if _, err := io.Copy(dst, file); err != nil {
log.Println("Handle upload file:", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json;charset=utf-8")
if req.FormValue("unzip") == "true" {
err = unzipFile(dstPath, dirpath)
os.Remove(dstPath)
message := "success"
if err != nil {
message = err.Error()
}
json.NewEncoder(w).Encode(map[string]interface{}{
"success": err == nil,
"description": message,
})
return
}
json.NewEncoder(w).Encode(map[string]interface{}{
"success": true,
"destination": dstPath,
@@ -310,7 +258,10 @@ 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)
if !isFile(relPath) {
http.Error(w, "Not a file", 403)
return
}
fi, err := os.Stat(relPath)
if err != nil {
http.Error(w, err.Error(), 500)
@@ -329,8 +280,6 @@ func (s *HTTPStaticServer) hInfo(w http.ResponseWriter, r *http.Request) {
case ".apk":
fji.Type = "apk"
fji.Extra = parseApkInfo(relPath)
case "":
fji.Type = "dir"
default:
fji.Type = "text"
}
@@ -341,6 +290,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"]
DldIncre(s.DBModel, r.RemoteAddr, path)
CompressToZip(w, filepath.Join(s.Root, path))
}
@@ -358,9 +308,13 @@ func (s *HTTPStaticServer) hUnzip(w http.ResponseWriter, r *http.Request) {
}
}
func combineURL(r *http.Request, path string) *url.URL {
func genURLStr(r *http.Request, path string) *url.URL {
scheme := "http"
if r.TLS != nil {
scheme = "https"
}
return &url.URL{
Scheme: r.URL.Scheme,
Scheme: scheme,
Host: r.Host,
Path: path,
}
@@ -399,11 +353,9 @@ func (s *HTTPStaticServer) hPlist(w http.ResponseWriter, r *http.Request) {
func (s *HTTPStaticServer) hIpaLink(w http.ResponseWriter, r *http.Request) {
path := mux.Vars(r)["path"]
var plistUrl string
if r.URL.Scheme == "https" {
plistUrl = combineURL(r, "/-/ipa/plist/"+path).String()
} else if s.PlistProxy != "" {
plistUrl := genURLStr(r, "/-/ipa/plist/"+path).String()
if r.TLS == nil {
// send plist to plistproxy and get a https link
httpPlistLink := "http://" + r.Host + "/-/ipa/plist/" + path
url, err := s.genPlistLink(httpPlistLink)
if err != nil {
@@ -411,9 +363,6 @@ func (s *HTTPStaticServer) hIpaLink(w http.ResponseWriter, r *http.Request) {
return
}
plistUrl = url
} else {
http.Error(w, "500: Server should be https:// or provide valid plistproxy", 500)
return
}
w.Header().Set("Content-Type", "text/html")
@@ -463,6 +412,7 @@ type HTTPFileInfo struct {
Type string `json:"type"`
Size int64 `json:"size"`
ModTime int64 `json:"mtime"`
DldCnt int `json:"dldcnt"`
}
type AccessTable struct {
@@ -597,6 +547,7 @@ func (s *HTTPStaticServer) hJSONList(w http.ResponseWriter, r *http.Request) {
Name: info.Name(),
Path: path,
ModTime: info.ModTime().UnixNano() / 1e6,
DldCnt: (*s.DBModel.Data)[path],
}
if search != "" {
name, err := filepath.Rel(requestPath, path)
@@ -816,3 +767,10 @@ func checkFilename(name string) error {
}
return nil
}
func DldIncre(model Model, rAddress string, path string) {
ip, _, _ := net.SplitHostPort(rAddress)
if DCounter.Validate(ip, path) {
model.Incre(path)
}
}

10
main.go
View File

@@ -144,7 +144,8 @@ func main() {
}
log.SetFlags(log.Lshortfile | log.LstdFlags)
ss := NewHTTPStaticServer(gcfg.Root)
DBModel.DBRead("ghs-stats.json")
ss := NewHTTPStaticServer(gcfg.Root, DBModel) // FIXME(ssx): should put DBModel inside HTTPStaticServer
ss.Theme = gcfg.Theme
ss.Title = gcfg.Title
ss.GoogleTrackerID = gcfg.GoogleTrackerID
@@ -160,10 +161,7 @@ func main() {
u.Scheme = "https"
ss.PlistProxy = u.String()
}
if ss.PlistProxy != "" {
log.Printf("plistproxy: %s", strconv.Quote(ss.PlistProxy))
}
var hdlr http.Handler = ss
hdlr = accesslog.NewLoggingHandler(hdlr, logger)
@@ -180,8 +178,6 @@ func main() {
handleOpenID(gcfg.Auth.OpenID, false) // FIXME(ssx): set secure default to false
// case "github":
// handleOAuth2ID(gcfg.Auth.Type, gcfg.Auth.ID, gcfg.Auth.Secret) // FIXME(ssx): set secure default to false
case "oauth2-proxy":
handleOauth2()
}
// CORS

View File

@@ -1,27 +0,0 @@
package main
import (
"encoding/json"
"net/http"
"net/url"
)
func handleOauth2() {
http.HandleFunc("/-/user", func(w http.ResponseWriter, r *http.Request) {
fullNameMap, _ := url.ParseQuery(r.Header.Get("X-Auth-Request-Fullname"))
var fullName string
for k := range fullNameMap {
fullName = k
break
}
user := &UserInfo{
Email: r.Header.Get("X-Auth-Request-Email"),
Name: fullName,
NickName: r.Header.Get("X-Auth-Request-User"),
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
data, _ := json.Marshal(user)
w.Write(data)
})
}

View File

@@ -3,7 +3,6 @@ package main
import (
"net"
"net/http"
"os"
"strings"
)
@@ -72,11 +71,3 @@ func getLocalIP() string {
}
return ""
}
func fileExists(path string) bool {
info, err := os.Stat(path)
if err != nil {
return false
}
return !info.IsDir()
}

56
zip.go
View File

@@ -14,7 +14,6 @@ import (
"strings"
dkignore "github.com/codeskyblue/dockerignore"
"golang.org/x/text/encoding/simplifiedchinese"
)
type Zip struct {
@@ -131,58 +130,3 @@ func ExtractFromZip(zipFile, path string, w io.Writer) (err error) {
}
return fmt.Errorf("File %s not found", strconv.Quote(path))
}
func unzipFile(filename, dest string) error {
zr, err := zip.OpenReader(filename)
if err != nil {
return err
}
defer zr.Close()
if dest == "" {
dest = filepath.Dir(filename)
}
for _, f := range zr.File {
rc, err := f.Open()
if err != nil {
return err
}
defer rc.Close()
// ignore .ghs.yml
filename := sanitizedName(f.Name)
if filepath.Base(filename) == ".ghs.yml" {
continue
}
fpath := filepath.Join(dest, filename)
// filename maybe GBK or UTF-8
// Ref: https://studygolang.com/articles/3114
if f.Flags&(1<<11) == 0 { // GBK
tr := simplifiedchinese.GB18030.NewDecoder()
fpathUtf8, err := tr.String(fpath)
if err == nil {
fpath = fpathUtf8
}
}
if f.FileInfo().IsDir() {
os.MkdirAll(fpath, os.ModePerm)
continue
}
os.MkdirAll(filepath.Dir(fpath), os.ModePerm)
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
_, err = io.Copy(outFile, rc)
outFile.Close()
if err != nil {
return err
}
}
return nil
}

View File

@@ -3,18 +3,13 @@ package main
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
)
func TestExtractFromZip(t *testing.T) {
buf := bytes.NewBuffer(nil)
err := ExtractFromZip("testdata/test.zip", "**/foo.txt", buf)
assert.Nil(t, err)
if err != nil {
t.Fatal(err)
}
t.Log("Content: " + buf.String())
}
//func TestUnzipTo(t *testing.T){
// err := unzipFile("testdata.zip", "./tmp")
// assert.Nil(t, err)
//}