diff --git a/assets/templates/html/pasteMeta.html.tmpl b/assets/templates/html/pasteMeta.html.tmpl index 15c113d..5c50084 100644 --- a/assets/templates/html/pasteMeta.html.tmpl +++ b/assets/templates/html/pasteMeta.html.tmpl @@ -6,8 +6,8 @@
 {{.Host}}/{{.Paste.Key}}{{.FileExt}}
 ---
-{{if and (ne .Paste.State.String "deleted") .CanDelete.Bool}}
-with delete token: {{.Host}}/{{.Paste.Key}}{{.FileExt}}?deleteToken={{.Request.URL.Query.Get "deleteToken"}}
+{{if and (ne .Paste.State.String "deleted") .CanDeleteBool}}
+with delete token: {{.Host}}/{{.Paste.Key}}{{.FileExt}}?deleteToken={{.Paste.DeleteToken}}
 {{else -}}
 with delete token: <unknown>
 {{end -}}
@@ -18,18 +18,11 @@ created: unknown
 {{else -}}
 created: {{.Paste.TimeCreated}}
 {{end -}}
-delete token: {{.CanDelete.String}}
+delete token: {{.CanDelete}}
 
-{{if and (ne .Paste.State.String "deleted") .CanDelete.Bool}}
+{{if and (ne .Paste.State.String "deleted") .CanDeleteBool}}
 ```
-# To delete this {{.Paste.Type}}, execute:
-{{- /*
-    We have the option here to take the deleteToken from the user request or
-    from .Paste.  Both are equivalent as long as .CanDelete is correct.  We
-    use the .Request value, because leaking the deleteToken would be a more
-    dramatic vulnerability.
-*/}}
-curl --request "DELETE" "{{.Host}}/{{.Paste.Key}}{{.FileExt}}?deleteToken={{.Request.URL.Query.Get "deleteToken"}}"
+curl --request "DELETE" "{{.Host}}/{{.Paste.Key}}{{.FileExt}}?deleteToken={{.Paste.DeleteToken}}"
 ```
 
