From 60fd92c95680be1737bb232fffdf3730d82ee791 Mon Sep 17 00:00:00 2001 From: Daan Sprenkels Date: Sun, 15 Sep 2019 22:54:07 +0200 Subject: [PATCH] Put existing pages in templates --- assets/templates/html/base.html.tmpl | 6 ++- assets/templates/html/error.html.tmpl | 5 -- assets/templates/html/pasteMeta.html.tmpl | 3 ++ assets/templates/txt/pasteMeta.txt.tmpl | 5 ++ handlers/handlers.go | 63 +++++++++-------------- handlers/views.go | 29 ++++++++++- metrics/metrics.go | 10 ++-- 7 files changed, 69 insertions(+), 52 deletions(-) create mode 100644 assets/templates/html/pasteMeta.html.tmpl create mode 100644 assets/templates/txt/pasteMeta.txt.tmpl diff --git a/assets/templates/html/base.html.tmpl b/assets/templates/html/base.html.tmpl index 71e2614..0911945 100644 --- a/assets/templates/html/base.html.tmpl +++ b/assets/templates/html/base.html.tmpl @@ -5,6 +5,10 @@ {{block "title" .}}rushlink{{end}}{{block "head-append" .}}{{end}} - {{block "body" .}}{{end}} + {{block "body" .}} +
+{{call .Pre}}
+        
+ {{end}} \ No newline at end of file diff --git a/assets/templates/html/error.html.tmpl b/assets/templates/html/error.html.tmpl index ec247f5..25edb4f 100644 --- a/assets/templates/html/error.html.tmpl +++ b/assets/templates/html/error.html.tmpl @@ -1,8 +1,3 @@ {{define "title"}} Error - rushlink -{{end}} -{{define "body"}} -
-{{.Message}}
-
{{end}} \ No newline at end of file diff --git a/assets/templates/html/pasteMeta.html.tmpl b/assets/templates/html/pasteMeta.html.tmpl new file mode 100644 index 0000000..d0be461 --- /dev/null +++ b/assets/templates/html/pasteMeta.html.tmpl @@ -0,0 +1,3 @@ +{{define "title"}} +'{{.Paste.Key}}' meta info - rushlink +{{end}} \ No newline at end of file diff --git a/assets/templates/txt/pasteMeta.txt.tmpl b/assets/templates/txt/pasteMeta.txt.tmpl new file mode 100644 index 0000000..5ca0503 --- /dev/null +++ b/assets/templates/txt/pasteMeta.txt.tmpl @@ -0,0 +1,5 @@ +key: {{.Paste.Key}} +type: {{.Paste.Type}} +state: {{.Paste.State}} +created: {{.Paste.TimeCreated}} +owner: {{if .IsOwner}}yes{{else}}no{{end}} diff --git a/handlers/handlers.go b/handlers/handlers.go index 4f5b97e..58cb638 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -1,7 +1,6 @@ package handlers import ( - "bytes" "crypto/rand" "crypto/subtle" "encoding/base64" @@ -28,7 +27,7 @@ type StoredPaste struct { Type PasteType State PasteState Content []byte - Key []byte + Key string OwnerToken [16]byte TimeCreated time.Time } @@ -46,31 +45,31 @@ const ( const CookieOwnerToken = "owner_token" // These keys are designated reserved, and will not be randomly chosen -var ReservedPasteKeys [][]byte = [][]byte{[]byte("xd42"), []byte("example")} +var ReservedPasteKeys = []string{"xd42", "example"} // Base64 encoding and decoding var base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" var base64Encoder = base64.RawURLEncoding.WithPadding(base64.NoPadding) -func (t PasteType) String() (string, error) { +func (t PasteType) String() string { switch t { case TypePaste: - return "paste", nil + return "paste" case TypeRedirect: - return "redirect", nil + return "redirect" default: - return "", fmt.Errorf("invalid PasteType (%v)", t) + return "invalid" } } -func (t PasteState) String() (string, error) { +func (t PasteState) String() string { switch t { case StatePresent: - return "present", nil + return "present" case StateDeleted: - return "deleted", nil + return "deleted" default: - return "", fmt.Errorf("invalid PasteState (%v)", t) + return "invalid" } } @@ -134,31 +133,17 @@ func pasteGetHandlerInner(w http.ResponseWriter, r *http.Request, noRedirect, sh } if showMeta { - typeString, err := storedPaste.Type.String() - if err != nil { - log.Printf("error: %v\n", err) - RenderInternalServerError(w, r, err) - return - } - stateString, err := storedPaste.State.String() - if err != nil { - log.Printf("error: %v\n", err) - RenderInternalServerError(w, r, err) - return - } - isOwner := "no" + isOwner := false ownerToken, ok := getOwnerTokenFromRequest(r) if ok && subtle.ConstantTimeCompare(ownerToken[:], storedPaste.OwnerToken[:]) == 1 { - isOwner = "yes" + isOwner = true } - // TODO(dsprenkels) This should be put into a template - w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, "key: %v\n", string(storedPaste.Key)) - fmt.Fprintf(w, "type: %v\n", typeString) - fmt.Fprintf(w, "state: %v\n", stateString) - fmt.Fprintf(w, "created: %v\n", storedPaste.TimeCreated.String()) - fmt.Fprintf(w, "are you the owner: %v\n", isOwner) + data := map[string]interface{}{ + "Paste": storedPaste, + "IsOwner": isOwner, + } + Render(w, r, "pasteMeta", data) return } @@ -265,7 +250,7 @@ func shortenURL(tx *bolt.Tx, userURL *url.URL, ownerKey [16]byte) (*StoredPaste, // Generate a key until it is not in the database, this occurs in O(log N), // where N is the amount of keys stored in the url-shorten database. epoch := 0 - var urlKey []byte + var urlKey string for { var err error urlKey, err = generateURLKey(epoch) @@ -273,14 +258,14 @@ func shortenURL(tx *bolt.Tx, userURL *url.URL, ownerKey [16]byte) (*StoredPaste, return nil, errors.Wrap(err, "url-key generation failed") } - found := shortenBucket.Get(urlKey) + found := shortenBucket.Get([]byte(urlKey)) if found == nil { break } isReserved := false for _, reservedKey := range ReservedPasteKeys { - if bytes.HasPrefix(urlKey, reservedKey) { + if strings.HasPrefix(urlKey, reservedKey) { isReserved = true break } @@ -305,17 +290,17 @@ func shortenURL(tx *bolt.Tx, userURL *url.URL, ownerKey [16]byte) (*StoredPaste, if err != nil { return nil, errors.Wrap(err, "encoding for database failed") } - if err := shortenBucket.Put(urlKey, storedBytes); err != nil { + if err := shortenBucket.Put([]byte(urlKey), storedBytes); err != nil { return nil, errors.Wrap(err, "database transaction failed") } return &storedPaste, nil } -func generateURLKey(epoch int) ([]byte, error) { +func generateURLKey(epoch int) (string, error) { urlKey := make([]byte, 4+epoch) _, err := rand.Read(urlKey) if err != nil { - return nil, err + return "", err } // Put all the values in the range 0..64 for easier base64-encoding for i := 0; i < len(urlKey); i++ { @@ -343,7 +328,7 @@ func generateURLKey(epoch int) ([]byte, error) { for i := range urlKey { urlKey[i] = base64Alphabet[urlKey[i]] } - return urlKey, nil + return string(urlKey), nil } func generateOwnerToken() ([16]byte, error) { diff --git a/handlers/views.go b/handlers/views.go index 6fd9079..3ce84ca 100644 --- a/handlers/views.go +++ b/handlers/views.go @@ -5,6 +5,7 @@ package handlers //go:generate go-bindata -pkg $GOPACKAGE -prefix ../assets ../assets/... import ( + "bytes" "fmt" html "html/template" "io" @@ -72,7 +73,7 @@ func parseFail(tmplName string, err error) { panic(err) } -func Render(w http.ResponseWriter, r *http.Request, tmplName string, data interface{}) { +func 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) @@ -95,8 +96,23 @@ func Render(w http.ResponseWriter, r *http.Request, tmplName string, data interf err = fmt.Errorf("'%v' not in HTMLTemplates", tmplName) break } + + // Construct a (lazy) plain-text view for inclusion in
+		pre := func() string {
+			tmpl := TextTemplates[tmplName]
+			if tmpl == nil {
+				panic(fmt.Errorf("'%v' not in TextTemplates", tmplName))
+			}
+			var buf bytes.Buffer
+			if err := tmpl.Execute(&buf, data); err != nil {
+				panic(err)
+			}
+			return buf.String()
+		}
+		data = mergeData(map[string]interface{}{"Pre": pre}, data)
 		err = tmpl.Execute(w, data)
 	default:
+		// Fall back to plain text without template
 		w.WriteHeader(http.StatusNotAcceptable)
 		io.WriteString(w, "could not resolve an acceptable content-type\n")
 	}
@@ -108,7 +124,7 @@ func Render(w http.ResponseWriter, r *http.Request, tmplName string, data interf
 
 func RenderError(w http.ResponseWriter, r *http.Request, status int, msg string) {
 	w.WriteHeader(status)
-	Render(w, r, "error", struct{ Message string }{msg})
+	Render(w, r, "error", map[string]interface{}{"Message": msg})
 }
 
 func RenderInternalServerError(w http.ResponseWriter, r *http.Request, err interface{}) {
@@ -116,6 +132,15 @@ func RenderInternalServerError(w http.ResponseWriter, r *http.Request, err inter
 	RenderError(w, r, http.StatusInternalServerError, msg)
 }
 
+// Merge the second data map into the first one, overwriting any key that is
+// already present.
+func mergeData(into, from map[string]interface{}) map[string]interface{} {
+	for k, v := range from {
+		into[k] = v
+	}
+	return into
+}
+
 // Try to resolve the preferred content-type for the response to this request.
 //
 // This is done by reading from the `types` argument. If one of them matches
diff --git a/metrics/metrics.go b/metrics/metrics.go
index 7c5c7d9..2b9ab8b 100644
--- a/metrics/metrics.go
+++ b/metrics/metrics.go
@@ -23,20 +23,20 @@ func StartMetricsServer() {
 	var (
 		_ = promauto.NewGaugeFunc(prometheus.GaugeOpts{
 			Namespace: "rushlink",
-			Subsystem: "shorten",
+			Subsystem: "pastes",
 			Name:      "urls_total",
-			Help:      "The current amount of shortened urls in the database.",
+			Help:      "The current amount of pastes in the database.",
 		}, func() float64 {
 			var metric float64
 			if err := db.DB.View(func(tx *bolt.Tx) error {
-				bucket := tx.Bucket([]byte("shorten"))
+				bucket := tx.Bucket([]byte("pastes"))
 				if bucket == nil {
-					return errors.New("bucket 'shorten' could not be found")
+					return errors.New("bucket 'pastes' could not be found")
 				}
 				metric = float64(bucket.Stats().KeyN)
 				return nil
 			}); err != nil {
-				log.Printf("error: %v", errors.Wrap(err, "fetching shorten_urls_total metric"))
+				log.Printf("error: %v", errors.Wrap(err, "fetching pastes_total metric"))
 				return 0
 			}
 			return metric