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 | ## USAGE | ||||||
| 
 | 
 | ||||||
|  |     # Upload a file | ||||||
|  |     curl -F'file=@yourfile.png' <a href="{{Request.Host}}">{{Request.Host}}</a> | ||||||
|  | 
 | ||||||
|     # Shorten a URL |     # Shorten a URL | ||||||
|     curl -F'shorten=http://example.com/some/long/url' <a href="{{Request.Host}}">{{Request.Host}}</a> |     curl -F'shorten=http://example.com/some/long/url' <a href="{{Request.Host}}">{{Request.Host}}</a> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -6,6 +6,9 @@ the command line. | |||||||
| 
 | 
 | ||||||
| ## USAGE | ## USAGE | ||||||
| 
 | 
 | ||||||
|  |     # Upload a file | ||||||
|  |     curl -F'file=@yourfile.png' {{Request.Host}} | ||||||
|  | 
 | ||||||
|     # Shorten a URL |     # Shorten a URL | ||||||
|     curl -F'shorten=http://example.com/some/long/url' {{Request.Host}} |     curl -F'shorten=http://example.com/some/long/url' {{Request.Host}} | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,15 +1,12 @@ | |||||||
| package handlers | package handlers | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"crypto/rand" |  | ||||||
| 	"crypto/subtle" | 	"crypto/subtle" | ||||||
| 	"encoding/base64" | 	"encoding/base64" | ||||||
| 	"encoding/hex" |  | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"log" | 	"log" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"strings" |  | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/gorilla/mux" | 	"github.com/gorilla/mux" | ||||||
| @ -17,31 +14,6 @@ import ( | |||||||
| 	bolt "go.etcd.io/bbolt" | 	bolt "go.etcd.io/bbolt" | ||||||
| 
 | 
 | ||||||
| 	"gitea.hashru.nl/dsprenkels/rushlink/db" | 	"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 | 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) { | func viewPasteHandlerInner(w http.ResponseWriter, r *http.Request, flags viewPaste) { | ||||||
| 	vars := mux.Vars(r) | 	vars := mux.Vars(r) | ||||||
| 	key := vars["key"] | 	key := vars["key"] | ||||||
| 	var storedPaste *storedPaste | 	var p *paste | ||||||
| 	if err := db.DB.View(func(tx *bolt.Tx) error { | 	if err := db.DB.View(func(tx *bolt.Tx) error { | ||||||
| 		var err error | 		var err error | ||||||
| 		storedPaste, err = getURL(tx, key) | 		p, err = getPaste(tx, key) | ||||||
| 		return err | 		return err | ||||||
| 	}); err != nil { | 	}); err != nil { | ||||||
| 		log.Printf("error: %v\n", err) | 		log.Printf("error: %v\n", err) | ||||||
| 		renderInternalServerError(w, r, err) | 		renderInternalServerError(w, r, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	if storedPaste == nil { | 	if p == nil { | ||||||
| 		renderError(w, r, http.StatusNotFound, "url key not found in the database") | 		renderError(w, r, http.StatusNotFound, "url key not found in the database") | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @ -130,7 +102,7 @@ func viewPasteHandlerInner(w http.ResponseWriter, r *http.Request, flags viewPas | |||||||
| 		if deleteToken == "" { | 		if deleteToken == "" { | ||||||
| 			canDelete.String = "undefined" | 			canDelete.String = "undefined" | ||||||
| 		} else { | 		} else { | ||||||
| 			if subtle.ConstantTimeCompare([]byte(deleteToken), []byte(storedPaste.DeleteToken)) == 1 { | 			if subtle.ConstantTimeCompare([]byte(deleteToken), []byte(p.DeleteToken)) == 1 { | ||||||
| 				canDelete.Bool = true | 				canDelete.Bool = true | ||||||
| 				canDelete.String = "correct" | 				canDelete.String = "correct" | ||||||
| 			} else { | 			} else { | ||||||
| @ -139,31 +111,31 @@ func viewPasteHandlerInner(w http.ResponseWriter, r *http.Request, flags viewPas | |||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		data := map[string]interface{}{ | 		data := map[string]interface{}{ | ||||||
| 			"Paste":     storedPaste, | 			"Paste":     p, | ||||||
| 			"CanDelete": canDelete, | 			"CanDelete": canDelete, | ||||||
| 		} | 		} | ||||||
| 		render(w, r, "pasteMeta", data) | 		render(w, r, "pasteMeta", data) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	switch storedPaste.State { | 	switch p.State { | ||||||
| 	case statePresent: | 	case statePresent: | ||||||
| 		if flags&viewNoRedirect == 0 { | 		if flags&viewNoRedirect == 0 { | ||||||
| 			rawurl := string(storedPaste.Content) | 			rawurl := string(p.Content) | ||||||
| 			urlParse, err := url.Parse(rawurl) | 			urlParse, err := url.Parse(rawurl) | ||||||
| 			if err != nil { | 			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") | 				renderInternalServerError(w, r, "invalid url in database") | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
| 			http.Redirect(w, r, urlParse.String(), http.StatusSeeOther) | 			http.Redirect(w, r, urlParse.String(), http.StatusSeeOther) | ||||||
| 		} | 		} | ||||||
| 		w.Write(storedPaste.Content) | 		w.Write(p.Content) | ||||||
| 	case stateDeleted: | 	case stateDeleted: | ||||||
| 		renderError(w, r, http.StatusGone, "paste has been deleted") | 		renderError(w, r, http.StatusGone, "paste has been deleted") | ||||||
| 	default: | 	default: | ||||||
| 		log.Printf("error: invalid storedPaste.State (%v) for key '%v'\n", storedPaste.State, storedPaste.Key) | 		log.Printf("error: invalid paste.State (%v) for key '%v'\n", p.State, p.Key) | ||||||
| 		msg := fmt.Sprintf("internal server error: invalid storedPaste.State (%v\n)", storedPaste.State) | 		msg := fmt.Sprintf("internal server error: invalid paste.State (%v\n)", p.State) | ||||||
| 		renderInternalServerError(w, r, msg) | 		renderInternalServerError(w, r, msg) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @ -210,18 +182,18 @@ func newRedirectPasteHandler(w http.ResponseWriter, r *http.Request) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var storedPaste *storedPaste | 	var paste *paste | ||||||
| 	if err := db.DB.Update(func(tx *bolt.Tx) error { | 	if err := db.DB.Update(func(tx *bolt.Tx) error { | ||||||
| 		// Generate a new delete token for this paste | 		// Generate a new delete token for this paste | ||||||
| 		var err error | 		var err error | ||||||
| 		storedPaste, err = shortenURL(tx, userURL) | 		paste, err = shortenURL(tx, userURL) | ||||||
| 		return err | 		return err | ||||||
| 	}); err != nil { | 	}); err != nil { | ||||||
| 		log.Printf("error: %v\n", err) | 		log.Printf("error: %v\n", err) | ||||||
| 		renderInternalServerError(w, r, err) | 		renderInternalServerError(w, r, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	data := map[string]interface{}{"Paste": storedPaste} | 	data := map[string]interface{}{"Paste": paste} | ||||||
| 	render(w, r, "newRedirectPasteSuccess", data) | 	render(w, r, "newRedirectPasteSuccess", data) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -238,18 +210,13 @@ func deletePasteHandler(w http.ResponseWriter, r *http.Request) { | |||||||
| 
 | 
 | ||||||
| 	var errorCode int | 	var errorCode int | ||||||
| 	if err := db.DB.Update(func(tx *bolt.Tx) error { | 	if err := db.DB.Update(func(tx *bolt.Tx) error { | ||||||
| 		paste, err := getURL(tx, key) | 		p, err := getPaste(tx, key) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			errorCode = http.StatusNotFound | 			errorCode = http.StatusNotFound | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		if subtle.ConstantTimeCompare([]byte(deleteToken), []byte(paste.DeleteToken)) == 1 { | 		if subtle.ConstantTimeCompare([]byte(deleteToken), []byte(p.DeleteToken)) == 1 { | ||||||
| 			// Replace the old paste with a new empty paste | 			p.delete(tx) | ||||||
| 			return savePaste(tx, key, storedPaste{ |  | ||||||
| 				Key:         paste.Key, |  | ||||||
| 				State:       stateDeleted, |  | ||||||
| 				DeleteToken: paste.DeleteToken, |  | ||||||
| 			}) |  | ||||||
| 		} | 		} | ||||||
| 		errorCode = http.StatusForbidden | 		errorCode = http.StatusForbidden | ||||||
| 		return errors.New("invalid delete token") | 		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 | // Add a new URL to the database | ||||||
| // | // | ||||||
| // Returns the new ID if the url was successfully shortened | // Returns the new ID if the url was successfully shortened | ||||||
| func shortenURL(tx *bolt.Tx, userURL *url.URL) (*storedPaste, error) { | func shortenURL(tx *bolt.Tx, userURL *url.URL) (*paste, error) { | ||||||
| 	pastesBucket := tx.Bucket([]byte(db.BUCKET_PASTES)) | 	pasteKey, err := generatePasteKey(tx) | ||||||
| 	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 { | 	if err != nil { | ||||||
| 			return nil, errors.Wrap(err, "url-key generation failed") | 		return nil, errors.Wrap(err, "generating paste key") | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		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++ |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Also generate a deleteToken | 	// Also generate a deleteToken | ||||||
| @ -321,78 +243,18 @@ func shortenURL(tx *bolt.Tx, userURL *url.URL) (*storedPaste, error) { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Store the new key | 	// Store the new key | ||||||
| 	storedPaste := storedPaste{ | 	p := paste{ | ||||||
| 		Type:        typeRedirect, | 		Type:        typeRedirect, | ||||||
| 		State:       statePresent, | 		State:       statePresent, | ||||||
| 		Content:     []byte(userURL.String()), | 		Content:     []byte(userURL.String()), | ||||||
| 		Key:         urlKey, | 		Key:         pasteKey, | ||||||
| 		DeleteToken: deleteToken, | 		DeleteToken: deleteToken, | ||||||
| 		TimeCreated: time.Now().UTC(), | 		TimeCreated: time.Now().UTC(), | ||||||
| 	} | 	} | ||||||
| 	if err := savePaste(tx, urlKey, storedPaste); err != nil { | 	if err := p.save(tx); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	return &storedPaste, nil | 	return &p, 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 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func getDeleteTokenFromRequest(r *http.Request) string { | 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