├── .gitignore ├── Dockerfile ├── README.md ├── build.sh ├── dereg.sh ├── doc ├── fileserver2-web1.png ├── fileserver2-web2.png ├── fileserver2-web3.png ├── fileserver2-web4.png ├── reg.png ├── webupload1.png └── webupload2.png ├── go.mod ├── go.sum ├── main.go ├── reg.sh ├── start.sh ├── upload.go ├── uploadPage.go └── upload_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | fileserver2 2 | bin 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | WORKDIR /app 3 | COPY fileserver2 /app/fs 4 | RUN mkdir /data 5 | #ENTRYPOINT /app/fs 6 | CMD /app/fs -path /data 7 | VOLUME ["/data"] 8 | EXPOSE 8000 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fileserver2 2 | 3 | A http download and upload server 4 | 5 | ## Features 6 | 7 | * Default to truncate if file exist 8 | * Delete support 9 | * Truncate support 10 | * Form value upload 11 | * File upload ( no need extra static index page anymore ) 12 | * Any level path specify 13 | * Web Any path upload support 14 | 15 | ## Install 16 | 17 | ``` 18 | go get github.com/chinglinwen/fileserver2 19 | ``` 20 | 21 | ## Examples 22 | 23 | ### File upload 24 | 25 | ``` 26 | [~ t1 ] $ cat example.log 27 | example body 28 | [~ t1 ] $ curl localhost:8000/uploadapi -F file=@example.log 29 | Files uploaded successfully : example.log 13 bytes 30 | ``` 31 | 32 | Results 33 | 34 | ``` 35 | [~ t ] $ cat example.log 36 | example body 37 | ``` 38 | 39 | ### Form upload 40 | 41 | ``` 42 | [~ t1 ] $ curl localhost:8000/uploadapi -F file=test -F data="test body" 43 | Files uploaded successfully : test 9 bytes 44 | ``` 45 | 46 | ### Path specify 47 | 48 | ``` 49 | [~ t1 ] $ curl localhost:8000/uploadapi -F file="a/b/c" -F data="hello" 50 | Files uploaded successfully : a/b/c 5 bytes 51 | 52 | [~ t1 ] $ curl localhost:8000/uploadapi -F file="a/b/c" -F file=@a.txt 53 | Files uploaded successfully : a/b/c 3 bytes 54 | 55 | [~ t1 ] $ curl localhost:8000/uploadapi -F file="../a/b/c" -F file=@a.txt 56 | file path should not contain the two dot 57 | ``` 58 | 59 | or 60 | 61 | ``` 62 | [~ t1 ] $ curl localhost:8000/a/b/uploadapi -F file="c" -F data="hello" 63 | Files uploaded successfully : a/b/c 5 bytes 64 | 65 | [~ t1 ] $ curl localhost:8000/a/b/uploadapi -F file="c" -F file=@a.txt 66 | Files uploaded successfully : a/b/c 3 bytes 67 | 68 | [~ t1 ] $ curl localhost:8000/../a/b/uploadapi -F file="c" -F file=@a.txt # won't work 69 | ``` 70 | 71 | ### Delete 72 | 73 | ``` 74 | [~ t1 ] $ curl localhost:8000/uploadapi -F file=example.log -F delete=yes 75 | file: example.log deleted 76 | 77 | [~ t1 ] $ curl localhost:8000/uploadapi -F file="a/b/c" -F delete=yes 78 | file: a/b/c deleted 79 | 80 | [~ t1 ] $ curl localhost:8000/uploadapi -F file="a" -F delete=yes 81 | file: a deleted 82 | ``` 83 | 84 | ### Truncate 85 | 86 | if file not exist, it will create the file 87 | 88 | if file exist, by default it will append to the file 89 | 90 | use truncate to overwrite the file 91 | 92 | ``` 93 | curl localhost:8000/uploadapi -F file=@example.log -F truncate=yes 94 | ``` 95 | 96 | or 97 | 98 | ``` 99 | curl localhost:8000/uploadapi -F file=test -F data="test body" -F truncate=yes 100 | ``` 101 | 102 | ### Usage 103 | 104 | ``` 105 | $ fileserver2 -h 106 | Usage of fileserver2: 107 | -author 108 | Show author. 109 | -path string 110 | File server path. (default ".") 111 | -port string 112 | Port number. (default "8000") 113 | -v Show version. 114 | ``` 115 | 116 | Notes: **path** specify where the file will be stored 117 | 118 | 119 | ## Web examples 120 | 121 | ### Choose files 122 | 123 | ![choose_files](doc/fileserver2-web1.png) 124 | 125 | ### Specify any path in url to upload 126 | 127 | ![webupload1](doc/webupload1.png) 128 | 129 | ![webupload2](doc/webupload2.png) 130 | 131 | ### Info 132 | 133 | ![choose_files](doc/fileserver2-web2.png) 134 | 135 | ### Results 136 | 137 | ![choose_files](doc/fileserver2-web3.png) 138 | 139 | ## Download example 140 | 141 | ### Web 142 | 143 | ![download](doc/fileserver2-web4.png) 144 | 145 | ### CLI 146 | 147 | ``` 148 | [~ t1 ] $ wget "localhost:8000/Track 1.wav" 149 | --2016-06-23 11:43:18-- http://localhost:8000/Track%201.wav 150 | Resolving localhost... ::1, 127.0.0.1 151 | Connecting to localhost|::1|:8000... connected. 152 | HTTP request sent, awaiting response... 200 OK 153 | Length: 26869360 (26M) [audio/x-wav] 154 | Saving to: `Track 1.wav' 155 | 156 | 100%[=============================================================================>] 26,869,360 --.-K/s in 0.1s 157 | 158 | 2016-06-23 11:43:18 (240 MB/s) - `Track 1.wav' saved [26869360/26869360] 159 | 160 | [~ t1 ] $ 161 | ``` 162 | 163 | ## Service register and deregister 164 | 165 | register 166 | 167 | ``` 168 | ./reg.sh 169 | ``` 170 | 171 | deregister 172 | 173 | ``` 174 | ./dereg.sh 175 | ``` 176 | 177 | ![register](doc/reg.png) 178 | 179 | end 180 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # . ~/.bashrc 4 | # rm -f fileserver2 fileserver2.exe 5 | # build32 6 | # winbuild 7 | 8 | # ver="$( grep 'version=' *.go | awk '{ print $2 }' FS='=' | \ 9 | # awk '{ print $1 }' FS=',' )" 10 | # tar -czf fileserver2.v$ver.tar.gz fileserver2 fileserver2.exe 11 | 12 | go build -o bin/fileserver2 *.go 13 | 14 | # end. 15 | -------------------------------------------------------------------------------- /dereg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # de-register a service 3 | 4 | service="fileserver2" 5 | 6 | url="http://localhost:8500/v1/agent/service/deregister/$service" 7 | curl "$url" 8 | 9 | # end. 10 | 11 | -------------------------------------------------------------------------------- /doc/fileserver2-web1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinglinwen/fileserver2/0899722422382b776ccf2e4beecb032a5d42afd1/doc/fileserver2-web1.png -------------------------------------------------------------------------------- /doc/fileserver2-web2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinglinwen/fileserver2/0899722422382b776ccf2e4beecb032a5d42afd1/doc/fileserver2-web2.png -------------------------------------------------------------------------------- /doc/fileserver2-web3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinglinwen/fileserver2/0899722422382b776ccf2e4beecb032a5d42afd1/doc/fileserver2-web3.png -------------------------------------------------------------------------------- /doc/fileserver2-web4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinglinwen/fileserver2/0899722422382b776ccf2e4beecb032a5d42afd1/doc/fileserver2-web4.png -------------------------------------------------------------------------------- /doc/reg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinglinwen/fileserver2/0899722422382b776ccf2e4beecb032a5d42afd1/doc/reg.png -------------------------------------------------------------------------------- /doc/webupload1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinglinwen/fileserver2/0899722422382b776ccf2e4beecb032a5d42afd1/doc/webupload1.png -------------------------------------------------------------------------------- /doc/webupload2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chinglinwen/fileserver2/0899722422382b776ccf2e4beecb032a5d42afd1/doc/webupload2.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/chinglinwen/fileserver2 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/chinglinwen/log v0.0.0-20180802093412-402fdc33bf76 7 | github.com/natefinch/lumberjack v2.0.0+incompatible 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/chinglinwen/log v0.0.0-20180802093412-402fdc33bf76 h1:CupGZ+1pK0bVmwlLsIv86p50M3doqJU5wx8XXXRz8vY= 2 | github.com/chinglinwen/log v0.0.0-20180802093412-402fdc33bf76/go.mod h1:ScnZN8A3LOA1xcpbZqAnhgcmGk/nPpHcHk/kxs0JcMk= 3 | github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= 4 | github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= 5 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base64" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "os" 10 | "strings" 11 | 12 | "github.com/natefinch/lumberjack" 13 | ) 14 | 15 | var ( 16 | path string 17 | port string 18 | ) 19 | 20 | func main() { 21 | flag.StringVar(&port, "port", "9000", "Port number") 22 | flag.StringVar(&path, "path", ".", "File server path") 23 | version := flag.Bool("version", false, "Show version") 24 | author := flag.Bool("author", false, "Show author") 25 | 26 | verbose := flag.Bool("v", false, "output to logfile ( default stdout )") 27 | logFile := flag.String("logfile", "fs.log", "log filename and path") 28 | logMaxSize := flag.Int("logmaxsize", 500, "log max size(megabytes)") 29 | logMaxAge := flag.Int("logmaxage", 28, "log max age (days)") 30 | logMaxBackups := flag.Int("logmaxbackups", 3, "log max backups number") 31 | 32 | auth := flag.Bool("auth", false, "enable basic auth") 33 | downloadAuth := flag.Bool("downloadauth", false, "enable download basic auth") 34 | user := flag.String("user", "admin", "user name for basic auth") 35 | password := flag.String("password", "Admin@Qax123_@", "password for basic auth") 36 | 37 | flag.Parse() 38 | 39 | //Display version info. 40 | if *version { 41 | fmt.Println("Fileserver2 version=1.3.0, 2020-7-7") 42 | os.Exit(0) 43 | } 44 | 45 | //Display author info. 46 | if *author { 47 | fmt.Println("Author is Wen Zhenglin") 48 | os.Exit(0) 49 | } 50 | 51 | if *verbose { 52 | log.SetOutput(&lumberjack.Logger{ 53 | Filename: *logFile, 54 | MaxSize: *logMaxSize, // megabytes 55 | MaxBackups: *logMaxBackups, 56 | MaxAge: *logMaxAge, //days 57 | }) 58 | } 59 | 60 | s := server{auth: *auth, downloadAuth: *downloadAuth, user: *user, pass: *password} 61 | err := http.ListenAndServe(":"+port, s) 62 | checkError(err) 63 | } 64 | 65 | type server struct { 66 | auth bool 67 | downloadAuth bool 68 | user string 69 | pass string 70 | } 71 | 72 | func (s server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 73 | // print logs 74 | ip := strings.Split(r.RemoteAddr, ":")[0] 75 | 76 | if strings.HasSuffix(r.RequestURI, "uploadapi") { 77 | if s.auth { 78 | log.Printf("uploadapi(auth), ip: %v, uri: %v, visited", ip, r.RequestURI) 79 | BasicAuthMiddleware(http.Handler(http.HandlerFunc(uploadHandler)), s.user, s.pass).ServeHTTP(w, r) 80 | return 81 | } 82 | log.Printf("uploadapi(noauth), ip: %v, uri: %v, visited", ip, r.RequestURI) 83 | uploadHandler(w, r) 84 | return 85 | } 86 | if strings.HasSuffix(r.RequestURI, "upload") { 87 | if s.auth { 88 | log.Printf("uploadpage(auth), ip: %v, uri: %v, visited", ip, r.RequestURI) 89 | BasicAuthMiddleware(http.Handler(http.HandlerFunc(uploadPageHandler)), s.user, s.pass).ServeHTTP(w, r) 90 | return 91 | } 92 | log.Printf("uploadpage(noauth), ip: %v, uri: %v, visited", ip, r.RequestURI) 93 | uploadPageHandler(w, r) 94 | return 95 | } 96 | if s.downloadAuth { 97 | log.Printf("dwonload(auth), ip: %v, uri: %v, visited", ip, r.RequestURI) 98 | BasicAuthMiddleware(http.FileServer(http.Dir(path)), s.user, s.pass).ServeHTTP(w, r) 99 | return 100 | } 101 | log.Printf("dwonload(noauth), ip: %v, uri: %v, visited", ip, r.RequestURI) 102 | http.FileServer(http.Dir(path)).ServeHTTP(w, r) 103 | } 104 | 105 | func checkError(err error) { 106 | if err != nil { 107 | fmt.Println("Fatal error ", err.Error()) 108 | os.Exit(1) 109 | } 110 | } 111 | 112 | // BasicAuthMiddleware is a middleware that provides basic auth 113 | func BasicAuthMiddleware(next http.Handler, username, password string) http.Handler { 114 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 115 | auth := r.Header.Get("Authorization") 116 | if auth == "" { 117 | w.Header().Set("WWW-Authenticate", `Basic realm="restricted"`) 118 | http.Error(w, "Unauthorized", http.StatusUnauthorized) 119 | log.Printf("auth failed, no header\n") 120 | return 121 | } 122 | 123 | // Check if the Authorization header is in the correct format 124 | parts := strings.SplitN(auth, " ", 2) 125 | if len(parts) != 2 || parts[0] != "Basic" { 126 | http.Error(w, "Unauthorized", http.StatusUnauthorized) 127 | log.Printf("auth failed, invalid header\n") 128 | return 129 | } 130 | 131 | // Decode the base64 encoded credentials 132 | decoded, err := base64.StdEncoding.DecodeString(parts[1]) 133 | if err != nil { 134 | http.Error(w, "Unauthorized", http.StatusUnauthorized) 135 | log.Printf("auth failed, not base64\n") 136 | return 137 | } 138 | 139 | // Check the username and password 140 | creds := strings.SplitN(string(decoded), ":", 2) 141 | if len(creds) != 2 || creds[0] != username || creds[1] != password { 142 | w.Header().Set("WWW-Authenticate", `Basic realm="restricted"`) 143 | http.Error(w, "Unauthorized", http.StatusUnauthorized) 144 | log.Printf("auth failed, incorrect password\n") 145 | return 146 | } 147 | 148 | // Proceed with the next handler 149 | next.ServeHTTP(w, r) 150 | }) 151 | } 152 | -------------------------------------------------------------------------------- /reg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # registering a service 3 | 4 | service="$( cat < 12 | Go upload 13 | 14 |
15 | 16 |
17 | Optional Filename: 18 | 19 | 20 |
21 | 22 | 23 | ` 24 | t, err := template.New("page").Parse(tpl) 25 | checkError(err) 26 | t.Execute(w, strings.TrimSuffix((r.RequestURI), "/upload")) 27 | } 28 | -------------------------------------------------------------------------------- /upload_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "syscall" 5 | "testing" 6 | ) 7 | 8 | func TestAppendFlag(t *testing.T) { 9 | appendmode := "append" 10 | appendflags := mode(len(appendmode) != 0) 11 | normalflags := mode(false) 12 | if appendflags != 1089 || normalflags != 577 { 13 | t.Errorf("flags setting failed, should not equal, %v = %v", appendflags, normalflags) 14 | return 15 | } 16 | t.Logf("appendflags: %v, normalflags: %v, syscallappend: %v\n", appendflags, normalflags, syscall.O_APPEND) 17 | } 18 | --------------------------------------------------------------------------------