Rename to uncensored-send

This commit is contained in:
2026-09-13 08:57:01 +02:00
parent eaa0a5d1d7
commit 7d0d303c2a
21 changed files with 61 additions and 61 deletions
+1 -1
View File
@@ -1,2 +1,2 @@
/send /uncensored-send
/data/ /data/
+4 -4
View File
@@ -13,8 +13,8 @@ Built for sharing large binaries with friends so that I don't have to manually m
``` ```
go build go build
./send token add me --vanity --admin ./uncensored-send token add me --vanity --admin
./send ./uncensored-send
``` ```
Open <http://localhost:8080>, click **Log in**, paste the token. That's it. Open <http://localhost:8080>, click **Log in**, paste the token. That's it.
@@ -25,9 +25,9 @@ upload limit.
## Options ## Options
**Read `./send --help` rather than this file.** **Read `./uncensored-send --help` rather than this file.**
Every option also reads from `SEND_`-prefixed environment variables. Every option also reads from `UNCENSORED_SEND_`-prefixed environment variables.
## Development ## Development
+1 -1
View File
@@ -1,3 +1,3 @@
module send module uncensored-send
go 1.25 go 1.25
+1 -1
View File
@@ -18,7 +18,7 @@ import (
"sync" "sync"
"time" "time"
"send/internal/config" "uncensored-send/internal/config"
) )
// tokenFilePerm is deliberately stricter than the rest of the data directory: // tokenFilePerm is deliberately stricter than the rest of the data directory:
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"testing" "testing"
"time" "time"
"send/internal/config" "uncensored-send/internal/config"
) )
func defaults() *config.Config { func defaults() *config.Config {
+1 -1
View File
@@ -35,7 +35,7 @@ type Config struct {
trustedProxyNets []*net.IPNet trustedProxyNets []*net.IPNet
} }
const EnvPrefix = "SEND_" const EnvPrefix = "UNCENSORED_SEND_"
// Register wires every option onto s. Short forms exist only for the options // Register wires every option onto s. Short forms exist only for the options
// reached often; everything else is long-only, by design. // reached often; everything else is long-only, by design.
+4 -4
View File
@@ -86,7 +86,7 @@ func TestFormatRoundTrips(t *testing.T) {
func TestHyphenConvention(t *testing.T) { func TestHyphenConvention(t *testing.T) {
newSet := func() (*Set, *string) { newSet := func() (*Set, *string) {
var listen string var listen string
s := NewSet("test", "SEND_TEST_") s := NewSet("test", "UNCENSORED_SEND_TEST_")
s.String(&listen, "listen", "l", "default", "ADDR", "listen address") s.String(&listen, "listen", "l", "default", "ADDR", "listen address")
return s, &listen return s, &listen
} }
@@ -116,9 +116,9 @@ func TestHyphenConvention(t *testing.T) {
} }
func TestEnvFallback(t *testing.T) { func TestEnvFallback(t *testing.T) {
t.Setenv("SEND_TEST_MAX_SIZE", "4GiB") t.Setenv("UNCENSORED_SEND_TEST_MAX_SIZE", "4GiB")
var size int64 var size int64
s := NewSet("test", "SEND_TEST_") s := NewSet("test", "UNCENSORED_SEND_TEST_")
s.Size(&size, "max-size", "s", "2GiB", "cap") s.Size(&size, "max-size", "s", "2GiB", "cap")
if err := s.Parse(nil); err != nil { if err := s.Parse(nil); err != nil {
t.Fatal(err) t.Fatal(err)
@@ -128,7 +128,7 @@ func TestEnvFallback(t *testing.T) {
} }
// An explicit flag still wins over the environment. // An explicit flag still wins over the environment.
s = NewSet("test", "SEND_TEST_") s = NewSet("test", "UNCENSORED_SEND_TEST_")
s.Size(&size, "max-size", "s", "2GiB", "cap") s.Size(&size, "max-size", "s", "2GiB", "cap")
if err := s.Parse([]string{"--max-size", "1GiB"}); err != nil { if err := s.Parse([]string{"--max-size", "1GiB"}); err != nil {
t.Fatal(err) t.Fatal(err)
+3 -3
View File
@@ -52,7 +52,7 @@ func (s *Set) note(long, short string) {
} }
} }
// envName maps --max-size to SEND_MAX_SIZE. // envName maps --max-size to UNCENSORED_SEND_MAX_SIZE.
func (s *Set) envName(long string) string { func (s *Set) envName(long string) string {
return s.envPrefix + strings.ToUpper(strings.ReplaceAll(long, "-", "_")) return s.envPrefix + strings.ToUpper(strings.ReplaceAll(long, "-", "_"))
} }
@@ -183,7 +183,7 @@ func (s *Set) checkConvention(args []string) error {
return nil return nil
} }
// applyEnv seeds every option from its SEND_-prefixed environment variable. // applyEnv seeds every option from its UNCENSORED_SEND_-prefixed environment variable.
// Called before parsing so an explicit command-line flag always wins. // Called before parsing so an explicit command-line flag always wins.
func (s *Set) applyEnv() error { func (s *Set) applyEnv() error {
for _, sp := range s.specs { for _, sp := range s.specs {
@@ -214,7 +214,7 @@ func (s *Set) Parse(args []string) error {
// Changed reports whether an option was given on the command line. // Changed reports whether an option was given on the command line.
// //
// Values picked up from the environment do not count: a command that acts only // Values picked up from the environment do not count: a command that acts only
// on the options you actually typed must not quietly act on a SEND_ variable // on the options you actually typed must not quietly act on a UNCENSORED_SEND_ variable
// exported for the server. // exported for the server.
func (s *Set) Changed(long string) bool { func (s *Set) Changed(long string) bool {
if s.fromEnv[long] { if s.fromEnv[long] {
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"slices" "slices"
"time" "time"
"send/internal/config" "uncensored-send/internal/config"
) )
// adminPage is the one view that shows every object, regardless of who // adminPage is the one view that shows every object, regardless of who
+1 -1
View File
@@ -14,7 +14,7 @@ import (
// reason to prefer it over localStorage, where any injected script could // reason to prefer it over localStorage, where any injected script could
// exfiltrate the credential. The page never needs to see the value: the server // exfiltrate the credential. The page never needs to see the value: the server
// resolves it and renders who the caller is. // resolves it and renders who the caller is.
const tokenCookie = "send_token" const tokenCookie = "uncensored_send_token"
// rememberFor is how long a persisted login survives. Tokens are revoked by // rememberFor is how long a persisted login survives. Tokens are revoked by
// deleting them from the token file, so a long window costs nothing. // deleting them from the token file, so a long window costs nothing.
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"net/http" "net/http"
"strings" "strings"
"send/internal/auth" "uncensored-send/internal/auth"
"send/internal/store" "uncensored-send/internal/store"
) )
// handleDelete removes an object early. Three credentials are accepted: the // handleDelete removes an object early. Three credentials are accepted: the
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"net/http" "net/http"
"strings" "strings"
"send/internal/store" "uncensored-send/internal/store"
) )
// downloadCSP is as inert as a policy gets. Combined with the attachment // downloadCSP is as inert as a policy gets. Combined with the attachment
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"net/url" "net/url"
"strings" "strings"
"send/internal/config" "uncensored-send/internal/config"
) )
// loginPage backs both the form and its error redisplay. // loginPage backs both the form and its error redisplay.
+3 -3
View File
@@ -6,9 +6,9 @@ import (
"net/http" "net/http"
"time" "time"
"send/internal/auth" "uncensored-send/internal/auth"
"send/internal/config" "uncensored-send/internal/config"
"send/internal/store" "uncensored-send/internal/store"
) )
func writeJSON(w http.ResponseWriter, status int, v any) { func writeJSON(w http.ResponseWriter, status int, v any) {
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"sync" "sync"
"time" "time"
"send/internal/config" "uncensored-send/internal/config"
) )
// limiter is a per-key token bucket. It is deliberately small: there is no // limiter is a per-key token bucket. It is deliberately small: there is no
+4 -4
View File
@@ -11,10 +11,10 @@ import (
"strings" "strings"
"time" "time"
"send/internal/auth" "uncensored-send/internal/auth"
"send/internal/config" "uncensored-send/internal/config"
"send/internal/store" "uncensored-send/internal/store"
"send/web" "uncensored-send/web"
) )
// Server holds everything the handlers need. It is safe for concurrent use. // Server holds everything the handlers need. It is safe for concurrent use.
+4 -4
View File
@@ -19,9 +19,9 @@ import (
"testing" "testing"
"time" "time"
"send/internal/auth" "uncensored-send/internal/auth"
"send/internal/config" "uncensored-send/internal/config"
"send/internal/store" "uncensored-send/internal/store"
) )
// clock is the fixed instant tests start from; s.now is swapped so expiry can // clock is the fixed instant tests start from; s.now is swapped so expiry can
@@ -42,7 +42,7 @@ func newHarness(t *testing.T, tweak func(*config.Config)) *harness {
dir := t.TempDir() dir := t.TempDir()
cfg := config.Config{} cfg := config.Config{}
fs := config.NewSet("test", "SEND_TEST_") fs := config.NewSet("test", "UNCENSORED_SEND_TEST_")
cfg.Register(fs) cfg.Register(fs)
if err := fs.Parse(nil); err != nil { if err := fs.Parse(nil); err != nil {
t.Fatal(err) t.Fatal(err)
+3 -3
View File
@@ -10,9 +10,9 @@ import (
"strings" "strings"
"time" "time"
"send/internal/auth" "uncensored-send/internal/auth"
"send/internal/config" "uncensored-send/internal/config"
"send/internal/store" "uncensored-send/internal/store"
) )
const ( const (
+12 -12
View File
@@ -1,4 +1,4 @@
// Command send is a small self-hosted file drop: uploads land in a flat data // Command uncensored-send is a small self-hosted file drop: uploads land in a flat data
// directory, expire on their own, and are served back as inert attachments. // directory, expire on their own, and are served back as inert attachments.
package main package main
@@ -15,10 +15,10 @@ import (
"syscall" "syscall"
"time" "time"
"send/internal/auth" "uncensored-send/internal/auth"
"send/internal/config" "uncensored-send/internal/config"
"send/internal/server" "uncensored-send/internal/server"
"send/internal/store" "uncensored-send/internal/store"
) )
func main() { func main() {
@@ -26,7 +26,7 @@ func main() {
if errors.Is(err, flag.ErrHelp) { if errors.Is(err, flag.ErrHelp) {
os.Exit(0) os.Exit(0)
} }
fmt.Fprintln(os.Stderr, "send: "+err.Error()) fmt.Fprintln(os.Stderr, "uncensored-send: "+err.Error())
os.Exit(1) os.Exit(1)
} }
} }
@@ -38,13 +38,13 @@ func run(args []string) error {
return serve(args) return serve(args)
} }
const serveUsage = `send - a small self-hosted file drop const serveUsage = `uncensored-send - a small self-hosted file drop
Usage: Usage:
send [options] run the server uncensored-send [options] run the server
send token add <name> [opts] mint a token, printing it once uncensored-send token add <name> [opts] mint a token, printing it once
send token list list tokens uncensored-send token list list tokens
send token rm <name> remove a token uncensored-send token rm <name> remove a token
Options take one hyphen with a single letter (-s) and two with a full Options take one hyphen with a single letter (-s) and two with a full
word (--max-size). Every option can also be set from the environment. word (--max-size). Every option can also be set from the environment.
@@ -54,7 +54,7 @@ Options:
func serve(args []string) error { func serve(args []string) error {
var cfg config.Config var cfg config.Config
fs := config.NewSet("send", config.EnvPrefix) fs := config.NewSet("uncensored-send", config.EnvPrefix)
fs.SetOutput(os.Stderr) fs.SetOutput(os.Stderr)
cfg.Register(fs) cfg.Register(fs)
+11 -11
View File
@@ -12,19 +12,19 @@ import (
"text/tabwriter" "text/tabwriter"
"time" "time"
"send/internal/auth" "uncensored-send/internal/auth"
"send/internal/config" "uncensored-send/internal/config"
) )
const tokenUsage = `send token - manage upload credentials const tokenUsage = `uncensored-send token - manage upload credentials
Usage: Usage:
send token add <name> [options] generate a token uncensored-send token add <name> [options] generate a token
send token add <name> --token - read a chosen one from stdin uncensored-send token add <name> --token - read a chosen one from stdin
send token rotate <name> [--token -] replace the secret, keep everything else uncensored-send token rotate <name> [--token -] replace the secret, keep everything else
send token set <name> [options] change limits and flags in place uncensored-send token set <name> [options] change limits and flags in place
send token list [options] uncensored-send token list [options]
send token rm <name> [options] uncensored-send token rm <name> [options]
A token grants its own size and lifetime limits. Any limit left unset is A token grants its own size and lifetime limits. Any limit left unset is
inherited from the running server's defaults, so a token with no options inherited from the running server's defaults, so a token with no options
@@ -136,7 +136,7 @@ type tokenOptions struct {
} }
func (o *tokenOptions) register() *config.Set { func (o *tokenOptions) register() *config.Set {
fs := config.NewSet("send token", config.EnvPrefix) fs := config.NewSet("uncensored-send token", config.EnvPrefix)
fs.String(&o.dataDir, "data", "d", "./data", "DIR", "directory holding the data") fs.String(&o.dataDir, "data", "d", "./data", "DIR", "directory holding the data")
fs.String(&o.tokensPath, "tokens", "", "", "FILE", "token file location (default <data>/tokens.json)") fs.String(&o.tokensPath, "tokens", "", "", "FILE", "token file location (default <data>/tokens.json)")
fs.String(&o.maxSize, "max-size", "s", "", "SIZE", "per-upload cap for this token; 'unlimited' to remove it") fs.String(&o.maxSize, "max-size", "s", "", "SIZE", "per-upload cap for this token; 'unlimited' to remove it")
@@ -296,7 +296,7 @@ func tokenSet(file *auth.File, name string, opts tokenOptions, fs *config.Set) e
changes = append(changes, fmt.Sprintf("--%s=%t", f.flag, f.value)) changes = append(changes, fmt.Sprintf("--%s=%t", f.flag, f.value))
} }
if fs.Changed("token") { if fs.Changed("token") {
return errors.New("use \"send token rotate\" to change the secret") return errors.New("use \"uncensored-send token rotate\" to change the secret")
} }
return nil return nil
}) })
+1 -1
View File
@@ -52,7 +52,7 @@
<section class="card"> <section class="card">
<h2>Tokens</h2> <h2>Tokens</h2>
<p class="hint"> <p class="hint">
Tokens are minted, rotated and revoked with <code>./send token</code> on the Tokens are minted, rotated and revoked with <code>./uncensored-send token</code> on the
server. They are deliberately not manageable from here: nothing that hands server. They are deliberately not manageable from here: nothing that hands
out credentials should be reachable over the network. out credentials should be reachable over the network.
</p> </p>