forked from electricdusk/rushlink
		
	refactor: Put paste model in separate file
This commit is contained in:
		
							parent
							
								
									f36ed4a4c7
								
							
						
					
					
						commit
						0ecdaafe2f
					
				@ -8,6 +8,9 @@ the command line.
 | 
			
		||||
 | 
			
		||||
## USAGE
 | 
			
		||||
 | 
			
		||||
    # Upload a file
 | 
			
		||||
    curl -F'file=@yourfile.png' <a href="{{Request.Host}}">{{Request.Host}}</a>
 | 
			
		||||
 | 
			
		||||
    # Shorten a URL
 | 
			
		||||
    curl -F'shorten=http://example.com/some/long/url' <a href="{{Request.Host}}">{{Request.Host}}</a>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,9 @@ the command line.
 | 
			
		||||
 | 
			
		||||
## USAGE
 | 
			
		||||
 | 
			
		||||
    # Upload a file
 | 
			
		||||
    curl -F'file=@yourfile.png' {{Request.Host}}
 | 
			
		||||
 | 
			
		||||
    # Shorten a URL
 | 
			
		||||
    curl -F'shorten=http://example.com/some/long/url' {{Request.Host}}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,15 +1,12 @@
 | 
			
		||||
package handlers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/rand"
 | 
			
		||||
	"crypto/subtle"
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/gorilla/mux"
 | 
			
		||||
