diff --git a/assets/templates/html/index.html.tmpl b/assets/templates/html/index.html.tmpl index f5520f5..7f6f45b 100644 --- a/assets/templates/html/index.html.tmpl +++ b/assets/templates/html/index.html.tmpl @@ -9,10 +9,10 @@ the command line. ## USAGE # Upload a file - curl -F'file=@yourfile.png' https://{{.Request.Host}} + curl -F'file=@yourfile.png' {{.Host}} # Shorten a URL - curl -F'shorten=http://example.com/some/long/url' https://{{.Request.Host}} + curl -F'shorten=http://example.com/some/long/url' {{.Host}} # The first line of the result will contain the shortened URL. # @@ -20,6 +20,6 @@ the command line. # information on how to delete the shortened object. # To upload a file and only extract the shortened URL (i.e. throw away the rest) - curl -F'file=@yourfile.png' https://{{.Request.Host}} | head -n 1 + curl -F'file=@yourfile.png' {{.Host}} | head -n 1 {{end}} diff --git a/assets/templates/html/newFileUploadPasteSuccess.html.tmpl b/assets/templates/html/newFileUploadPasteSuccess.html.tmpl index 2879864..d5f8f3b 100644 --- a/assets/templates/html/newFileUploadPasteSuccess.html.tmpl +++ b/assets/templates/html/newFileUploadPasteSuccess.html.tmpl @@ -4,13 +4,13 @@ Success - rushlink {{define "body"}}
-https://{{.Request.Host}}/{{.Paste.Key}}{{.FileExt}}
+{{.Host}}/{{.Paste.Key}}{{.FileExt}}
---
# View metadata
- curl https://{{.Request.Host}}/{{.Paste.Key}}{{.FileExt}}/meta?deleteToken={{.Paste.DeleteToken | urlquery}}
+ curl {{.Host}}/{{.Paste.Key}}{{.FileExt}}/meta?deleteToken={{.Paste.DeleteToken | urlquery}}
# Delete this object
- curl --request DELETE https://{{.Request.Host}}/{{.Paste.Key}}{{.FileExt}}?deleteToken={{.Paste.DeleteToken | urlquery}}
+ curl --request DELETE {{.Host}}/{{.Paste.Key}}{{.FileExt}}?deleteToken={{.Paste.DeleteToken | urlquery}}
{{end}}
\ No newline at end of file
diff --git a/assets/templates/html/newRedirectPasteSuccess.html.tmpl b/assets/templates/html/newRedirectPasteSuccess.html.tmpl
index 2b8f980..b1b0372 100644
--- a/assets/templates/html/newRedirectPasteSuccess.html.tmpl
+++ b/assets/templates/html/newRedirectPasteSuccess.html.tmpl
@@ -4,13 +4,13 @@ Success - rushlink
{{define "body"}}
-https://{{.Request.Host}}/{{.Paste.Key}}
+{{.Host}}/{{.Paste.Key}}
---
# View metadata
-curl https://{{.Request.Host}}/{{.Paste.Key}}/meta?deleteToken={{.Paste.DeleteToken | urlquery}}
+curl {{.Host}}/{{.Paste.Key}}/meta?deleteToken={{.Paste.DeleteToken | urlquery}}
# Delete this object
-curl --request DELETE https://{{.Request.Host}}/{{.Paste.Key}}?deleteToken={{.Paste.DeleteToken | urlquery}}
+curl --request DELETE {{.Host}}/{{.Paste.Key}}?deleteToken={{.Paste.DeleteToken | urlquery}}
{{end}}
\ No newline at end of file
diff --git a/assets/templates/txt/deletePasteSuccess.txt.tmpl b/assets/templates/txt/deletePasteSuccess.txt.tmpl
index 0ffae3c..ac40c43 100644
--- a/assets/templates/txt/deletePasteSuccess.txt.tmpl
+++ b/assets/templates/txt/deletePasteSuccess.txt.tmpl
@@ -1 +1 @@
-<{{.Request.Host}}/{{.Paste.Key}}> was succesfully deleted
+<{{.Host}}/{{.Paste.Key}}> was succesfully deleted
diff --git a/assets/templates/txt/index.txt.tmpl b/assets/templates/txt/index.txt.tmpl
index 5a02660..0dd996d 100644
--- a/assets/templates/txt/index.txt.tmpl
+++ b/assets/templates/txt/index.txt.tmpl
@@ -7,10 +7,10 @@ the command line.
## USAGE
# Upload a file
- curl -F'file=@yourfile.png' https://{{.Request.Host}}
+ curl -F'file=@yourfile.png' {{.Host}}
# Shorten a URL
- curl -F'shorten=http://example.com/some/long/url' https://{{.Request.Host}}
+ curl -F'shorten=http://example.com/some/long/url' {{.Host}}
# The first line of the result will contain the shortened URL.
#
@@ -18,4 +18,4 @@ the command line.
# information on how to delete the shortened object.
# To upload a file and only extract the shortened URL (i.e. throw away the rest)
- curl -F'file=@yourfile.png' https://{{.Request.Host}} | head -n 1
\ No newline at end of file
+ curl -F'file=@yourfile.png' {{.Host}} | head -n 1
\ No newline at end of file
diff --git a/assets/templates/txt/newFileUploadPasteSuccess.txt.tmpl b/assets/templates/txt/newFileUploadPasteSuccess.txt.tmpl
index 7100139..a94e8cd 100644
--- a/assets/templates/txt/newFileUploadPasteSuccess.txt.tmpl
+++ b/assets/templates/txt/newFileUploadPasteSuccess.txt.tmpl
@@ -1,8 +1,8 @@
-https://{{.Request.Host}}/{{.Paste.Key}}{{.FileExt}}
+{{.Host}}/{{.Paste.Key}}{{.FileExt}}
---
# View metadata
-curl https://{{.Request.Host}}/{{.Paste.Key}}{{.FileExt}}/meta?deleteToken={{.Paste.DeleteToken | urlquery}}
+curl {{.Host}}/{{.Paste.Key}}{{.FileExt}}/meta?deleteToken={{.Paste.DeleteToken | urlquery}}
# Delete this object
-curl --request DELETE https://{{.Request.Host}}/{{.Paste.Key}}{{.FileExt}}?deleteToken={{.Paste.DeleteToken | urlquery}}
+curl --request DELETE {{.Host}}/{{.Paste.Key}}{{.FileExt}}?deleteToken={{.Paste.DeleteToken | urlquery}}
diff --git a/assets/templates/txt/newRedirectPasteSuccess.txt.tmpl b/assets/templates/txt/newRedirectPasteSuccess.txt.tmpl
index 96ebba8..5b1e2f6 100644
--- a/assets/templates/txt/newRedirectPasteSuccess.txt.tmpl
+++ b/assets/templates/txt/newRedirectPasteSuccess.txt.tmpl
@@ -1,8 +1,8 @@
-https://{{.Request.Host}}/{{.Paste.Key}}
+{{.Host}}/{{.Paste.Key}}
---
# View metadata
-curl https://{{.Request.Host}}/{{.Paste.Key}}/meta?deleteToken={{.Paste.DeleteToken | urlquery}}
+curl {{.Host}}/{{.Paste.Key}}/meta?deleteToken={{.Paste.DeleteToken | urlquery}}
# Delete this object
-curl --request DELETE https://{{.Request.Host}}/{{.Paste.Key}}?deleteToken={{.Paste.DeleteToken | urlquery}}
+curl --request DELETE {{.Host}}/{{.Paste.Key}}?deleteToken={{.Paste.DeleteToken | urlquery}}
diff --git a/assets/templates/txt/pasteMeta.txt.tmpl b/assets/templates/txt/pasteMeta.txt.tmpl
index 51e0719..0eb2ceb 100644
--- a/assets/templates/txt/pasteMeta.txt.tmpl
+++ b/assets/templates/txt/pasteMeta.txt.tmpl
@@ -1,4 +1,4 @@
-METADATA on <{{.Request.Host}}/{{.Paste.Key}}>:
+METADATA on <{{.Host}}/{{.Paste.Key}}>:
TYPE: {{.Paste.Type}}
STATE: {{.Paste.State}}
@@ -12,6 +12,6 @@ DELETE TOKEN: {{.CanDelete.String}}
{{if and (ne .Paste.State.String "deleted") .CanDelete.Bool}}
```
# To delete this {{.Paste.Type}}, execute:
-curl --request "DELETE" "{{.Request.Host}}/{{.Paste.Key}}?deleteToken={{.Request.URL.Query.Get "deleteToken"}}"
+curl --request "DELETE" "{{.Host}}/{{.Paste.Key}}?deleteToken={{.Request.URL.Query.Get "deleteToken"}}"
```
{{end}}
diff --git a/cmd/rushlink/main.go b/cmd/rushlink/main.go
index 98cad7a..f5ea838 100644
--- a/cmd/rushlink/main.go
+++ b/cmd/rushlink/main.go
@@ -13,6 +13,7 @@ var (
fileStorePath = flag.String("file-store", "", "path to the directory where uploaded files will be stored")
httpListen = flag.String("listen", "127.0.0.1:8000", "listen address (host:port)")
metricsListen = flag.String("metrics_listen", "127.0.0.1:58614", "listen address for metrics (host:port)")
+ hostURL = flag.String("host", "", "host root (defaults to using the request 'Host' header with HTTPS")
)
func main() {
@@ -29,5 +30,5 @@ func main() {
defer database.Close()
go rushlink.StartMetricsServer(*metricsListen, database, filestore)
- rushlink.StartMainServer(*httpListen, database, filestore)
+ rushlink.StartMainServer(*httpListen, database, filestore, hostURL)
}
diff --git a/handlers.go b/handlers.go
index 06117ab..20b7c77 100644
--- a/handlers.go
+++ b/handlers.go
@@ -30,7 +30,7 @@ const (
const cookieDeleteToken = "owner_token"
func (rl *rushlink) indexGetHandler(w http.ResponseWriter, r *http.Request) {
- render(w, r, "index", map[string]interface{}{})
+ rl.render(w, r, "index", map[string]interface{}{})
}
func (rl *rushlink) uploadFileGetHandler(w http.ResponseWriter, r *http.Request) {
@@ -49,7 +49,7 @@ func (rl *rushlink) uploadFileGetHandler(w http.ResponseWriter, r *http.Request)
return err
}); err != nil {
if badID {
- renderError(w, r, http.StatusNotFound, "malformed file id")
+ rl.renderError(w, r, http.StatusNotFound, "malformed file id")
return
}
// unexpected error
@@ -61,7 +61,7 @@ func (rl *rushlink) uploadFileGetHandler(w http.ResponseWriter, r *http.Request)
if err != nil {
if os.IsNotExist(err) {
log.Printf("error: '%v' should exist according to the database, but it doesn't", filePath)
- renderError(w, r, http.StatusNotFound, "file not found")
+ rl.renderError(w, r, http.StatusNotFound, "file not found")
return
}
// unexpected error
@@ -114,7 +114,7 @@ func (rl *rushlink) viewPasteHandlerInner(w http.ResponseWriter, r *http.Request
}
if p == nil {
- renderError(w, r, http.StatusNotFound, "url key not found in the database")
+ rl.renderError(w, r, http.StatusNotFound, "url key not found in the database")
return
}
@@ -139,7 +139,7 @@ func (rl *rushlink) viewPasteHandlerInner(w http.ResponseWriter, r *http.Request
"Paste": p,
"CanDelete": canDelete,
}
- render(w, r, "pasteMeta", data)
+ rl.render(w, r, "pasteMeta", data)
return
}
@@ -164,7 +164,7 @@ func (rl *rushlink) viewPasteHandlerInner(w http.ResponseWriter, r *http.Request
}
fmt.Fprint(w, location)
case db.PasteStateDeleted:
- renderError(w, r, http.StatusGone, "paste has been deleted\n")
+ rl.renderError(w, r, http.StatusGone, "paste has been deleted\n")
default:
panic(errors.Errorf("invalid paste.State (%v) for key '%v'", p.State, p.Key))
}
@@ -179,7 +179,7 @@ func (rl *rushlink) newPasteHandler(w http.ResponseWriter, r *http.Request) {
// Fallthrough
} else {
msg := fmt.Sprintf("could not parse form: %v\n", err)
- renderError(w, r, http.StatusBadRequest, msg)
+ rl.renderError(w, r, http.StatusBadRequest, msg)
return
}
@@ -189,7 +189,7 @@ func (rl *rushlink) newPasteHandler(w http.ResponseWriter, r *http.Request) {
return
}
- renderError(w, r, http.StatusBadRequest, "no 'file' and no 'shorten' fields given in form\n")
+ rl.renderError(w, r, http.StatusBadRequest, "no 'file' and no 'shorten' fields given in form\n")
}
func (rl *rushlink) newFileUploadPasteHandler(w http.ResponseWriter, r *http.Request, file multipart.File, header multipart.FileHeader) {
@@ -214,7 +214,7 @@ func (rl *rushlink) newFileUploadPasteHandler(w http.ResponseWriter, r *http.Req
"Paste": paste,
"FileUpload": fu,
"FileExt": filepath.Ext(fu.FileName)}
- render(w, r, "newFileUploadPasteSuccess", data)
+ rl.render(w, r, "newFileUploadPasteSuccess", data)
}
func (rl *rushlink) newPasteHandlerURLEncoded(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
@@ -224,7 +224,7 @@ func (rl *rushlink) newPasteHandlerURLEncoded(w http.ResponseWriter, r *http.Req
}
shorten := r.PostFormValue("shorten")
if shorten == "" {
- renderError(w, r, http.StatusBadRequest, "no 'shorten' param given\n")
+ rl.renderError(w, r, http.StatusBadRequest, "no 'shorten' param given\n")
return
}
rl.newRedirectPasteHandler(w, r, shorten)
@@ -234,15 +234,15 @@ func (rl *rushlink) newRedirectPasteHandler(w http.ResponseWriter, r *http.Reque
userURL, err := url.ParseRequestURI(rawurl)
if err != nil {
msg := fmt.Sprintf("invalid url (%v): %v", err, rawurl)
- renderError(w, r, http.StatusBadRequest, msg)
+ rl.renderError(w, r, http.StatusBadRequest, msg)
return
}
if userURL.Scheme == "" {
- renderError(w, r, http.StatusBadRequest, "invalid url (unspecified scheme)\n")
+ rl.renderError(w, r, http.StatusBadRequest, "invalid url (unspecified scheme)\n")
return
}
if userURL.Host == "" {
- renderError(w, r, http.StatusBadRequest, "invalid url (unspecified host)\n")
+ rl.renderError(w, r, http.StatusBadRequest, "invalid url (unspecified host)\n")
return
}
@@ -255,7 +255,7 @@ func (rl *rushlink) newRedirectPasteHandler(w http.ResponseWriter, r *http.Reque
panic(err)
}
data := map[string]interface{}{"Paste": paste}
- render(w, r, "newRedirectPasteSuccess", data)
+ rl.render(w, r, "newRedirectPasteSuccess", data)
}
// Delete a URL from the database
@@ -265,7 +265,7 @@ func (rl *rushlink) deletePasteHandler(w http.ResponseWriter, r *http.Request) {
deleteToken := getDeleteTokenFromRequest(r)
if deleteToken == "" {
- renderError(w, r, http.StatusBadRequest, "no delete token provided\n")
+ rl.renderError(w, r, http.StatusBadRequest, "no delete token provided\n")
return
}
@@ -293,12 +293,12 @@ func (rl *rushlink) deletePasteHandler(w http.ResponseWriter, r *http.Request) {
return nil
}); err != nil {
log.Printf("error: %v\n", err)
- renderError(w, r, errorCode, fmt.Sprintf("error: %v\n", err))
+ rl.renderError(w, r, errorCode, fmt.Sprintf("error: %v\n", err))
return
}
data := map[string]interface{}{"Paste": paste}
- render(w, r, "deletePasteSuccess", data)
+ rl.render(w, r, "deletePasteSuccess", data)
}
// Add a new fileUpload redirect to the database
diff --git a/router.go b/router.go
index b18907f..33ef50f 100644
--- a/router.go
+++ b/router.go
@@ -4,23 +4,30 @@ import (
"fmt"
"log"
"net/http"
+ "net/url"
"runtime/debug"
"strconv"
"time"
"gitea.hashru.nl/dsprenkels/rushlink/internal/db"
"github.com/gorilla/mux"
+ "github.com/pkg/errors"
)
const urlKeyExpr = "{key:[A-Za-z0-9-_]{4,}}"
const urlKeyWithExtExpr = urlKeyExpr + "{ext:\\.[A-Za-z0-9-_]+}"
type rushlink struct {
- db *db.Database
- fs *db.FileStore
+ db *db.Database
+ fs *db.FileStore
+ host *url.URL
}
-func recoveryMiddleware(next http.Handler) http.Handler {
+func (rl *rushlink) Host() *url.URL {
+ return rl.host
+}
+
+func (rl *rushlink) recoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
defer func() {
@@ -34,13 +41,22 @@ func recoveryMiddleware(next http.Handler) http.Handler {
if err := recover(); err != nil {
log.Printf("error: %v\n", err)
debug.PrintStack()
- renderInternalServerError(w, r, err)
+ rl.renderInternalServerError(w, r, err)
}
}()
next.ServeHTTP(w, r)
})
}
+func (rl *rushlink) metricsMiddleware(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ srw := statusResponseWriter{Inner: w}
+ next.ServeHTTP(&srw, r)
+ status := strconv.Itoa(srw.StatusCode)
+ metricRequestsTotalCounter.WithLabelValues(status, r.Method).Inc()
+ })
+}
+
type statusResponseWriter struct {
Inner http.ResponseWriter
StatusCode int
@@ -62,26 +78,26 @@ func (w *statusResponseWriter) WriteHeader(statusCode int) {
w.Inner.WriteHeader(statusCode)
}
-func metricsMiddleware(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- srw := statusResponseWriter{Inner: w}
- next.ServeHTTP(&srw, r)
- status := strconv.Itoa(srw.StatusCode)
- metricRequestsTotalCounter.WithLabelValues(status, r.Method).Inc()
- })
-}
-
// StartMainServer starts the main http server listening on addr.
-func StartMainServer(addr string, db *db.Database, fs *db.FileStore) {
+func StartMainServer(addr string, db *db.Database, fs *db.FileStore, rawhost *string) {
+ var host *url.URL
+ if rawhost != nil {
+ var err error
+ host, err = url.Parse(*rawhost)
+ if err != nil {
+ log.Fatalln(errors.Wrap(err, "could not parse host flag"))
+ }
+ }
rl := rushlink{
- db: db,
- fs: fs,
+ db: db,
+ fs: fs,
+ host: host,
}
// Initialize Gorilla router
router := mux.NewRouter()
- router.Use(recoveryMiddleware)
- router.Use(metricsMiddleware)
+ router.Use(rl.recoveryMiddleware)
+ router.Use(rl.metricsMiddleware)
router.HandleFunc("/uploads/{id:[A-Za-z0-9-_]+}/{filename:.+}", rl.uploadFileGetHandler).Methods("GET", "HEAD")
router.HandleFunc("/", rl.indexGetHandler).Methods("GET", "HEAD")
router.HandleFunc("/", rl.newPasteHandler).Methods("POST")
diff --git a/views.go b/views.go
index 2c2a305..be7f9b5 100644
--- a/views.go
+++ b/views.go
@@ -76,7 +76,22 @@ func parseFail(tmplName string, err error) {
panic(errors.Wrapf(err, "parsing of %v failed", tmplName))
}
-func render(w http.ResponseWriter, r *http.Request, tmplName string, data map[string]interface{}) {
+func mapExtend(m map[string]interface{}, key string, value interface{}) {
+ if m[key] != nil {
+ return
+ }
+ m[key] = value
+}
+
+func (rl *rushlink) resolveHost(r *http.Request) string {
+ rlHost := rl.Host()
+ if rlHost != nil {
+ return rlHost.String()
+ }
+ return r.Host
+}
+
+func (rl *rushlink) render(w http.ResponseWriter, r *http.Request, tmplName string, data map[string]interface{}) {
contentType, err := resolveResponseContentType(r, []string{"text/plain", "text/html"})
if err != nil {
w.WriteHeader(http.StatusNotAcceptable)
@@ -84,7 +99,8 @@ func render(w http.ResponseWriter, r *http.Request, tmplName string, data map[st
}
// Add the request to the template data
- data["Request"] = r
+ mapExtend(data, "Host", rl.resolveHost(r))
+ mapExtend(data, "Request", r)
switch contentType {
case "text/plain":
@@ -117,10 +133,9 @@ func render(w http.ResponseWriter, r *http.Request, tmplName string, data map[st
}
w.WriteHeader(http.StatusOK)
- if r.Method == "HEAD" {
- return
+ if r.Method != "HEAD" {
+ err = tmpl.Execute(w, data)
}
- err = tmpl.Execute(w, data)
default:
// Fall back to plain text without template
w.WriteHeader(http.StatusNotAcceptable)
@@ -132,14 +147,14 @@ func render(w http.ResponseWriter, r *http.Request, tmplName string, data map[st
}
}
-func renderError(w http.ResponseWriter, r *http.Request, status int, msg string) {
+func (rl *rushlink) renderError(w http.ResponseWriter, r *http.Request, status int, msg string) {
w.WriteHeader(status)
- render(w, r, "error", map[string]interface{}{"Message": msg})
+ rl.render(w, r, "error", map[string]interface{}{"Message": msg})
}
-func renderInternalServerError(w http.ResponseWriter, r *http.Request, err interface{}) {
+func (rl *rushlink) renderInternalServerError(w http.ResponseWriter, r *http.Request, err interface{}) {
msg := fmt.Sprintf("internal server error: %v", err)
- renderError(w, r, http.StatusInternalServerError, msg)
+ rl.renderError(w, r, http.StatusInternalServerError, msg)
}
// Try to resolve the preferred content-type for the response to this request.