Put existing pages in templates

This commit is contained in:
Daan Sprenkels 2019-09-15 22:54:07 +02:00
parent 372d7c0487
commit 60fd92c956
7 changed files with 69 additions and 52 deletions

View File

@ -5,6 +5,10 @@
<title>{{block "title" .}}rushlink{{end}}</title>{{block "head-append" .}}{{end}} <title>{{block "title" .}}rushlink{{end}}</title>{{block "head-append" .}}{{end}}
</head> </head>
<body> <body>
{{block "body" .}}{{end}} {{block "body" .}}
<pre>
{{call .Pre}}
</pre>
{{end}}
</body> </body>
</html> </html>

View File

@ -1,8 +1,3 @@
{{define "title"}} {{define "title"}}
Error - rushlink Error - rushlink
{{end}}
{{define "body"}}
<pre>
{{.Message}}
</pre>
{{end}} {{end}}

View File

@ -0,0 +1,3 @@
{{define "title"}}
'{{.Paste.Key}}' meta info - rushlink
{{end}}

View File

@ -0,0 +1,5 @@
key: {{.Paste.Key}}
type: {{.Paste.Type}}
state: {{.Paste.State}}
created: {{.Paste.TimeCreated}}
owner: {{if .IsOwner}}yes{{else}}no{{end}}

View File