@ -17,31 +14,6 @@ import (
 | 
			
		||||
	bolt "go.etcd.io/bbolt"
 | 
			
		||||
 | 
			
		||||
	"gitea.hashru.nl/dsprenkels/rushlink/db"
 | 
			
		||||
	"gitea.hashru.nl/dsprenkels/rushlink/gobmarsh"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type pasteType int
 | 
			
		||||
type pasteState int
 | 
			
		||||
 | 
			
		||||
type storedPaste struct {
 | 
			
		||||
	Type        pasteType
 | 
			
		||||
	State       pasteState
 | 
			
		||||
	Content     []byte
 | 
			
		||||
	Key         string
 | 
			
		||||
	DeleteToken string
 | 
			
		||||
	TimeCreated time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	typeUndef    pasteType = 0
 | 
			
		||||
	typePaste              = 1
 | 
			
		||||
	typeRedirect           = 2
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	stateUndef   pasteState = 0
 | 
			
		||||
	statePresent            = 1
 | 
			
		||||
	stateDeleted            = 2
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type viewPaste uint
 | 
			
		||||
@ -106,17 +78,17 @@ func viewPasteHandlerMeta(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
func viewPasteHandlerInner(w http.ResponseWriter, r *http.Request, flags viewPaste) {
 | 
			
		||||
	vars := mux.Vars(r)
 | 
			
		||||
	key := vars["key"]
 | 
			
		||||
	var storedPaste *storedPaste
 | 
			
		||||
	var p *paste
 | 
			
		||||
	if err := db.DB.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		var err error
 | 
			
		||||
		storedPaste, err = getURL(tx, key)
 | 
			
		||||
		p, err = getPaste(tx, key)
 | 
			
		||||
		return err
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		log.Printf("error: %v\n", err)
 | 
			
		||||
		renderInternalServerError(w, r, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if storedPaste == nil {
 | 
			
		||||
	if p == nil {
 | 
			
		||||
		renderError(w, r, http.StatusNotFound, "url key not found in the database")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@ -130,7 +102,7 @@ func viewPasteHandlerInner(w http.ResponseWriter, r *http.Request, flags viewPas
 | 
			
		||||
		if deleteToken == "" {
 | 
			
		||||
			canDelete.String = "undefined"
 | 
			
		||||
		} else {
 | 
			
		||||
			if subtle.ConstantTimeCompare([]byte(deleteToken), []byte(storedPaste.DeleteToken)) == 1 {
 | 
			
		||||
			if subtle.ConstantTimeCompare([]byte(deleteToken), []byte(p.DeleteToken)) == 1 {
 | 
			
		||||
				canDelete.Bool = true
 | 
			
		||||
				canDelete.String = "correct"
 | 
			
		||||
			} else {
 | 
			
		||||
@ -139,31 +111,31 @@ func viewPasteHandlerInner(w http.ResponseWriter, r *http.Request, flags viewPas
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		data := map[string]interface{}{
 | 
			
		||||
			"Paste":     storedPaste,
 | 
			
		||||
			"Paste":     p,
 | 
			
		||||
			"CanDelete": canDelete,
 | 
			
		||||
		}
 | 
			
		||||
		render(w, r, "pasteMeta", data)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch storedPaste.State {
 | 
			
		||||
	switch p.State {
 | 
			
		||||
	case statePresent:
 | 
			
		||||
		if flags&viewNoRedirect == 0 {
 | 
			
		||||
			rawurl := string(storedPaste.Content)
 | 
			
		||||
			rawurl := string(p.Content)
 | 
			
		||||
			urlParse, err := url.Parse(rawurl)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Printf("error: invalid URL ('%v') in database for key '%v': %v\n", rawurl, storedPaste.Key, err)
 | 
			
		||||
				log.Printf("error: invalid URL ('%v') in database for key '%v': %v\n", rawurl, p.Key, err)
 | 
			
		||||
				renderInternalServerError(w, r, "invalid url in database")
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			http.Redirect(w, r, urlParse.String(), http.StatusSeeOther)
 | 
			
		||||
		}
 | 
			
		||||
		w.Write(storedPaste.Content)
 | 
			
		||||
		w.Write(p.Content)
 | 
			
		||||
	case stateDeleted:
 | 
			
		||||
		renderError(w, r, http.StatusGone, "paste has been deleted")
 | 
			
		||||
	default:
 | 
			
		||||
		log.Printf("error: invalid storedPaste.State (%v) for key '%v'\n", storedPaste.State, storedPaste.Key)
 | 
			
		||||
		msg := fmt.Sprintf("internal server error: invalid storedPaste.State (%v\n)", storedPaste.State)
 | 
			
		||||
		log.Printf("error: invalid paste.State (%v) for key '%v'\n", p.State, p.Key)
 | 
			
		||||
		msg := fmt.Sprintf("internal server error: invalid paste.State (%v\n)", p.State)
 | 
			
		||||
		renderInternalServerError(w, r, msg)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -210,18 +182,18 @@ func newRedirectPasteHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var storedPaste *storedPaste
 | 
			
		||||
	var paste *paste
 | 
			
		||||
	if err := db.DB.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		// Generate a new delete token for this paste
 | 
			
		||||
		var err error
 | 
			
		||||
		storedPaste, err = shortenURL(tx, userURL)
 | 
			
		||||
		paste, err = shortenURL(tx, userURL)
 | 
			
		||||
		return err
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		log.Printf("error: %v\n", err)
 | 
			
		||||
		renderInternalServerError(w, r, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	data := map[string]interface{}{"Paste": storedPaste}
 | 
			
		||||
	data := map[string]interface{}{"Paste": paste}
 | 
			
		||||
	render(w, r, "newRedirectPasteSuccess", data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -238,18 +210,13 @@ func deletePasteHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
 | 
			
		||||
	var errorCode int
 | 
			
		||||
	if err := db.DB.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		paste, err := getURL(tx, key)
 | 
			
		||||
		p, err := getPaste(tx, key)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errorCode = http.StatusNotFound
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if subtle.ConstantTimeCompare([]byte(deleteToken), []byte(paste.DeleteToken)) == 1 {
 | 
			
		||||
			// Replace the old paste with a new empty paste
 | 
			
		||||
			return savePaste(tx, key, storedPaste{
 | 
			
		||||
				Key:         paste.Key,
 | 
			
		||||
				State:       stateDeleted,
 | 
			
		||||
				DeleteToken: paste.DeleteToken,
 | 
			
		||||
			})
 | 
			
		||||
		if subtle.ConstantTimeCompare([]byte(deleteToken), []byte(p.DeleteToken)) == 1 {
 | 
			
		||||
			p.delete(tx)
 | 
			
		||||
		}
 | 
			
		||||
		errorCode = http.StatusForbidden
 | 
			
		||||
		return errors.New("invalid delete token")
 | 
			
		||||
@ -260,58 +227,13 @@ func deletePasteHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Retrieve a URL from the database
 | 
			
		||||
func getURL(tx *bolt.Tx, key string) (*storedPaste, error) {
 | 
			
		||||
	pastesBucket := tx.Bucket([]byte(db.BUCKET_PASTES))
 | 
			
		||||
	if pastesBucket == nil {
 | 
			
		||||
		return nil, errors.Errorf("bucket %v does not exist", db.BUCKET_PASTES)
 | 
			
		||||
	}
 | 
			
		||||
	storedBytes := pastesBucket.Get([]byte(key))
 | 
			
		||||
	if storedBytes == nil {
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
	storedPaste := &storedPaste{}
 | 
			
		||||
	err := gobmarsh.Unmarshal(storedBytes, storedPaste)
 | 
			
		||||
	return storedPaste, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Add a new URL to the database
 | 
			
		||||
//
 | 
			
		||||
// Returns the new ID if the url was successfully shortened
 | 
			
		||||
func shortenURL(tx *bolt.Tx, userURL *url.URL) (*storedPaste, error) {
 | 
			
		||||
	pastesBucket := tx.Bucket([]byte(db.BUCKET_PASTES))
 | 
			
		||||
	if pastesBucket == nil {
 | 
			
		||||
		return nil, errors.Errorf("bucket %v does not exist", db.BUCKET_PASTES)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 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 string
 | 
			
		||||
	for {
 | 
			
		||||
		var err error
 | 
			
		||||
		urlKey, err = generateURLKey(epoch)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, "url-key generation failed")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		found := pastesBucket.Get([]byte(urlKey))
 | 
			
		||||
		if found == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		isReserved := false
 | 
			
		||||
		for _, reservedKey := range ReservedPasteKeys {
 | 
			
		||||
			if strings.HasPrefix(urlKey, reservedKey) {
 | 
			
		||||
				isReserved = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !isReserved {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		epoch++
 | 
			
		||||
func shortenURL(tx *bolt.Tx, userURL *url.URL) (*paste, error) {
 | 
			
		||||
	pasteKey, err := generatePasteKey(tx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errors.Wrap(err, "generating paste key")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Also generate a deleteToken
 | 
			
		||||
@ -321,78 +243,18 @@ func shortenURL(tx *bolt.Tx, userURL *url.URL) (*storedPaste, error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Store the new key
 | 
			
		||||
	storedPaste := storedPaste{
 | 
			
		||||
	p := paste{
 | 
			
		||||
		Type:        typeRedirect,
 | 
			
		||||
		State:       statePresent,
 | 
			
		||||
		Content:     []byte(userURL.String()),
 | 
			
		||||
		Key:         urlKey,
 | 
			
		||||
		Key:         pasteKey,
 | 
			
		||||
		DeleteToken: deleteToken,
 | 
			
		||||
		TimeCreated: time.Now().UTC(),
 | 
			
		||||
	}
 | 
			
		||||
	if err := savePaste(tx, urlKey, storedPaste); err != nil {
 | 
			
		||||
	if err := p.save(tx); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return &storedPaste, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func savePaste(tx *bolt.Tx, key string, paste storedPaste) error {
 | 
			
		||||
	bucket := tx.Bucket([]byte(db.BUCKET_PASTES))
 | 
			
		||||
	if bucket == nil {
 | 
			
		||||
		return errors.Errorf("bucket %v does not exist", db.BUCKET_PASTES)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	buf, err := gobmarsh.Marshal(paste)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "encoding for database failed")
 | 
			
		||||
	}
 | 
			
		||||
	if err := bucket.Put([]byte(key), buf); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "database transaction failed")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func generateURLKey(epoch int) (string, error) {
 | 
			
		||||
	urlKey := make([]byte, 4+epoch)
 | 
			
		||||
	_, err := rand.Read(urlKey)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	// Put all the values in the range 0..64 for easier base64-encoding
 | 
			
		||||
	for i := 0; i < len(urlKey); i++ {
 | 
			
		||||
		urlKey[i] &= 0x3F
 | 
			
		||||
	}
 | 
			
		||||
	// Implement truncate-resistance by forcing the prefix to
 | 
			
		||||
	//     0b111110xxxxxxxxxx
 | 
			
		||||
	//       ^----- {epoch} ones followed by a single 0
 | 
			
		||||
	//
 | 
			
		||||
	// Example when epoch is 1: prefix is 0b10.
 | 
			
		||||
	i := 0
 | 
			
		||||
	for i < epoch {
 | 
			
		||||
		// Set this bit to 1
 | 
			
		||||
		limb := i / 6
 | 
			
		||||
		bit := i % 6
 | 
			
		||||
		urlKey[limb] |= 1 << uint(5-bit)
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	// Finally set the next bit to 0
 | 
			
		||||
	limb := i / 6
 | 
			
		||||
	bit := i % 6
 | 
			
		||||
	urlKey[limb] &= ^(1 << uint(5-bit))
 | 
			
		||||
 | 
			
		||||
	// Convert this ID to a canonical base64 notation
 | 
			
		||||
	for i := range urlKey {
 | 
			
		||||
		urlKey[i] = base64Alphabet[urlKey[i]]
 | 
			
		||||
	}
 | 
			
		||||
	return string(urlKey), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func generateDeleteToken() (string, error) {
 | 
			
		||||
	var deleteToken [16]byte
 | 
			
		||||
	_, err := rand.Read(deleteToken[:])
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	return hex.EncodeToString(deleteToken[:]), nil
 | 
			
		||||
	return &p, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getDeleteTokenFromRequest(r *http.Request) string {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										159
									
								
								handlers/paste.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								handlers/paste.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,159 @@
 | 
			
		||||
package handlers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/rand"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"gitea.hashru.nl/dsprenkels/rushlink/db"
 | 
			
		||||
	"gitea.hashru.nl/dsprenkels/rushlink/gobmarsh"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	bolt "go.etcd.io/bbolt"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type pasteType int
 | 
			
		||||
type pasteState int
 | 
			
		||||
 | 
			
		||||
type paste struct {
 | 
			
		||||
	Type        pasteType
 | 
			
		||||
	State       pasteState
 | 
			
		||||
	Content     []byte
 | 
			
		||||
	Key         string
 | 
			
		||||
	DeleteToken string
 | 
			
		||||
	TimeCreated time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	typeUndef    pasteType = 0
 | 
			
		||||
	typePaste              = 1
 | 
			
		||||
	typeRedirect           = 2
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	stateUndef   pasteState = 0
 | 
			
		||||
	statePresent            = 1
 | 
			
		||||
	stateDeleted            = 2
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Retrieve a paste from the database
 | 
			
		||||
func getPaste(tx *bolt.Tx, key string) (*paste, error) {
 | 
			
		||||
	pastesBucket := tx.Bucket([]byte(db.BUCKET_PASTES))
 | 
			
		||||
	if pastesBucket == nil {
 | 
			
		||||
		return nil, errors.Errorf("bucket %v does not exist", db.BUCKET_PASTES)
 | 
			
		||||
	}
 | 
			
		||||
	storedBytes := pastesBucket.Get([]byte(key))
 | 
			
		||||
	if storedBytes == nil {
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
	p := &paste{}
 | 
			
		||||
	err := gobmarsh.Unmarshal(storedBytes, p)
 | 
			
		||||
	return p, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *paste) save(tx *bolt.Tx) error {
 | 
			
		||||
	bucket := tx.Bucket([]byte(db.BUCKET_PASTES))
 | 
			
		||||
	if bucket == nil {
 | 
			
		||||
		return errors.Errorf("bucket %v does not exist", db.BUCKET_PASTES)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	buf, err := gobmarsh.Marshal(p)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "encoding for database failed")
 | 
			
		||||
	}
 | 
			
		||||
	if err := bucket.Put([]byte(p.Key), buf); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "database transaction failed")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p paste) delete(tx *bolt.Tx) error {
 | 
			
		||||
	// Replace the old paste with a new empty paste
 | 
			
		||||
	return (&paste{
 | 
			
		||||
		Key:         p.Key,
 | 
			
		||||
		State:       stateDeleted,
 | 
			
		||||
		DeleteToken: p.DeleteToken,
 | 
			
		||||
	}).save(tx)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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.
 | 
			
		||||
func generatePasteKey(tx *bolt.Tx) (string, error) {
 | 
			
		||||
	pastesBucket := tx.Bucket([]byte(db.BUCKET_PASTES))
 | 
			
		||||
	if pastesBucket == nil {
 | 
			
		||||
		return "", errors.Errorf("bucket %v does not exist", db.BUCKET_PASTES)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	epoch := 0
 | 
			
		||||
	var key string
 | 
			
		||||
	for {
 | 
			
		||||
		var err error
 | 
			
		||||
		key, err = generatePasteKeyInner(epoch)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", errors.Wrap(err, "url-key generation failed")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		found := pastesBucket.Get([]byte(key))
 | 
			
		||||
		if found == nil {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		isReserved := false
 | 
			
		||||
		for _, reservedKey := range ReservedPasteKeys {
 | 
			
		||||
			if strings.HasPrefix(key, reservedKey) {
 | 
			
		||||
				isReserved = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !isReserved {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		epoch++
 | 
			
		||||
	}
 | 
			
		||||
	return key, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func generatePasteKeyInner(epoch int) (string, error) {
 | 
			
		||||
	urlKey := make([]byte, 4+epoch)
 | 
			
		||||
	_, err := rand.Read(urlKey)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	// Put all the values in the range 0..64 for easier base64-encoding
 | 
			
		||||
	for i := 0; i < len(urlKey); i++ {
 | 
			
		||||
		urlKey[i] &= 0x3F
 | 
			
		||||
	}
 | 
			
		||||
	// Implement truncate-resistance by forcing the prefix to
 | 
			
		||||
	//     0b111110xxxxxxxxxx
 | 
			
		||||
	//       ^----- {epoch} ones followed by a single 0
 | 
			
		||||
	//
 | 
			
		||||
	// Example when epoch is 1: prefix is 0b10.
 | 
			
		||||
	i := 0
 | 
			
		||||
	for i < epoch {
 | 
			
		||||
		// Set this bit to 1
 | 
			
		||||
		limb := i / 6
 | 
			
		||||
		bit := i % 6
 | 
			
		||||
		urlKey[limb] |= 1 << uint(5-bit)
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	// Finally set the next bit to 0
 | 
			
		||||
	limb := i / 6
 | 
			
		||||
	bit := i % 6
 | 
			
		||||
	urlKey[limb] &= ^(1 << uint(5-bit))
 | 
			
		||||
 | 
			
		||||
	// Convert this ID to a canonical base64 notation
 | 
			
		||||
	for i := range urlKey {
 | 
			
		||||
		urlKey[i] = base64Alphabet[urlKey[i]]
 | 
			
		||||
	}
 | 
			
		||||
	return string(urlKey), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func generateDeleteToken() (string, error) {
 | 
			
		||||
	var deleteToken [16]byte
 | 
			
		||||
	_, err := rand.Read(deleteToken[:])
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	return hex.EncodeToString(deleteToken[:]), nil
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user