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}}
</head>
<body>
{{block "body" .}}{{end}}
{{block "body" .}}
<pre>
{{call .Pre}}
</pre>
{{end}}
</body>
</html>

View File

@ -1,8 +1,3 @@
{{define "title"}}
Error - rushlink
{{end}}
{{define "body"}}
<pre>
{{.Message}}
</pre>
{{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
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) {

View File

@ -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>
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

View File

@ -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