shortcode

package
v0.60.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 12, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package shortcode is a kit for apps that hand out short codes (a link shortener, an invitation code): the alphabets a code is drawn from, the one canonical form a code is stored and looked up by, crypto/rand generation, and the guessing arithmetic a setup screen shows. It runs in the app's module; filex itself keeps none of an app's codes.

The arithmetic assumes filex's own miss limiter on an app's public route (MissesPerDay): the host counts the visits an app answered "miss" and holds a client back past that many.

Index

Constants

View Source
const (
	PresetDigits  = "digits"   // 0-9
	PresetAlnum   = "alnum"    // A-Z a-z 0-9 (a-z 0-9 when case does not matter)
	PresetURLSafe = "url-safe" // alnum and - . _ ~ (RFC 3986 "unreserved")
	PresetEmoji   = "emoji"    // emoji.txt
	PresetCustom  = "custom"
)

The alphabets the setup wizard offers. A custom set is the administrator's own list of symbols.

View Source
const (
	// MinBits is the floor of a generated code's keyspace, in bits
	// (log2 of |alphabet|^length). A rule under it cannot be saved.
	//
	// Why 45: filex's miss limiter lets one client make MissesPerDay wrong
	// guesses a day. An attacker with 10,000 addresses against 100,000 live
	// links finds ONE of them, on average, after
	// 2^45 / (100,000 × 10,000 × 100) ≈ 350 days - and the instance raises
	// an alarm long before. 40 bits is about 11 days under the same attacker.
	MinBits = 45
	// GoodBits is where the wizard stops warning.
	GoodBits = 50
	// MaxLength is the longest generated code (and alias), in symbols.
	MaxLength = 64
	// MinCustomSymbols / MaxCustomSymbols bound a custom set.
	MinCustomSymbols = 2
	MaxCustomSymbols = 4096
	// MinAliasLength is the shortest alias an app may ask for.
	MinAliasLength = 3
)

The limits on a code rule.

View Source
const (
	ModelAttackerAddresses = 10000
	ModelLiveLinks         = 100000
)

The attacker the wizard's estimate assumes (GuessDays).

View Source
const MissesPerDay = 100

MissesPerDay is the most wrong guesses filex lets one client make in a day on an app's public route before its miss limiter holds the client back (internal/shortlink; a test there keeps the two equal).

Variables

View Source
var (
	ErrPreset        = errors.New("short_link_alphabet_unknown")
	ErrCustomSymbols = errors.New("short_link_alphabet_symbols")
	ErrSymbol        = errors.New("short_link_alphabet_symbol")
	ErrCollision     = errors.New("short_link_alphabet_collision")
	ErrLength        = errors.New("short_link_length")
	ErrTooWeak       = errors.New("short_link_too_weak")
	ErrCode          = errors.New("short_link_code_invalid")
)

Errors a rule or a code is refused with. The API answers them as codes.

Functions

func Bits

func Bits(size, n int) float64

Bits is log2(size^n).

func Canonical

func Canonical(code string, caseSensitive bool) (string, bool)

Canonical is the canonical form of a code: NFC, variation selectors removed, and - when case does not matter - every symbol folded (FoldRune). ok is false for what can never be a code: empty, longer than MaxLength symbols, or holding a symbol SymbolAllowed refuses (a space, a control or invisible character, a URL delimiter, an emoji sequence joiner or a skin-tone modifier).

func CheckAlias

func CheckAlias(alias string, caseSensitive bool) (string, error)

CheckAlias validates a code an app chose (an alias) and answers its canonical form: at least MinAliasLength symbols, each one SymbolAllowed, and not a reserved path. An alias need not be drawn from the alphabet, and it is outside the keyspace floor - a word is guessable by nature.

func DecodeSegment

func DecodeSegment(escaped string) (string, bool)

DecodeSegment reads one ESCAPED path segment (from URL.EscapedPath) into text: percent escapes decoded once, the result valid UTF-8 with no `/` (an escaped slash is not part of a code). ok is false for anything else.

func EscapePath

func EscapePath(code string) string

EscapePath is the code as it goes into a link that is copied, mailed or handed to a chat: every byte outside ASCII percent-encoded (UTF-8), so a messenger that would stop a link at the first emoji sees one unbroken address. A browser shows it decoded in its address bar.

func FoldRune

func FoldRune(r rune) rune

FoldRune is the case-insensitive form of one symbol: the lower case of its upper case. So `A`, `a` fold to `a`, and `i`, `ı`, `I`, `İ` all fold to `i` - which is why a case-insensitive custom set may hold only one of them.

func GuessDays

func GuessDays(bits float64, links, addresses int) float64

GuessDays is how many days, on average, an attacker with `addresses` addresses, each held to MissesPerDay wrong guesses a day, needs to find ONE of `links` live links in a keyspace of `bits` bits. +Inf when nothing is guessing.

func Key

func Key(canonical string) string

Key is what the database stores and compares for a canonical code.

func ReservedCode

func ReservedCode(canonical string) bool

ReservedCode reports a canonical code that is one of those paths.

func SymbolAllowed

func SymbolAllowed(r rune) bool

SymbolAllowed reports whether r may be a symbol of a code: a letter, a number, one of `- . _ ~`, or an emoji of the emoji set. Nothing that ends a path segment or means something in a URL (`/ ? # % + &` ...), no space, no control or invisible character, no combining mark on its own.

func Symbols

func Symbols(code string) int

Symbols is how many symbols a canonical code has.

Types

type Alphabet

type Alphabet struct {
	// contains filtered or unexported fields
}

Alphabet is the set of symbols generated codes are drawn from, and whether a visitor's code is matched with case.

func NewAlphabet

func NewAlphabet(preset, custom string, caseSensitive bool) (Alphabet, error)

NewAlphabet builds one of the presets, or a custom set from custom (every rune one symbol; spaces and commas between them are ignored).

When case does not matter, the letters are matched folded (FoldRune): a preset draws lower-case letters only, and a custom set may not hold two symbols that fold to the same one (`a` and `A`; `i`, `ı`, `I` and `İ`), since a visitor could not tell the codes apart.

func (Alphabet) Bits

func (a Alphabet) Bits(n int) float64

Bits is the keyspace of a code of n symbols, log2(|alphabet|^n).

func (Alphabet) CaseSensitive

func (a Alphabet) CaseSensitive() bool

CaseSensitive reports whether codes are matched with case.

func (Alphabet) CheckRule

func (a Alphabet) CheckRule(n int) error

CheckRule refuses a length out of range or under the keyspace floor.

func (Alphabet) Generate

func (a Alphabet) Generate(n int) (string, error)

Generate draws a code of n symbols, every symbol uniformly and independently from crypto/rand (rejection sampling: no modulo bias, no sequence, nothing derived from a clock or a counter).

func (Alphabet) MinLengthFor

func (a Alphabet) MinLengthFor(bits float64) int

MinLengthFor is the shortest code of this alphabet that reaches MinBits.

func (Alphabet) Preset

func (a Alphabet) Preset() string

Preset is the alphabet's preset name.

func (Alphabet) Size

func (a Alphabet) Size() int

Size is how many symbols a code is drawn from.

func (Alphabet) Symbols

func (a Alphabet) Symbols() string

Symbols is the alphabet as a string, in order.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL