Skip to content

Commit

Permalink
Add TLS support (fixes #4), clean up error handling.
Browse files Browse the repository at this point in the history
  • Loading branch information
sgreben committed Nov 22, 2020
1 parent 0493996 commit 512a8ce
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 42 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VERSION = 1.5.3
VERSION = 1.6.0

APP := http-file-server
PACKAGES := $(shell go list -f {{.Dir}} ./...)
Expand Down
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- [Serving multiple paths, setting the HTTP port via CLI arguments](#serving-multiple-paths-setting-the-http-port-via-cli-arguments)
- [Setting the HTTP port via environment variables](#setting-the-http-port-via-environment-variables)
- [Uploading files using cURL](#uploading-files-using-curl)
- [HTTPS (SSL/TLS)](#https-ssltls)
- [Get it](#get-it)
- [Using `go get`](#using-go-get)
- [Pre-built binary](#pre-built-binary)
Expand Down Expand Up @@ -71,6 +72,15 @@ $ ./http-file-server -uploads /=/path/to/serve
curl -LF "[email protected]" localhost:8080/path/to/upload/to
```

### HTTPS (SSL/TLS)

To terminate SSL at the file server, set `-ssl-cert` (`SSL_CERTIFICATE`) and `-ssl-key` (`SSL_KEY`) to the respective files' paths:

```sh
$ ./http-file-server -port 8443 -ssl-cert server.crt -ssl-key server.key
2020/03/10 22:00:54 http-file-server (HTTPS) listening on ":8443"
```

## Get it

### Using `go get`
Expand All @@ -85,14 +95,14 @@ Or [download a binary](https://github.com/sgreben/http-file-server/releases/late

```sh
# Linux
curl -L https://github.com/sgreben/http-file-server/releases/download/1.5.3/http-file-server_1.5.3_linux_x86_64.tar.gz | tar xz
curl -L https://github.com/sgreben/http-file-server/releases/download/1.6.0/http-file-server_1.6.0_linux_x86_64.tar.gz | tar xz

# OS X
curl -L https://github.com/sgreben/http-file-server/releases/download/1.5.3/http-file-server_1.5.3_osx_x86_64.tar.gz | tar xz
curl -L https://github.com/sgreben/http-file-server/releases/download/1.6.0/http-file-server_1.6.0_osx_x86_64.tar.gz | tar xz

# Windows
curl -LO https://github.com/sgreben/http-file-server/releases/download/1.5.3/http-file-server_1.5.3_windows_x86_64.zip
unzip http-file-server_1.5.3_windows_x86_64.zip
curl -LO https://github.com/sgreben/http-file-server/releases/download/1.6.0/http-file-server_1.6.0_windows_x86_64.zip
unzip http-file-server_1.6.0_windows_x86_64.zip
```

## Use it
Expand All @@ -118,6 +128,10 @@ Usage of http-file-server:
(alias for -route)
-route value
a route definition ROUTE=PATH (ROUTE defaults to basename of PATH if omitted)
-ssl-cert string
path to SSL server certificate (environment variable "SSL_CERTIFICATE")
-ssl-key string
path to SSL private key (environment variable "SSL_KEY")
-u (alias for -uploads)
-uploads
allow uploads (environment variable "UPLOADS")
Expand Down
10 changes: 10 additions & 0 deletions README.template.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- [Serving multiple paths, setting the HTTP port via CLI arguments](#serving-multiple-paths-setting-the-http-port-via-cli-arguments)
- [Setting the HTTP port via environment variables](#setting-the-http-port-via-environment-variables)
- [Uploading files using cURL](#uploading-files-using-curl)
- [HTTPS (SSL/TLS)](#https-ssltls)
- [Get it](#get-it)
- [Using `go get`](#using-go-get)
- [Pre-built binary](#pre-built-binary)
Expand Down Expand Up @@ -71,6 +72,15 @@ $ ./http-file-server -uploads /=/path/to/serve
curl -LF "[email protected]" localhost:8080/path/to/upload/to
```

### HTTPS (SSL/TLS)

To terminate SSL at the file server, set `-ssl-cert` (`SSL_CERTIFICATE`) and `-ssl-key` (`SSL_KEY`) to the respective files' paths:

```sh
$ ./http-file-server -port 8443 -ssl-cert server.crt -ssl-key server.key
2020/03/10 22:00:54 http-file-server (HTTPS) listening on ":8443"
```

## Get it

### Using `go get`
Expand Down
26 changes: 18 additions & 8 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,25 @@ import (
)

const (
defaultAddr = ":8080"
addrEnvVarName = "ADDR"
portEnvVarName = "PORT"
quietEnvVarName = "QUIET"
allowUploadsEnvVarName = "UPLOADS"
rootRoute = "/"
addrEnvVarName = "ADDR"
allowUploadsEnvVarName = "UPLOADS"
defaultAddr = ":8080"
portEnvVarName = "PORT"
quietEnvVarName = "QUIET"
rootRoute = "/"
sslCertificateEnvVarName = "SSL_CERTIFICATE"
sslKeyEnvVarName = "SSL_KEY"
)

var (
addrFlag = os.Getenv(addrEnvVarName)
allowUploadsFlag = os.Getenv(allowUploadsEnvVarName) == "true"
portFlag64, _ = strconv.ParseInt(os.Getenv(portEnvVarName), 10, 64)
portFlag = int(portFlag64)
quietFlag = os.Getenv(quietEnvVarName) == "true"
allowUploadsFlag = os.Getenv(allowUploadsEnvVarName) == "true"
routesFlag routes
sslCertificate = os.Getenv(sslCertificateEnvVarName)
sslKey = os.Getenv(sslKeyEnvVarName)
)

func init() {
Expand All @@ -46,6 +50,8 @@ func init() {
flag.BoolVar(&allowUploadsFlag, "u", allowUploadsFlag, "(alias for -uploads)")
flag.Var(&routesFlag, "route", routesFlag.help())
flag.Var(&routesFlag, "r", "(alias for -route)")
flag.StringVar(&sslCertificate, "ssl-cert", sslCertificate, fmt.Sprintf("path to SSL server certificate (environment variable %q)", sslCertificateEnvVarName))
flag.StringVar(&sslKey, "ssl-key", sslKey, fmt.Sprintf("path to SSL private key (environment variable %q)", sslKeyEnvVarName))
flag.Parse()
if quietFlag {
log.SetOutput(ioutil.Discard)
Expand Down Expand Up @@ -76,7 +82,7 @@ func server(addr string, routes routes) error {
paths := make(map[string]string)

if len(routes.Values) == 0 {
routes.Set(".")
_ = routes.Set(".")
}

for _, route := range routes.Values {
Expand Down Expand Up @@ -104,6 +110,10 @@ func server(addr string, routes routes) error {
if binaryPath == "" {
binaryPath = "server"
}
if sslCertificate != "" && sslKey != "" {
log.Printf("%s (HTTPS) listening on %q", filepath.Base(binaryPath), addr)
return http.ListenAndServeTLS(addr, sslCertificate, sslKey, mux)
}
log.Printf("%s listening on %q", filepath.Base(binaryPath), addr)
return http.ListenAndServe(addr, mux)
}
Expand Down
70 changes: 41 additions & 29 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,39 +116,41 @@ var (
directoryListingTemplate = template.Must(template.New("").Parse(directoryListingTemplateText))
)

func (f *fileHandler) serveStatus(w http.ResponseWriter, r *http.Request, status int) {
func (f *fileHandler) serveStatus(w http.ResponseWriter, r *http.Request, status int) error {
w.WriteHeader(status)
w.Write([]byte(http.StatusText(status)))
_, err := w.Write([]byte(http.StatusText(status)))
if err != nil {
return err
}
return nil
}

func (f *fileHandler) serveTarGz(w http.ResponseWriter, r *http.Request, path string) {
func (f *fileHandler) serveTarGz(w http.ResponseWriter, r *http.Request, path string) error {
w.Header().Set("Content-Type", tarGzContentType)
name := filepath.Base(path) + ".tar.gz"
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename=%q`, name))
tarGz(w, path)
return tarGz(w, path)
}

func (f *fileHandler) serveZip(w http.ResponseWriter, r *http.Request, osPath string) {
func (f *fileHandler) serveZip(w http.ResponseWriter, r *http.Request, osPath string) error {
w.Header().Set("Content-Type", zipContentType)
name := filepath.Base(osPath) + ".zip"
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename=%q`, name))
zip(w, osPath)
return zip(w, osPath)
}

func (f *fileHandler) serveDir(w http.ResponseWriter, r *http.Request, osPath string) {
func (f *fileHandler) serveDir(w http.ResponseWriter, r *http.Request, osPath string) error {
d, err := os.Open(osPath)
if err != nil {
f.serveStatus(w, r, http.StatusInternalServerError)
return
return err
}
files, err := d.Readdir(-1)
if err != nil {
f.serveStatus(w, r, http.StatusInternalServerError)
return
return err
}
sort.Slice(files, func(i, j int) bool { return files[i].Name() < files[j].Name() })
w.Header().Set("Content-Type", "text/html; charset=utf-8")
directoryListingTemplate.Execute(w, directoryListingData{
return directoryListingTemplate.Execute(w, directoryListingData{
AllowUpload: f.allowUpload,
Title: func() string {
relPath, _ := filepath.Rel(f.path, osPath)
Expand Down Expand Up @@ -194,33 +196,31 @@ func (f *fileHandler) serveDir(w http.ResponseWriter, r *http.Request, osPath st
})
}

func (f *fileHandler) serveUploadTo(w http.ResponseWriter, r *http.Request, osPath string) {
func (f *fileHandler) serveUploadTo(w http.ResponseWriter, r *http.Request, osPath string) error {
if err := r.ParseForm(); err != nil {
f.serveStatus(w, r, http.StatusInternalServerError)
return
return err
}
in, h, err := r.FormFile("file")
if err == http.ErrMissingFile {
w.Header().Set("Location", r.URL.String())
w.WriteHeader(303)
}
if err != nil {
f.serveStatus(w, r, http.StatusInternalServerError)
return
return err
}
outPath := filepath.Join(osPath, filepath.Base(h.Filename))
out, err := os.OpenFile(outPath, os.O_CREATE|os.O_WRONLY, 0600)
defer out.Close()
if err != nil {
f.serveStatus(w, r, http.StatusInternalServerError)
return
return err
}
defer out.Close()

if _, err := io.Copy(out, in); err != nil {
f.serveStatus(w, r, http.StatusInternalServerError)
return
return err
}
w.Header().Set("Location", r.URL.String())
w.WriteHeader(303)
return nil
}

// ServeHTTP is http.Handler.ServeHTTP
Expand All @@ -239,19 +239,31 @@ func (f *fileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
info, err := os.Stat(osPath)
switch {
case os.IsNotExist(err):
f.serveStatus(w, r, http.StatusNotFound)
_ = f.serveStatus(w, r, http.StatusNotFound)
case os.IsPermission(err):
f.serveStatus(w, r, http.StatusForbidden)
_ = f.serveStatus(w, r, http.StatusForbidden)
case err != nil:
f.serveStatus(w, r, http.StatusInternalServerError)
_ = f.serveStatus(w, r, http.StatusInternalServerError)
case r.URL.Query().Get(zipKey) != "":
f.serveZip(w, r, osPath)
err := f.serveZip(w, r, osPath)
if err != nil {
_ = f.serveStatus(w, r, http.StatusInternalServerError)
}
case r.URL.Query().Get(tarGzKey) != "":
f.serveTarGz(w, r, osPath)
err := f.serveTarGz(w, r, osPath)
if err != nil {
_ = f.serveStatus(w, r, http.StatusInternalServerError)
}
case f.allowUpload && info.IsDir() && r.Method == http.MethodPost:
f.serveUploadTo(w, r, osPath)
err := f.serveUploadTo(w, r, osPath)
if err != nil {
_ = f.serveStatus(w, r, http.StatusInternalServerError)
}
case info.IsDir():
f.serveDir(w, r, osPath)
err := f.serveDir(w, r, osPath)
if err != nil {
_ = f.serveStatus(w, r, http.StatusInternalServerError)
}
default:
http.ServeFile(w, r, osPath)
}
Expand Down
3 changes: 3 additions & 0 deletions tar.gz.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ func tarGz(w io.Writer, path string) error {
defer file.Close()
header := new(tar.Header)
path, err = filepath.Rel(basePath, path)
if err != nil {
return err
}
header.Name = path
header.Size = stat.Size()
header.Mode = int64(stat.Mode())
Expand Down
3 changes: 3 additions & 0 deletions zip.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ func zip(w io.Writer, path string) error {
}
defer file.Close()
path, err = filepath.Rel(basePath, path)
if err != nil {
return err
}
zw, err := w.Create(path)
if err != nil {
return err
Expand Down

0 comments on commit 512a8ce

Please sign in to comment.