{{end}} diff --git a/assets/templates/txt/pasteMeta.txt.tmpl b/assets/templates/txt/pasteMeta.txt.tmpl index 04cb8dc..c2c5ee5 100644 --- a/assets/templates/txt/pasteMeta.txt.tmpl +++ b/assets/templates/txt/pasteMeta.txt.tmpl @@ -1,6 +1,6 @@ {{.Host}}/{{.Paste.Key}}{{.FileExt}} --- -{{if and (ne .Paste.State.String "deleted") .CanDelete.Bool}} +{{if and (ne .Paste.State.String "deleted") .CanDeleteBool}} with delete token: {{.Host}}/{{.Paste.Key}}{{.FileExt}}?deleteToken={{.Request.URL.Query.Get "deleteToken"}} {{else -}} with delete token: @@ -11,17 +11,11 @@ created: unknown {{else -}} created: {{.Paste.TimeCreated}} {{end -}} -delete token: {{.CanDelete.String}} +delete token: {{.CanDelete}} -{{if and (ne .Paste.State.String "deleted") .CanDelete.Bool}} +{{if and (ne .Paste.State.String "deleted") .CanDeleteBool}} ``` # To delete this {{.Paste.Type}}, execute: -{{- /* - We have the option here to take the deleteToken from the user request or - from .Paste. Both are equivalent as long as .CanDelete is correct. We - use the .Request value, because leaking the deleteToken would be a more - dramatic vulnerability. -*/ -}} -curl --request "DELETE" "{{.Host}}/{{.Paste.Key}}{{.FileExt}}?deleteToken={{.Request.URL.Query.Get "deleteToken"}}" +curl --request "DELETE" "{{.Host}}/{{.Paste.Key}}{{.FileExt}}?deleteToken={{.Paste.DeleteToken}}" ``` {{end}} diff --git a/handlers.go b/handlers.go index c491f34..77d1ced 100644 --- a/handlers.go +++ b/handlers.go @@ -28,6 +28,31 @@ const ( const cookieDeleteToken = "owner_token" +type canDelete uint + +const ( + canDeleteUndef canDelete = iota + canDeleteYes + canDeleteNo +) + +func (cd *canDelete) Bool() bool { + return *cd == canDeleteYes +} + +func (cd *canDelete) String() string { + switch *cd { + case canDeleteUndef: + return "undefined" + case canDeleteYes: + return "correct" + case canDeleteNo: + return "invalid" + default: + panic("unreachable") + } +} + func (rl *rushlink) staticGetHandler(w http.ResponseWriter, r *http.Request) { rl.renderStatic(w, r, mux.Vars(r)["path"]) } @@ -79,24 +104,22 @@ func (rl *rushlink) uploadFileGetHandler(w http.ResponseWriter, r *http.Request) } func (rl *rushlink) viewPasteHandler(w http.ResponseWriter, r *http.Request) { - rl.viewPasteHandlerInner(w, r, 0) + rl.viewPasteHandlerFlags(w, r, 0) } func (rl *rushlink) viewPasteHandlerNoRedirect(w http.ResponseWriter, r *http.Request) { - rl.viewPasteHandlerInner(w, r, viewNoRedirect) + rl.viewPasteHandlerFlags(w, r, viewNoRedirect) } func (rl *rushlink) viewPasteHandlerMeta(w http.ResponseWriter, r *http.Request) { - rl.viewPasteHandlerInner(w, r, viewShowMeta) + rl.viewPasteHandlerFlags(w, r, viewShowMeta) } -func (rl *rushlink) viewPasteHandlerInner(w http.ResponseWriter, r *http.Request, flags viewPaste) { +func (rl *rushlink) viewPasteHandlerFlags(w http.ResponseWriter, r *http.Request, flags viewPaste) { vars := mux.Vars(r) key := vars["key"] var p *db.Paste - var fuID *uuid.UUID var fu *db.FileUpload - var fileExt string if err := rl.db.Bolt.View(func(tx *bolt.Tx) error { var err error p, err = db.GetPaste(tx, key) @@ -106,12 +129,10 @@ func (rl *rushlink) viewPasteHandlerInner(w http.ResponseWriter, r *http.Request if p != nil && p.Type == db.PasteTypeFileUpload { var id uuid.UUID copy(id[:], p.Content) - fuID = &id fu, err = db.GetFileUpload(tx, id) if err != nil { return err } - fileExt = fu.Ext() } return nil }); err != nil { @@ -123,30 +144,12 @@ func (rl *rushlink) viewPasteHandlerInner(w http.ResponseWriter, r *http.Request return } - if flags&viewShowMeta != 0 { - canDelete := struct { - Bool bool - String string - }{Bool: false} - deleteToken := getDeleteTokenFromRequest(r) - if deleteToken == "" { - canDelete.String = "undefined" - } else { - if subtle.ConstantTimeCompare([]byte(deleteToken), []byte(p.DeleteToken)) == 1 { - canDelete.Bool = true - canDelete.String = "correct" - } else { - canDelete.String = "invalid" - } - } + rl.viewPasteHandlerInner(w, r, flags, p, fu) +} - data := map[string]interface{}{ - "Paste": p, - "FileExt": fileExt, - "CanDelete": canDelete, - } - rl.render(w, r, "pasteMeta", data) - return +func (rl *rushlink) viewPasteHandlerInner(w http.ResponseWriter, r *http.Request, flags viewPaste, p *db.Paste, fu *db.FileUpload) { + if flags&viewShowMeta != 0 { + rl.viewPasteHandlerInnerMeta(w, r, p, fu) } switch p.State { @@ -155,7 +158,7 @@ func (rl *rushlink) viewPasteHandlerInner(w http.ResponseWriter, r *http.Request switch p.Type { case db.PasteTypeFileUpload: if fu == nil { - panic(fmt.Sprintf("file for id %v does not exist in database\n", fuID)) + panic(fmt.Sprintf("file for id %v does not exist in database\n", string(p.Content))) } location = fu.URL().String() break @@ -176,6 +179,46 @@ func (rl *rushlink) viewPasteHandlerInner(w http.ResponseWriter, r *http.Request } } +func (rl *rushlink) viewPasteHandlerInnerMeta(w http.ResponseWriter, r *http.Request, p *db.Paste, fu *db.FileUpload) { + var cd canDelete + deleteToken := getDeleteTokenFromRequest(r) + if deleteToken != "" { + if subtle.ConstantTimeCompare([]byte(deleteToken), []byte(p.DeleteToken)) == 1 { + cd = canDeleteYes + } else { + cd = canDeleteNo + } + } + + var fileExt string + if fu != nil { + fileExt = fu.Ext() + } + data := map[string]interface{}{ + "Paste": p, + "FileExt": fileExt, + "CanDelete": cd, + "CanDeleteBool": cd.Bool(), + } + rl.render(w, r, "pasteMeta", data) + return +} + +func (rl *rushlink) viewCreateSuccess(w http.ResponseWriter, r *http.Request, p *db.Paste, fu *db.FileUpload) { + var fileExt string + if fu != nil { + fileExt = fu.Ext() + } + data := map[string]interface{}{ + "Paste": p, + "FileExt": fileExt, + "CanDelete": canDeleteYes, + "CanDeleteBool": true, + } + rl.render(w, r, "pasteMeta", data) + return +} + func (rl *rushlink) newPasteHandler(w http.ResponseWriter, r *http.Request) { file, fileHeader, err := r.FormFile("file") if err == nil { @@ -216,7 +259,7 @@ func (rl *rushlink) newFileUploadPasteHandler(w http.ResponseWriter, r *http.Req }); err != nil { panic(err) } - rl.renderCreateSuccess(w, r, paste, fu) + rl.viewCreateSuccess(w, r, paste, fu) } func (rl *rushlink) newPasteHandlerURLEncoded(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { @@ -256,7 +299,7 @@ func (rl *rushlink) newRedirectPasteHandler(w http.ResponseWriter, r *http.Reque }); err != nil { panic(err) } - rl.renderCreateSuccess(w, r, paste, nil) + rl.viewCreateSuccess(w, r, paste, nil) } // Delete a URL from the database @@ -297,7 +340,7 @@ func (rl *rushlink) deletePasteHandler(w http.ResponseWriter, r *http.Request) { rl.renderError(w, r, errorCode, fmt.Sprintf("error: %v\n", err)) return } - rl.renderCreateSuccess(w, r, paste, nil) + rl.viewCreateSuccess(w, r, paste, nil) } // Add a new fileUpload redirect to the database diff --git a/views.go b/views.go index 10d5f6a..88cdfee 100644 --- a/views.go +++ b/views.go @@ -9,7 +9,6 @@ import ( "io" "log" "net/http" - "net/url" "path/filepath" "regexp" "runtime/debug" @@ -19,8 +18,6 @@ import ( text "text/template" "time" - "gitea.hashru.nl/dsprenkels/rushlink/internal/db" - "github.com/pkg/errors" ) @@ -168,22 +165,6 @@ func (rl *rushlink) renderInternalServerError(w http.ResponseWriter, r *http.Req rl.renderError(w, r, http.StatusInternalServerError, msg) } -func (rl *rushlink) renderCreateSuccess(w http.ResponseWriter, r *http.Request, paste *db.Paste, fu *db.FileUpload) { - if paste == nil { - panic("paste should not be nil") - } - var fileExt string - if fu != nil { - fileExt = fu.Ext() - } - var redirectURL url.URL - redirectURL.Path = fmt.Sprintf("/%s%s/meta", paste.Key, fileExt) - queryVals := redirectURL.Query() - queryVals.Add("deleteToken", paste.DeleteToken) - redirectURL.RawQuery = queryVals.Encode() - http.Redirect(w, r, redirectURL.String(), http.StatusFound) -} - // resolveHost constructs the `scheme://host` part of rushlinks public API. // // If the `--host` flag is set, it will return that URL.