66 lines
1.7 KiB
Go
66 lines
1.7 KiB
Go
package research
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Cursors locate a public corpus row, not an account or authentication token.
|
|
// Binding to normalized filters prevents accidentally paging a different query.
|
|
type position struct {
|
|
Version int `json:"v"`
|
|
ID int64 `json:"id"`
|
|
Key string `json:"key"`
|
|
Before bool `json:"before"`
|
|
Filter string `json:"filter"`
|
|
}
|
|
|
|
var errCursor = errors.New("invalid cursor")
|
|
|
|
func filterKey(f Filters) string {
|
|
sort, search := f.Sort, f.SearchIn
|
|
if sort == "" {
|
|
sort = "recent"
|
|
}
|
|
if search == "" {
|
|
search = "text"
|
|
}
|
|
data, _ := json.Marshal([]string{strings.TrimSpace(f.Query), f.Topic, sort, search, strconv.Itoa(f.PageSize)})
|
|
sum := sha256.Sum256(data)
|
|
return hex.EncodeToString(sum[:16])
|
|
}
|
|
func encodeCursor(f Filters, p position, before bool) string {
|
|
p.Version, p.Before, p.Filter = 1, before, filterKey(f)
|
|
if f.Sort != "title" {
|
|
p.Key = ""
|
|
}
|
|
data, _ := json.Marshal(p)
|
|
return base64.RawURLEncoding.EncodeToString(data)
|
|
}
|
|
func decodeCursor(f Filters) (position, error) {
|
|
var p position
|
|
if f.Cursor == "" {
|
|
return p, nil
|
|
}
|
|
if len(f.Cursor) > 32768 {
|
|
return p, errCursor
|
|
}
|
|
data, err := base64.RawURLEncoding.Strict().DecodeString(f.Cursor)
|
|
if err != nil {
|
|
return p, errCursor
|
|
}
|
|
decoder := json.NewDecoder(bytes.NewReader(data))
|
|
decoder.DisallowUnknownFields()
|
|
if decoder.Decode(&p) != nil || decoder.Decode(new(any)) != io.EOF || p.Version != 1 || p.ID <= 0 || p.Filter != filterKey(f) || strings.ContainsRune(p.Key, 0) || (f.Sort != "title" && p.Key != "") {
|
|
return position{}, errCursor
|
|
}
|
|
return p, nil
|
|
}
|