Validate key format before retrieving from database

Fixes #67
This commit is contained in:
Daan Sprenkels 2020-07-27 16:57:09 +02:00
parent 6d3e8028cb
commit 26be9b5104
2 changed files with 71 additions and 9 deletions

View File

@ -98,7 +98,7 @@ func (rl *rushlink) viewPasteHandlerFlags(w http.ResponseWriter, r *http.Request
key := vars["key"]
var p *db.Paste
var fu *db.FileUpload
if err := rl.db.Bolt.View(func(tx *bolt.Tx) error {
err := rl.db.Bolt.View(func(tx *bolt.Tx) error {
var err error
p, err = db.GetPaste(tx, key)
if err != nil {
@ -113,12 +113,13 @@ func (rl *rushlink) viewPasteHandlerFlags(w http.ResponseWriter, r *http.Request
}
}
return nil
}); err != nil {
panic(err)
}
if p == nil {
rl.renderError(w, r, http.StatusNotFound, "url key not found in the database")
})
if err != nil {
status := db.ErrHTTPStatusCode(err)
if status == http.StatusInternalServerError {
panic(err)
}
rl.renderError(w, r, status, err.Error())
return
}

View File

@ -4,6 +4,7 @@ import (
"crypto/rand"
"encoding/base64"
"encoding/hex"
"net/http"
"net/url"
"strings"
"time"
@ -53,6 +54,35 @@ const (
PasteStateDeleted
)
// minKeyLen specifies the mimimum length of a paste key.
const minKeyLen = 4
var (
// ErrKeyInvalidChar occurs when a key contains an invalid character.
ErrKeyInvalidChar = errors.New("invalid character in key")
// ErrKeyInvalidLength occurs when a key embeds a length that is incorrect.
ErrKeyInvalidLength = errors.New("key length encoding is incorrect")
// ErrPasteDoesNotExist occurs when a key does not exist in the database.
ErrPasteDoesNotExist = errors.New("url key not found in the database")
)
// ErrHTTPStatusCode returns the HTTP status code that should correspond to
// the provided error.
// server error, or false if it is not.
func ErrHTTPStatusCode(err error) int {
switch err {
case nil:
return 0
case ErrKeyInvalidChar:
return http.StatusNotFound
case ErrKeyInvalidLength:
return http.StatusNotFound
case ErrPasteDoesNotExist:
return http.StatusNotFound
}
return http.StatusInternalServerError
}
// Base64 encoding and decoding
var base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
var base64Encoder = base64.RawURLEncoding.WithPadding(base64.NoPadding)
@ -87,13 +117,45 @@ func (t PasteState) String() string {
// GetPaste retrieves a paste from the database.
func GetPaste(tx *bolt.Tx, key string) (*Paste, error) {
if err := ValidatePasteKey(key); err != nil {
return nil, err
}
return GetPasteNoValidate(tx, key)
}
// ValidatePasteKey validates the format of the key that has
func ValidatePasteKey(key string) error {
internalLen := minKeyLen
countingOnes := true
for _, ch := range key {
limb := strings.IndexRune(base64Alphabet, ch)
if limb == -1 {
return ErrKeyInvalidChar
}
for i := 5; i >= 0 && countingOnes; i-- {
if (limb>>uint(i))&0x1 == 0 {
countingOnes = false
break
}
internalLen++
}
}
if internalLen != len(key) {
return ErrKeyInvalidLength
}
return nil
}
// GetPasteNoValidate retrieves a paste from the database without validating
// the key format first.
func GetPasteNoValidate(tx *bolt.Tx, key string) (*Paste, error) {
pastesBucket := tx.Bucket([]byte(BucketPastes))
if pastesBucket == nil {
return nil, errors.Errorf("bucket %v does not exist", BucketPastes)
}
storedBytes := pastesBucket.Get([]byte(key))
if storedBytes == nil {
return nil, nil
return nil, ErrPasteDoesNotExist
}
return decodePaste(storedBytes)
}
@ -214,7 +276,6 @@ func GeneratePasteKey(tx *bolt.Tx, minimumEntropy int) (string, error) {
// is used to ensure that a new key has at least some amount of guessing
// entropy.
func generatePasteKeyInner(epoch, entropy int) (string, error) {
minKeyLen := 4
entropyEpoch := entropy
entropyEpoch -= minKeyLen * 6 // First 4 characters provide 24 bits.
entropyEpoch++ // One bit less because of '0' bit.