@ -1,7 +1,6 @@
package handlers package handlers
import ( import (
"bytes"
"crypto/rand" "crypto/rand"
"crypto/subtle" "crypto/subtle"
"encoding/base64" "encoding/base64"
@ -28,7 +27,7 @@ type StoredPaste struct {
Type PasteType Type PasteType
State PasteState State PasteState
Content []byte Content []byte
Key []byte Key string
OwnerToken [16]byte OwnerToken [16]byte
TimeCreated time.Time TimeCreated time.Time
} }
@ -46,31 +45,31 @@ const (
const CookieOwnerToken = "owner_token" const CookieOwnerToken = "owner_token"
// These keys are designated reserved, and will not be randomly chosen // 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 // Base64 encoding and decoding
var base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" var base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
var base64Encoder = base64.RawURLEncoding.WithPadding(base64.NoPadding) var base64Encoder = base64.RawURLEncoding.WithPadding(base64.NoPadding)
func (t PasteType) String() (string, error) { func (t PasteType) String() string {
switch t { switch t {
case TypePaste: case TypePaste:
return "paste", nil return "paste"
case TypeRedirect: case TypeRedirect:
return "redirect", nil return "redirect"
default: default:
return "", fmt.Errorf("invalid PasteType (%v)", t) return "invalid"
} }
} }
func (t PasteState) String() (string, error) { func (t PasteState) String() string {
switch t { switch t {
case StatePresent: case StatePresent:
return "present", nil return "present"
case StateDeleted: case StateDeleted:
return "deleted", nil return "deleted"
default: 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 { if showMeta {
typeString, err := storedPaste.Type.String() isOwner := false
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"
ownerToken, ok := getOwnerTokenFromRequest(r) ownerToken, ok := getOwnerTokenFromRequest(r)
if ok && subtle.ConstantTimeCompare(ownerToken[:], storedPaste.OwnerToken[:]) == 1 { if ok && subtle.ConstantTimeCompare(ownerToken[:], storedPaste.OwnerToken[:]) == 1 {
isOwner = "yes" isOwner = true
} }
// TODO(dsprenkels) This should be put into a template data := map[string]interface{}{
w.WriteHeader(http.StatusOK) "Paste": storedPaste,
fmt.Fprintf(w, "key: %v\n", string(storedPaste.Key)) "IsOwner": isOwner,
fmt.Fprintf(w, "type: %v\n", typeString) }
fmt.Fprintf(w, "state: %v\n", stateString) Render(w, r, "pasteMeta", data)
fmt.Fprintf(w, "created: %v\n", storedPaste.TimeCreated.String())
fmt.Fprintf(w, "are you the owner: %v\n", isOwner)
return 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), // 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. // where N is the amount of keys stored in the url-shorten database.
epoch := 0 epoch := 0
var urlKey []byte var urlKey string
for { for {
var err error var err error
urlKey, err = generateURLKey(epoch) 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") return nil, errors.Wrap(err, "url-key generation failed")
} }
found := shortenBucket.Get(urlKey) found := shortenBucket.Get([]byte(urlKey))
if found == nil { if found == nil {
break break
} }
isReserved := false isReserved := false
for _, reservedKey := range ReservedPasteKeys { for _, reservedKey := range ReservedPasteKeys {
if bytes.HasPrefix(urlKey, reservedKey) { if strings.HasPrefix(urlKey, reservedKey) {
isReserved = true isReserved = true
break break
} }
@ -305,17 +290,17 @@ func shortenURL(tx *bolt.Tx, userURL *url.URL, ownerKey [16]byte) (*StoredPaste,
if err != nil { if err != nil {
return nil, errors.Wrap(err, "encoding for database failed") 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 nil, errors.Wrap(err, "database transaction failed")
} }
return &storedPaste, nil return &storedPaste, nil
} }
func generateURLKey(epoch int) ([]byte, error) { func generateURLKey(epoch int) (string, error) {
urlKey := make([]byte, 4+epoch) urlKey := make([]byte, 4+epoch)
_, err := rand.Read(urlKey) _, err := rand.Read(urlKey)
if err != nil { if err != nil {
return nil, err return "", err
} }
// Put all the values in the range 0..64 for easier base64-encoding // Put all the values in the range 0..64 for easier base64-encoding
for i := 0; i < len(urlKey); i++ { for i := 0; i < len(urlKey); i++ {
@ -343,7 +328,7 @@ func generateURLKey(epoch int) ([]byte, error) {
for i := range urlKey { for i := range urlKey {
urlKey[i] = base64Alphabet[urlKey[i]] urlKey[i] = base64Alphabet[urlKey[i]]
} }
return urlKey, nil return string(urlKey), nil
} }
func generateOwnerToken() ([16]byte, error) { func generateOwnerToken() ([16]byte, error) {

View File

@ -5,6 +5,7 @@ package handlers
//go:generate go-bindata -pkg $GOPACKAGE -prefix ../assets ../assets/... //go:generate go-bindata -pkg $GOPACKAGE -prefix ../assets ../assets/...
import ( import (
"bytes"
"fmt" "fmt"
html "html/template" html "html/template"
"io" "io"
@ -72,7 +73,7 @@ func parseFail(tmplName string, err error) {
panic(err) 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"}) contentType, err := resolveResponseContentType(r, []string{"text/plain", "text/html"})
if err != nil { if err != nil {
w.WriteHeader(http.StatusNotAcceptable) 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) err = fmt.Errorf("'%v' not in HTMLTemplates", tmplName)
break break
} }
// Construct a (lazy) plain-text view for inclusion in <pre>
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) err = tmpl.Execute(w, data)
default: default:
// Fall back to plain text without template
w.WriteHeader(http.StatusNotAcceptable) w.WriteHeader(http.StatusNotAcceptable)
io.WriteString(w, "could not resolve an acceptable content-type\n") 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) { func RenderError(w http.ResponseWriter, r *http.Request, status int, msg string) {
w.WriteHeader(status) 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{}) { 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) 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. // 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 // This is done by reading from the `types` argument. If one of them matches

View File

@ -23,20 +23,20 @@ func StartMetricsServer() {
var ( var (
_ = promauto.NewGaugeFunc(prometheus.GaugeOpts{ _ = promauto.NewGaugeFunc(prometheus.GaugeOpts{
Namespace: "rushlink", Namespace: "rushlink",
Subsystem: "shorten", Subsystem: "pastes",
Name: "urls_total", Name: "urls_total",
Help: "The current amount of shortened urls in the database.", Help: "The current amount of pastes in the database.",
}, func() float64 { }, func() float64 {
var metric float64 var metric float64
if err := db.DB.View(func(tx *bolt.Tx) error { if err := db.DB.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte("shorten")) bucket := tx.Bucket([]byte("pastes"))
if bucket == nil { 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) metric = float64(bucket.Stats().KeyN)
return nil return nil
}); err != 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 0
} }
return metric return metric