From 38b28f9bf43e5281415575938b97f241e004a3ba Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Wed, 12 Jan 2022 21:24:48 -0500 Subject: [PATCH] CLI; docs docs docs --- client/client.go | 10 ++- client/options.go | 15 +++++ cmd/publish.go | 44 ++++++++++++- cmd/serve.go | 16 ++--- docs/config.md | 89 ++++++++++++++----------- docs/publish.md | 95 ++++++++++++++++++++++++--- server/config.go | 148 +++++++++++++++++++++--------------------- server/server.go | 28 ++++---- server/server_test.go | 10 +-- server/visitor.go | 8 +-- 10 files changed, 309 insertions(+), 154 deletions(-) diff --git a/client/client.go b/client/client.go index 81defdc9..b3bf7ab4 100644 --- a/client/client.go +++ b/client/client.go @@ -67,6 +67,12 @@ func New(config *Config) *Client { } // Publish sends a message to a specific topic, optionally using options. +// See PublishReader for details. +func (c *Client) Publish(topic, message string, options ...PublishOption) (*Message, error) { + return c.PublishReader(topic, strings.NewReader(message), options...) +} + +// PublishReader sends a message to a specific topic, optionally using options. // // A topic can be either a full URL (e.g. https://myhost.lan/mytopic), a short URL which is then prepended https:// // (e.g. myhost.lan -> https://myhost.lan), or a short name which is expanded using the default host in the @@ -74,9 +80,9 @@ func New(config *Config) *Client { // // To pass title, priority and tags, check out WithTitle, WithPriority, WithTagsList, WithDelay, WithNoCache, // WithNoFirebase, and the generic WithHeader. -func (c *Client) Publish(topic, message string, options ...PublishOption) (*Message, error) { +func (c *Client) PublishReader(topic string, body io.Reader, options ...PublishOption) (*Message, error) { topicURL := c.expandTopicURL(topic) - req, _ := http.NewRequest("POST", topicURL, strings.NewReader(message)) + req, _ := http.NewRequest("POST", topicURL, body) for _, option := range options { if err := option(req); err != nil { return nil, err diff --git a/client/options.go b/client/options.go index 716528d7..ccf6985b 100644 --- a/client/options.go +++ b/client/options.go @@ -16,6 +16,11 @@ type PublishOption = RequestOption // SubscribeOption is an option that can be passed to a Client.Subscribe or Client.Poll call type SubscribeOption = RequestOption +// WithMessage sets the notification message. This is an alternative way to passing the message body. +func WithMessage(message string) PublishOption { + return WithHeader("X-Message", message) +} + // WithTitle adds a title to a message func WithTitle(title string) PublishOption { return WithHeader("X-Title", title) @@ -50,6 +55,16 @@ func WithClick(url string) PublishOption { return WithHeader("X-Click", url) } +// WithAttach sets a URL that will be used by the client to download an attachment +func WithAttach(attach string) PublishOption { + return WithHeader("X-Attach", attach) +} + +// WithFilename sets a filename for the attachment, and/or forces the HTTP body to interpreted as an attachment +func WithFilename(filename string) PublishOption { + return WithHeader("X-Filename", filename) +} + // WithEmail instructs the server to also send the message to the given e-mail address func WithEmail(email string) PublishOption { return WithHeader("X-Email", email) diff --git a/cmd/publish.go b/cmd/publish.go index 7c71a1b2..c2860b43 100644 --- a/cmd/publish.go +++ b/cmd/publish.go @@ -5,6 +5,9 @@ import ( "fmt" "github.com/urfave/cli/v2" "heckel.io/ntfy/client" + "io" + "os" + "path/filepath" "strings" ) @@ -21,6 +24,9 @@ var cmdPublish = &cli.Command{ &cli.StringFlag{Name: "tags", Aliases: []string{"tag", "T"}, Usage: "comma separated list of tags and emojis"}, &cli.StringFlag{Name: "delay", Aliases: []string{"at", "in", "D"}, Usage: "delay/schedule message"}, &cli.StringFlag{Name: "click", Aliases: []string{"U"}, Usage: "URL to open when notification is clicked"}, + &cli.StringFlag{Name: "attach", Aliases: []string{"a"}, Usage: "URL to send as an external attachment"}, + &cli.StringFlag{Name: "filename", Aliases: []string{"n"}, Usage: "Filename for the attachment"}, + &cli.StringFlag{Name: "file", Aliases: []string{"f"}, Usage: "File to upload as an attachment"}, &cli.StringFlag{Name: "email", Aliases: []string{"e-mail", "mail", "e"}, Usage: "also send to e-mail address"}, &cli.BoolFlag{Name: "no-cache", Aliases: []string{"C"}, Usage: "do not cache message server-side"}, &cli.BoolFlag{Name: "no-firebase", Aliases: []string{"F"}, Usage: "do not forward message to Firebase"}, @@ -37,6 +43,9 @@ Examples: ntfy pub --at=8:30am delayed_topic Laterzz # Send message at 8:30am ntfy pub -e phil@example.com alerts 'App is down!' # Also send email to phil@example.com ntfy pub --click="https://reddit.com" redd 'New msg' # Opens Reddit when notification is clicked + ntfy pub --attach="http://some.tld/file.zip" files # Send ZIP archive from URL as attachment + ntfy pub --file=flower.jpg flowers 'Nice!' # Send image.jpg as attachment + cat flower.jpg | ntfy pub --file=- flowers 'Nice!' # Same as above, send image.jpg as attachment ntfy trigger mywebhook # Sending without message, useful for webhooks Please also check out the docs on publishing messages. Especially for the --tags and --delay options, @@ -59,6 +68,9 @@ func execPublish(c *cli.Context) error { tags := c.String("tags") delay := c.String("delay") click := c.String("click") + attach := c.String("attach") + filename := c.String("filename") + file := c.String("file") email := c.String("email") noCache := c.Bool("no-cache") noFirebase := c.Bool("no-firebase") @@ -82,7 +94,13 @@ func execPublish(c *cli.Context) error { options = append(options, client.WithDelay(delay)) } if click != "" { - options = append(options, client.WithClick(email)) + options = append(options, client.WithClick(click)) + } + if attach != "" { + options = append(options, client.WithAttach(attach)) + } + if filename != "" { + options = append(options, client.WithFilename(filename)) } if email != "" { options = append(options, client.WithEmail(email)) @@ -93,8 +111,30 @@ func execPublish(c *cli.Context) error { if noFirebase { options = append(options, client.WithNoFirebase()) } + var body io.Reader + if file == "" { + body = strings.NewReader(message) + } else { + if message != "" { + options = append(options, client.WithMessage(message)) + } + if file == "-" { + if filename == "" { + options = append(options, client.WithFilename("stdin")) + } + body = c.App.Reader + } else { + if filename == "" { + options = append(options, client.WithFilename(filepath.Base(file))) + } + body, err = os.Open(file) + if err != nil { + return err + } + } + } cl := client.New(conf) - m, err := cl.Publish(topic, message, options...) + m, err := cl.PublishReader(topic, body, options...) if err != nil { return err } diff --git a/cmd/serve.go b/cmd/serve.go index 28ec5231..3c33f2e7 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -23,7 +23,7 @@ var flagsServe = []cli.Flag{ altsrc.NewStringFlag(&cli.StringFlag{Name: "cache-file", Aliases: []string{"C"}, EnvVars: []string{"NTFY_CACHE_FILE"}, Usage: "cache file used for message caching"}), altsrc.NewDurationFlag(&cli.DurationFlag{Name: "cache-duration", Aliases: []string{"b"}, EnvVars: []string{"NTFY_CACHE_DURATION"}, Value: server.DefaultCacheDuration, Usage: "buffer messages for this time to allow `since` requests"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-cache-dir", EnvVars: []string{"NTFY_ATTACHMENT_CACHE_DIR"}, Usage: "cache directory for attached files"}), - altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-total-size-limit", Aliases: []string{"A"}, EnvVars: []string{"NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT"}, DefaultText: "1G", Usage: "limit of the on-disk attachment cache"}), + altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-total-size-limit", Aliases: []string{"A"}, EnvVars: []string{"NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT"}, DefaultText: "5G", Usage: "limit of the on-disk attachment cache"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "attachment-file-size-limit", Aliases: []string{"Y"}, EnvVars: []string{"NTFY_ATTACHMENT_FILE_SIZE_LIMIT"}, DefaultText: "15M", Usage: "per-file attachment size limit (e.g. 300k, 2M, 100M)"}), altsrc.NewDurationFlag(&cli.DurationFlag{Name: "attachment-expiry-duration", Aliases: []string{"X"}, EnvVars: []string{"NTFY_ATTACHMENT_EXPIRY_DURATION"}, Value: server.DefaultAttachmentExpiryDuration, DefaultText: "3h", Usage: "duration after which uploaded attachments will be deleted (e.g. 3h, 20h)"}), altsrc.NewDurationFlag(&cli.DurationFlag{Name: "keepalive-interval", Aliases: []string{"k"}, EnvVars: []string{"NTFY_KEEPALIVE_INTERVAL"}, Value: server.DefaultKeepaliveInterval, Usage: "interval of keepalive messages"}), @@ -37,8 +37,8 @@ var flagsServe = []cli.Flag{ altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-server-addr-prefix", EnvVars: []string{"NTFY_SMTP_SERVER_ADDR_PREFIX"}, Usage: "SMTP email address prefix for topics to prevent spam (e.g. 'ntfy-')"}), altsrc.NewIntFlag(&cli.IntFlag{Name: "global-topic-limit", Aliases: []string{"T"}, EnvVars: []string{"NTFY_GLOBAL_TOPIC_LIMIT"}, Value: server.DefaultTotalTopicLimit, Usage: "total number of topics allowed"}), altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-subscription-limit", EnvVars: []string{"NTFY_VISITOR_SUBSCRIPTION_LIMIT"}, Value: server.DefaultVisitorSubscriptionLimit, Usage: "number of subscriptions per visitor"}), - altsrc.NewStringFlag(&cli.StringFlag{Name: "visitor-attachment-total-size-limit", EnvVars: []string{"NTFY_VISITOR_ATTACHMENT_TOTAL_SIZE_LIMIT"}, Value: "50M", Usage: "total storage limit used for attachments per visitor"}), - altsrc.NewStringFlag(&cli.StringFlag{Name: "visitor-attachment-daily-traffic-limit", EnvVars: []string{"NTFY_VISITOR_ATTACHMENT_DAILY_TRAFFIC_LIMIT"}, Value: "500M", Usage: "total daily attachment download/upload traffic limit per visitor"}), + altsrc.NewStringFlag(&cli.StringFlag{Name: "visitor-attachment-total-size-limit", EnvVars: []string{"NTFY_VISITOR_ATTACHMENT_TOTAL_SIZE_LIMIT"}, Value: "100M", Usage: "total storage limit used for attachments per visitor"}), + altsrc.NewStringFlag(&cli.StringFlag{Name: "visitor-attachment-daily-bandwidth-limit", EnvVars: []string{"NTFY_VISITOR_ATTACHMENT_DAILY_BANDWIDTH_LIMIT"}, Value: "500M", Usage: "total daily attachment download/upload bandwidth limit per visitor"}), altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-request-limit-burst", EnvVars: []string{"NTFY_VISITOR_REQUEST_LIMIT_BURST"}, Value: server.DefaultVisitorRequestLimitBurst, Usage: "initial limit of requests per visitor"}), altsrc.NewDurationFlag(&cli.DurationFlag{Name: "visitor-request-limit-replenish", EnvVars: []string{"NTFY_VISITOR_REQUEST_LIMIT_REPLENISH"}, Value: server.DefaultVisitorRequestLimitReplenish, Usage: "interval at which burst limit is replenished (one per x)"}), altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-email-limit-burst", EnvVars: []string{"NTFY_VISITOR_EMAIL_LIMIT_BURST"}, Value: server.DefaultVisitorEmailLimitBurst, Usage: "initial limit of e-mails per visitor"}), @@ -93,7 +93,7 @@ func execServe(c *cli.Context) error { totalTopicLimit := c.Int("global-topic-limit") visitorSubscriptionLimit := c.Int("visitor-subscription-limit") visitorAttachmentTotalSizeLimitStr := c.String("visitor-attachment-total-size-limit") - visitorAttachmentDailyTrafficLimitStr := c.String("visitor-attachment-daily-traffic-limit") + visitorAttachmentDailyBandwidthLimitStr := c.String("visitor-attachment-daily-bandwidth-limit") visitorRequestLimitBurst := c.Int("visitor-request-limit-burst") visitorRequestLimitReplenish := c.Duration("visitor-request-limit-replenish") visitorEmailLimitBurst := c.Int("visitor-email-limit-burst") @@ -134,11 +134,11 @@ func execServe(c *cli.Context) error { if err != nil { return err } - visitorAttachmentDailyTrafficLimit, err := parseSize(visitorAttachmentDailyTrafficLimitStr, server.DefaultVisitorAttachmentDailyTrafficLimit) + visitorAttachmentDailyBandwidthLimit, err := parseSize(visitorAttachmentDailyBandwidthLimitStr, server.DefaultVisitorAttachmentDailyBandwidthLimit) if err != nil { return err - } else if visitorAttachmentDailyTrafficLimit > math.MaxInt { - return fmt.Errorf("config option visitor-attachment-daily-traffic-limit must be lower than %d", math.MaxInt) + } else if visitorAttachmentDailyBandwidthLimit > math.MaxInt { + return fmt.Errorf("config option visitor-attachment-daily-bandwidth-limit must be lower than %d", math.MaxInt) } // Run server @@ -167,7 +167,7 @@ func execServe(c *cli.Context) error { conf.TotalTopicLimit = totalTopicLimit conf.VisitorSubscriptionLimit = visitorSubscriptionLimit conf.VisitorAttachmentTotalSizeLimit = visitorAttachmentTotalSizeLimit - conf.VisitorAttachmentDailyTrafficLimit = int(visitorAttachmentDailyTrafficLimit) + conf.VisitorAttachmentDailyBandwidthLimit = int(visitorAttachmentDailyBandwidthLimit) conf.VisitorRequestLimitBurst = visitorRequestLimitBurst conf.VisitorRequestLimitReplenish = visitorRequestLimitReplenish conf.VisitorEmailLimitBurst = visitorEmailLimitBurst diff --git a/docs/config.md b/docs/config.md index 69a8744e..c78fc886 100644 --- a/docs/config.md +++ b/docs/config.md @@ -153,6 +153,7 @@ or the root domain: proxy_http_version 1.1; proxy_buffering off; + proxy_request_buffering off; proxy_redirect off; proxy_set_header Host $http_host; @@ -161,6 +162,8 @@ or the root domain: proxy_connect_timeout 3m; proxy_send_timeout 3m; proxy_read_timeout 3m; + + client_max_body_size 20m; # Must be >= attachment-file-size-limit in /etc/ntfy/server.yml } } @@ -179,8 +182,9 @@ or the root domain: location / { proxy_pass http://127.0.0.1:2586; proxy_http_version 1.1; - + proxy_buffering off; + proxy_request_buffering off; proxy_redirect off; proxy_set_header Host $http_host; @@ -189,6 +193,8 @@ or the root domain: proxy_connect_timeout 3m; proxy_send_timeout 3m; proxy_read_timeout 3m; + + client_max_body_size 20m; # Must be >= attachment-file-size-limit in /etc/ntfy/server.yml } } ``` @@ -413,7 +419,12 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`). | `firebase-key-file` | `NTFY_FIREBASE_KEY_FILE` | *filename* | - | If set, also publish messages to a Firebase Cloud Messaging (FCM) topic for your app. This is optional and only required to save battery when using the Android app. See [Firebase (FCM](#firebase-fcm). | | `cache-file` | `NTFY_CACHE_FILE` | *filename* | - | If set, messages are cached in a local SQLite database instead of only in-memory. This allows for service restarts without losing messages in support of the since= parameter. See [message cache](#message-cache). | | `cache-duration` | `NTFY_CACHE_DURATION` | *duration* | 12h | Duration for which messages will be buffered before they are deleted. This is required to support the `since=...` and `poll=1` parameter. Set this to `0` to disable the cache entirely. | -| `behind-proxy` | `NTFY_BEHIND_PROXY` | *bool* | false | If set, the X-Forwarded-For header is used to determine the visitor IP address instead of the remote address of the connection. | +| `attachment-cache-dir` | `NTFY_ATTACHMENT_CACHE_DIR` | *directory* | - | Cache directory for attached files. To enable attachments, this has to be set. | +| `attachment-total-size-limit` | `NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT` | *size* | 5G | Limit of the on-disk attachment cache directory. If the limits is exceeded, new attachments will be rejected. | +| `attachment-file-size-limit` | `NTFY_ATTACHMENT_FILE_SIZE_LIMIT` | *size* | 15M | Per-file attachment size limit (e.g. 300k, 2M, 100M). Larger attachment will be rejected. | +| `attachment-expiry-duration` | `NTFY_ATTACHMENT_EXPIRY_DURATION` | *duration* | 3h | Duration after which uploaded attachments will be deleted (e.g. 3h, 20h). Strongly affects `visitor-attachment-total-size-limit`. | +| `keepalive-interval` | `NTFY_KEEPALIVE_INTERVAL` | *duration* | 55s | Interval in which keepalive messages are sent to the client. This is to prevent intermediaries closing the connection for inactivity. Note that the Android app has a hardcoded timeout at 77s, so it should be less than that. | +| `manager-interval` | `$NTFY_MANAGER_INTERVAL` | *duration* | 1m | Interval in which the manager prunes old messages, deletes topics and prints the stats. | | `smtp-sender-addr` | `NTFY_SMTP_SENDER_ADDR` | `host:port` | - | SMTP server address to allow email sending | | `smtp-sender-user` | `NTFY_SMTP_SENDER_USER` | *string* | - | SMTP user; only used if e-mail sending is enabled | | `smtp-sender-pass` | `NTFY_SMTP_SENDER_PASS` | *string* | - | SMTP password; only used if e-mail sending is enabled | @@ -421,26 +432,24 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`). | `smtp-server-listen` | `NTFY_SMTP_SERVER_LISTEN` | `[ip]:port` | - | Defines the IP address and port the SMTP server will listen on, e.g. `:25` or `1.2.3.4:25` | | `smtp-server-domain` | `NTFY_SMTP_SERVER_DOMAIN` | *domain name* | - | SMTP server e-mail domain, e.g. `ntfy.sh` | | `smtp-server-addr-prefix` | `NTFY_SMTP_SERVER_ADDR_PREFIX` | `[ip]:port` | - | Optional prefix for the e-mail addresses to prevent spam, e.g. `ntfy-` | -| `keepalive-interval` | `NTFY_KEEPALIVE_INTERVAL` | *duration* | 55s | Interval in which keepalive messages are sent to the client. This is to prevent intermediaries closing the connection for inactivity. Note that the Android app has a hardcoded timeout at 77s, so it should be less than that. | -| `manager-interval` | `$NTFY_MANAGER_INTERVAL` | *duration* | 1m | Interval in which the manager prunes old messages, deletes topics and prints the stats. | | `global-topic-limit` | `NTFY_GLOBAL_TOPIC_LIMIT` | *number* | 15,000 | Rate limiting: Total number of topics before the server rejects new topics. | | `visitor-subscription-limit` | `NTFY_VISITOR_SUBSCRIPTION_LIMIT` | *number* | 30 | Rate limiting: Number of subscriptions per visitor (IP address) | +| `visitor-attachment-total-size-limit` | `NTFY_VISITOR_ATTACHMENT_TOTAL_SIZE_LIMIT` | *size* | 100M | Total storage limit used for attachments per visitor, for all attachments combined. Storage is freed after attachments expire. See `attachment-expiry-duration`. | +| `visitor-attachment-daily-bandwidth-limit` | `NTFY_VISITOR_ATTACHMENT_DAILY_BANDWIDTH_LIMIT` | *size* | 500M | Total daily attachment download/upload traffic limit per visitor. This is to protect your bandwidth costs from exploding. | | `visitor-request-limit-burst` | `NTFY_VISITOR_REQUEST_LIMIT_BURST` | *number* | 60 | Allowed GET/PUT/POST requests per second, per visitor. This setting is the initial bucket of requests each visitor has | | `visitor-request-limit-replenish` | `NTFY_VISITOR_REQUEST_LIMIT_REPLENISH` | *duration* | 10s | Strongly related to `visitor-request-limit-burst`: The rate at which the bucket is refilled | -| `visitor-email-limit-burst` | `NTFY_VISITOR_EMAIL_LIMIT_BURST` | *number* | 16 |Initial limit of e-mails per visitor | +| `visitor-email-limit-burst` | `NTFY_VISITOR_EMAIL_LIMIT_BURST` | *number* | 16 | Initial limit of e-mails per visitor | | `visitor-email-limit-replenish` | `NTFY_VISITOR_EMAIL_LIMIT_REPLENISH` | *duration* | 1h | Strongly related to `visitor-email-limit-burst`: The rate at which the bucket is refilled | -xx daily traffic limit -xx DefaultVisitorAttachmentTotalSizeLimit -xx attachment cache dir -xx attachment +| `behind-proxy` | `NTFY_BEHIND_PROXY` | *bool* | false | If set, the X-Forwarded-For header is used to determine the visitor IP address instead of the remote address of the connection. | -The format for a *duration* is: `(smh)`, e.g. 30s, 20m or 1h. +The format for a *duration* is: `(smh)`, e.g. 30s, 20m or 1h. +The format for a *size* is: `(GMK)`, e.g. 1G, 200M or 4000k. ## Command line options ``` $ ntfy serve --help NAME: - ntfy serve - Run the ntfy server + main serve - Run the ntfy server USAGE: ntfy serve [OPTIONS..] @@ -456,31 +465,37 @@ DESCRIPTION: ntfy serve --listen-http :8080 # Starts server with alternate port OPTIONS: - --config value, -c value config file (default: /etc/ntfy/server.yml) [$NTFY_CONFIG_FILE] - --base-url value, -B value externally visible base URL for this host (e.g. https://ntfy.sh) [$NTFY_BASE_URL] - --listen-http value, -l value ip:port used to as HTTP listen address (default: ":80") [$NTFY_LISTEN_HTTP] - --listen-https value, -L value ip:port used to as HTTPS listen address [$NTFY_LISTEN_HTTPS] - --key-file value, -K value private key file, if listen-https is set [$NTFY_KEY_FILE] - --cert-file value, -E value certificate file, if listen-https is set [$NTFY_CERT_FILE] - --firebase-key-file value, -F value Firebase credentials file; if set additionally publish to FCM topic [$NTFY_FIREBASE_KEY_FILE] - --cache-file value, -C value cache file used for message caching [$NTFY_CACHE_FILE] - --cache-duration since, -b since buffer messages for this time to allow since requests (default: 12h0m0s) [$NTFY_CACHE_DURATION] - --keepalive-interval value, -k value interval of keepalive messages (default: 55s) [$NTFY_KEEPALIVE_INTERVAL] - --manager-interval value, -m value interval of for message pruning and stats printing (default: 1m0s) [$NTFY_MANAGER_INTERVAL] - --smtp-sender-addr value SMTP server address (host:port) for outgoing emails [$NTFY_SMTP_SENDER_ADDR] - --smtp-sender-user value SMTP user (if e-mail sending is enabled) [$NTFY_SMTP_SENDER_USER] - --smtp-sender-pass value SMTP password (if e-mail sending is enabled) [$NTFY_SMTP_SENDER_PASS] - --smtp-sender-from value SMTP sender address (if e-mail sending is enabled) [$NTFY_SMTP_SENDER_FROM] - --smtp-server-listen value SMTP server address (ip:port) for incoming emails, e.g. :25 [$NTFY_SMTP_SERVER_LISTEN] - --smtp-server-domain value SMTP domain for incoming e-mail, e.g. ntfy.sh [$NTFY_SMTP_SERVER_DOMAIN] - --smtp-server-addr-prefix value SMTP email address prefix for topics to prevent spam (e.g. 'ntfy-') [$NTFY_SMTP_SERVER_ADDR_PREFIX] - --global-topic-limit value, -T value total number of topics allowed (default: 5000) [$NTFY_GLOBAL_TOPIC_LIMIT] - --visitor-subscription-limit value number of subscriptions per visitor (default: 30) [$NTFY_VISITOR_SUBSCRIPTION_LIMIT] - --visitor-request-limit-burst value initial limit of requests per visitor (default: 60) [$NTFY_VISITOR_REQUEST_LIMIT_BURST] - --visitor-request-limit-replenish value interval at which burst limit is replenished (one per x) (default: 10s) [$NTFY_VISITOR_REQUEST_LIMIT_REPLENISH] - --visitor-email-limit-burst value initial limit of e-mails per visitor (default: 16) [$NTFY_VISITOR_EMAIL_LIMIT_BURST] - --visitor-email-limit-replenish value interval at which burst limit is replenished (one per x) (default: 1h0m0s) [$NTFY_VISITOR_EMAIL_LIMIT_REPLENISH] - --behind-proxy, -P if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting) (default: false) [$NTFY_BEHIND_PROXY] - --help, -h show help (default: false) + --config value, -c value config file (default: /etc/ntfy/server.yml) [$NTFY_CONFIG_FILE] + --base-url value, -B value externally visible base URL for this host (e.g. https://ntfy.sh) [$NTFY_BASE_URL] + --listen-http value, -l value ip:port used to as HTTP listen address (default: ":80") [$NTFY_LISTEN_HTTP] + --listen-https value, -L value ip:port used to as HTTPS listen address [$NTFY_LISTEN_HTTPS] + --key-file value, -K value private key file, if listen-https is set [$NTFY_KEY_FILE] + --cert-file value, -E value certificate file, if listen-https is set [$NTFY_CERT_FILE] + --firebase-key-file value, -F value Firebase credentials file; if set additionally publish to FCM topic [$NTFY_FIREBASE_KEY_FILE] + --cache-file value, -C value cache file used for message caching [$NTFY_CACHE_FILE] + --cache-duration since, -b since buffer messages for this time to allow since requests (default: 12h0m0s) [$NTFY_CACHE_DURATION] + --attachment-cache-dir value cache directory for attached files [$NTFY_ATTACHMENT_CACHE_DIR] + --attachment-total-size-limit value, -A value limit of the on-disk attachment cache (default: 5G) [$NTFY_ATTACHMENT_TOTAL_SIZE_LIMIT] + --attachment-file-size-limit value, -Y value per-file attachment size limit (e.g. 300k, 2M, 100M) (default: 15M) [$NTFY_ATTACHMENT_FILE_SIZE_LIMIT] + --attachment-expiry-duration value, -X value duration after which uploaded attachments will be deleted (e.g. 3h, 20h) (default: 3h) [$NTFY_ATTACHMENT_EXPIRY_DURATION] + --keepalive-interval value, -k value interval of keepalive messages (default: 55s) [$NTFY_KEEPALIVE_INTERVAL] + --manager-interval value, -m value interval of for message pruning and stats printing (default: 1m0s) [$NTFY_MANAGER_INTERVAL] + --smtp-sender-addr value SMTP server address (host:port) for outgoing emails [$NTFY_SMTP_SENDER_ADDR] + --smtp-sender-user value SMTP user (if e-mail sending is enabled) [$NTFY_SMTP_SENDER_USER] + --smtp-sender-pass value SMTP password (if e-mail sending is enabled) [$NTFY_SMTP_SENDER_PASS] + --smtp-sender-from value SMTP sender address (if e-mail sending is enabled) [$NTFY_SMTP_SENDER_FROM] + --smtp-server-listen value SMTP server address (ip:port) for incoming emails, e.g. :25 [$NTFY_SMTP_SERVER_LISTEN] + --smtp-server-domain value SMTP domain for incoming e-mail, e.g. ntfy.sh [$NTFY_SMTP_SERVER_DOMAIN] + --smtp-server-addr-prefix value SMTP email address prefix for topics to prevent spam (e.g. 'ntfy-') [$NTFY_SMTP_SERVER_ADDR_PREFIX] + --global-topic-limit value, -T value total number of topics allowed (default: 15000) [$NTFY_GLOBAL_TOPIC_LIMIT] + --visitor-subscription-limit value number of subscriptions per visitor (default: 30) [$NTFY_VISITOR_SUBSCRIPTION_LIMIT] + --visitor-attachment-total-size-limit value total storage limit used for attachments per visitor (default: "100M") [$NTFY_VISITOR_ATTACHMENT_TOTAL_SIZE_LIMIT] + --visitor-attachment-daily-bandwidth-limit value total daily attachment download/upload bandwidth limit per visitor (default: "500M") [$NTFY_VISITOR_ATTACHMENT_DAILY_BANDWIDTH_LIMIT] + --visitor-request-limit-burst value initial limit of requests per visitor (default: 60) [$NTFY_VISITOR_REQUEST_LIMIT_BURST] + --visitor-request-limit-replenish value interval at which burst limit is replenished (one per x) (default: 10s) [$NTFY_VISITOR_REQUEST_LIMIT_REPLENISH] + --visitor-email-limit-burst value initial limit of e-mails per visitor (default: 16) [$NTFY_VISITOR_EMAIL_LIMIT_BURST] + --visitor-email-limit-replenish value interval at which burst limit is replenished (one per x) (default: 1h0m0s) [$NTFY_VISITOR_EMAIL_LIMIT_REPLENISH] + --behind-proxy, -P if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting) (default: false) [$NTFY_BEHIND_PROXY] + --help, -h show help (default: false) ``` diff --git a/docs/publish.md b/docs/publish.md index 61d30411..ace4c593 100644 --- a/docs/publish.md +++ b/docs/publish.md @@ -659,16 +659,81 @@ Here's an example that will open Reddit when the notification is clicked: ])); ``` -## Send files + URLs +## Attachments (send files) +You can send images and other files to your phone as attachments to a notification. The attachments are then downloaded +onto your phone (depending on size and setting automatically), and can be used from the Downloads folder. + +There are two different ways to send attachments, either via PUT or by passing an external URL. + +**Upload attachments from your computer**: To send an attachment from your computer as a file, you can send it as the +PUT request body. If a message is greater than the maximum message size or consists of non-UTF-8 characters, the ntfy +server will automatically detect the mime type and size, and send the message as an attachment file. + +You can optionally pass a filename (or force attachment mode for small text-messages) by passing the `X-Filename` header +or query parameter (or any of its aliases `Filename`, `File` or `f`). + +Here's an example showing how to upload an image: + + +=== "Command line (curl)" + ``` + curl \ + -T flower.jpg \ + ntfy.sh/flowers + ``` + +=== "ntfy CLI" + ``` + ntfy publish \ + --file=flower.jpg \ + flowers + ``` + +=== "HTTP" + ``` http + PUT /flowers HTTP/1.1 + Host: ntfy.sh + + + ``` + +=== "JavaScript" + ``` javascript + fetch('https://ntfy.sh/flowers', { + method: 'PUT', + body: document.getElementById("file").files[0] + }) + ``` + +=== "Go" + ``` go + file, _ := os.Open("flower.jpg") + req, _ := http.NewRequest("PUT", "https://ntfy.sh/flowers", file) + http.DefaultClient.Do(req) + ``` + +=== "Python" + ``` python + requests.put("https://ntfy.sh/flowers", + data=open("flower.jpg", 'rb')) + ``` + +=== "PHP" + ``` php-inline + file_get_contents('https://ntfy.sh/reddit_alerts', false, stream_context_create([ + 'http' => [ + 'method' => 'PUT', + 'content' => XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXxx + ] + ])); + ``` + ``` - Uploaded attachment - External attachment - Preview without attachment -# Upload and send attachment -curl -T image.jpg ntfy.sh/howdy - # Upload and send attachment with custom message and filename curl \ -T flower.jpg \ @@ -951,17 +1016,30 @@ to `no`. This will instruct the server not to forward messages to Firebase. ])); ``` +### UnifiedPush +!!! info + This setting is not relevant to users, only to app developers and people interested in [UnifiedPush](https://unifiedpush.org). + +[UnifiedPush](https://unifiedpush.org) is a standard for receiving push notifications without using the Google-owned +[Firebase Cloud Messaging (FCM)](https://firebase.google.com/docs/cloud-messaging) service. It puts push notifications +in the control of the user. ntfy can act as a **UnifiedPush distributor**, forwarding messages to apps that support it. + +When publishing messages to a topic, apps using ntfy as a UnifiedPush distributor can set the `X-UnifiedPush` header or query +parameter (or any of its aliases `unifiedpush` or `up`) to `1` to [disable Firebase](#disable-firebase). As of today, this +option is equivalent to `Firebase: no`, but was introduced to allow future flexibility. + ## Limitations There are a few limitations to the API to prevent abuse and to keep the server healthy. Most of them you won't run into, but just in case, let's list them all: | Limit | Description | |---|---| -| **Message length** | Each message can be up to 4096 bytes long. Longer messages are truncated. | +| **Message length** | Each message can be up to 4,096 bytes long. Longer messages are truncated. | | **Requests** | By default, the server is configured to allow 60 requests at once, and then refills the your allowed requests bucket at a rate of one request per 10 seconds. You can read more about this in the [rate limiting](config.md#rate-limiting) section. | | **E-mails** | By default, the server is configured to allow sending 16 e-mails at once, and then refills the your allowed e-mail bucket at a rate of one per hour. You can read more about this in the [rate limiting](config.md#rate-limiting) section. | | **Subscription limits** | By default, the server allows each visitor to keep 30 connections to the server open. | -| **Total number of topics** | By default, the server is configured to allow 5,000 topics. The ntfy.sh server has higher limits though. | +| **Bandwidth** | By default, the server allows 500 MB of GET/PUT/POST traffic for attachments per visitor in a 24 hour period. Traffic exceeding that is rejected. | +| **Total number of topics** | By default, the server is configured to allow 15,000 topics. The ntfy.sh server has higher limits though. | ## List of all parameters The following is a list of all parameters that can be passed when publishing a message. Parameter names are **case-insensitive**, @@ -975,8 +1053,9 @@ and can be passed as **HTTP headers** or **query parameters in the URL**. They a | `X-Tags` | `Tags`, `Tag`, `ta` | [Tags and emojis](#tags-emojis) | | `X-Delay` | `Delay`, `X-At`, `At`, `X-In`, `In` | Timestamp or duration for [delayed delivery](#scheduled-delivery) | | `X-Click` | `Click` | URL to open when [notification is clicked](#click-action) | -| `X-Filename` | `Filename`, `file`, `f` | XXXXXXXXXXXXXXXX | +| `X-Attach` | `Attach`, `a` | URL to send as an [attachment](#attachments-send-files), as an alternative to PUT/POST-ing an attachment | +| `X-Filename` | `Filename`, `file`, `f` | Optional [attachment](#attachments-send-files) filename, as it appears in the client | | `X-Email` | `X-E-Mail`, `Email`, `E-Mail`, `mail`, `e` | E-mail address for [e-mail notifications](#e-mail-notifications) | | `X-Cache` | `Cache` | Allows disabling [message caching](#message-caching) | | `X-Firebase` | `Firebase` | Allows disabling [sending to Firebase](#disable-firebase) | -| `X-UnifiedPush` | `UnifiedPush`, `up` | XXXXXXXXXXXXXXXX | +| `X-UnifiedPush` | `UnifiedPush`, `up` | [UnifiedPush](#unifiedpush) publish option, currently equivalent to `Firebase: no` | diff --git a/server/config.go b/server/config.go index e304d849..3843cdae 100644 --- a/server/config.go +++ b/server/config.go @@ -23,8 +23,8 @@ const ( const ( DefaultMessageLengthLimit = 4096 // Bytes DefaultTotalTopicLimit = 15000 - DefaultAttachmentTotalSizeLimit = int64(10 * 1024 * 1024 * 1024) // 10 GB - DefaultAttachmentFileSizeLimit = int64(15 * 1024 * 1024) // 15 MB + DefaultAttachmentTotalSizeLimit = int64(5 * 1024 * 1024 * 1024) // 5 GB + DefaultAttachmentFileSizeLimit = int64(15 * 1024 * 1024) // 15 MB DefaultAttachmentExpiryDuration = 3 * time.Hour ) @@ -33,87 +33,87 @@ const ( // - per visitor request limit: max number of PUT/GET/.. requests (here: 60 requests bucket, replenished at a rate of one per 10 seconds) // - per visitor email limit: max number of emails (here: 16 email bucket, replenished at a rate of one per hour) // - per visitor attachment size limit: total per-visitor attachment size in bytes to be stored on the server -// - per visitor attachment daily traffic limit: number of bytes that can be transferred to/from the server +// - per visitor attachment daily bandwidth limit: number of bytes that can be transferred to/from the server const ( - DefaultVisitorSubscriptionLimit = 30 - DefaultVisitorRequestLimitBurst = 60 - DefaultVisitorRequestLimitReplenish = 10 * time.Second - DefaultVisitorEmailLimitBurst = 16 - DefaultVisitorEmailLimitReplenish = time.Hour - DefaultVisitorAttachmentTotalSizeLimit = 100 * 1024 * 1024 // 100 MB - DefaultVisitorAttachmentDailyTrafficLimit = 500 * 1024 * 1024 // 500 MB + DefaultVisitorSubscriptionLimit = 30 + DefaultVisitorRequestLimitBurst = 60 + DefaultVisitorRequestLimitReplenish = 10 * time.Second + DefaultVisitorEmailLimitBurst = 16 + DefaultVisitorEmailLimitReplenish = time.Hour + DefaultVisitorAttachmentTotalSizeLimit = 100 * 1024 * 1024 // 100 MB + DefaultVisitorAttachmentDailyBandwidthLimit = 500 * 1024 * 1024 // 500 MB ) // Config is the main config struct for the application. Use New to instantiate a default config struct. type Config struct { - BaseURL string - ListenHTTP string - ListenHTTPS string - KeyFile string - CertFile string - FirebaseKeyFile string - CacheFile string - CacheDuration time.Duration - AttachmentCacheDir string - AttachmentTotalSizeLimit int64 - AttachmentFileSizeLimit int64 - AttachmentExpiryDuration time.Duration - KeepaliveInterval time.Duration - ManagerInterval time.Duration - AtSenderInterval time.Duration - FirebaseKeepaliveInterval time.Duration - SMTPSenderAddr string - SMTPSenderUser string - SMTPSenderPass string - SMTPSenderFrom string - SMTPServerListen string - SMTPServerDomain string - SMTPServerAddrPrefix string - MessageLimit int - MinDelay time.Duration - MaxDelay time.Duration - TotalTopicLimit int - TotalAttachmentSizeLimit int64 - VisitorSubscriptionLimit int - VisitorAttachmentTotalSizeLimit int64 - VisitorAttachmentDailyTrafficLimit int - VisitorRequestLimitBurst int - VisitorRequestLimitReplenish time.Duration - VisitorEmailLimitBurst int - VisitorEmailLimitReplenish time.Duration - BehindProxy bool + BaseURL string + ListenHTTP string + ListenHTTPS string + KeyFile string + CertFile string + FirebaseKeyFile string + CacheFile string + CacheDuration time.Duration + AttachmentCacheDir string + AttachmentTotalSizeLimit int64 + AttachmentFileSizeLimit int64 + AttachmentExpiryDuration time.Duration + KeepaliveInterval time.Duration + ManagerInterval time.Duration + AtSenderInterval time.Duration + FirebaseKeepaliveInterval time.Duration + SMTPSenderAddr string + SMTPSenderUser string + SMTPSenderPass string + SMTPSenderFrom string + SMTPServerListen string + SMTPServerDomain string + SMTPServerAddrPrefix string + MessageLimit int + MinDelay time.Duration + MaxDelay time.Duration + TotalTopicLimit int + TotalAttachmentSizeLimit int64 + VisitorSubscriptionLimit int + VisitorAttachmentTotalSizeLimit int64 + VisitorAttachmentDailyBandwidthLimit int + VisitorRequestLimitBurst int + VisitorRequestLimitReplenish time.Duration + VisitorEmailLimitBurst int + VisitorEmailLimitReplenish time.Duration + BehindProxy bool } // NewConfig instantiates a default new server config func NewConfig() *Config { return &Config{ - BaseURL: "", - ListenHTTP: DefaultListenHTTP, - ListenHTTPS: "", - KeyFile: "", - CertFile: "", - FirebaseKeyFile: "", - CacheFile: "", - CacheDuration: DefaultCacheDuration, - AttachmentCacheDir: "", - AttachmentTotalSizeLimit: DefaultAttachmentTotalSizeLimit, - AttachmentFileSizeLimit: DefaultAttachmentFileSizeLimit, - AttachmentExpiryDuration: DefaultAttachmentExpiryDuration, - KeepaliveInterval: DefaultKeepaliveInterval, - ManagerInterval: DefaultManagerInterval, - MessageLimit: DefaultMessageLengthLimit, - MinDelay: DefaultMinDelay, - MaxDelay: DefaultMaxDelay, - AtSenderInterval: DefaultAtSenderInterval, - FirebaseKeepaliveInterval: DefaultFirebaseKeepaliveInterval, - TotalTopicLimit: DefaultTotalTopicLimit, - VisitorSubscriptionLimit: DefaultVisitorSubscriptionLimit, - VisitorAttachmentTotalSizeLimit: DefaultVisitorAttachmentTotalSizeLimit, - VisitorAttachmentDailyTrafficLimit: DefaultVisitorAttachmentDailyTrafficLimit, - VisitorRequestLimitBurst: DefaultVisitorRequestLimitBurst, - VisitorRequestLimitReplenish: DefaultVisitorRequestLimitReplenish, - VisitorEmailLimitBurst: DefaultVisitorEmailLimitBurst, - VisitorEmailLimitReplenish: DefaultVisitorEmailLimitReplenish, - BehindProxy: false, + BaseURL: "", + ListenHTTP: DefaultListenHTTP, + ListenHTTPS: "", + KeyFile: "", + CertFile: "", + FirebaseKeyFile: "", + CacheFile: "", + CacheDuration: DefaultCacheDuration, + AttachmentCacheDir: "", + AttachmentTotalSizeLimit: DefaultAttachmentTotalSizeLimit, + AttachmentFileSizeLimit: DefaultAttachmentFileSizeLimit, + AttachmentExpiryDuration: DefaultAttachmentExpiryDuration, + KeepaliveInterval: DefaultKeepaliveInterval, + ManagerInterval: DefaultManagerInterval, + MessageLimit: DefaultMessageLengthLimit, + MinDelay: DefaultMinDelay, + MaxDelay: DefaultMaxDelay, + AtSenderInterval: DefaultAtSenderInterval, + FirebaseKeepaliveInterval: DefaultFirebaseKeepaliveInterval, + TotalTopicLimit: DefaultTotalTopicLimit, + VisitorSubscriptionLimit: DefaultVisitorSubscriptionLimit, + VisitorAttachmentTotalSizeLimit: DefaultVisitorAttachmentTotalSizeLimit, + VisitorAttachmentDailyBandwidthLimit: DefaultVisitorAttachmentDailyBandwidthLimit, + VisitorRequestLimitBurst: DefaultVisitorRequestLimitBurst, + VisitorRequestLimitReplenish: DefaultVisitorRequestLimitReplenish, + VisitorEmailLimitBurst: DefaultVisitorEmailLimitBurst, + VisitorEmailLimitReplenish: DefaultVisitorEmailLimitReplenish, + BehindProxy: false, } } diff --git a/server/server.go b/server/server.go index 08d89d41..f0de4b99 100644 --- a/server/server.go +++ b/server/server.go @@ -123,11 +123,6 @@ var ( docsStaticFs embed.FS docsStaticCached = &util.CachingEmbedFS{ModTime: time.Now(), FS: docsStaticFs} - errHTTPNotFound = &errHTTP{40401, http.StatusNotFound, "page not found", ""} - errHTTPTooManyRequestsLimitRequests = &errHTTP{42901, http.StatusTooManyRequests, "limit reached: too many requests, please be nice", "https://ntfy.sh/docs/publish/#limitations"} - errHTTPTooManyRequestsLimitEmails = &errHTTP{42902, http.StatusTooManyRequests, "limit reached: too many emails, please be nice", "https://ntfy.sh/docs/publish/#limitations"} - errHTTPTooManyRequestsLimitSubscriptions = &errHTTP{42903, http.StatusTooManyRequests, "limit reached: too many active subscriptions, please be nice", "https://ntfy.sh/docs/publish/#limitations"} - errHTTPTooManyRequestsLimitTotalTopics = &errHTTP{42904, http.StatusTooManyRequests, "limit reached: the total number of topics on the server has been reached, please contact the admin", "https://ntfy.sh/docs/publish/#limitations"} errHTTPBadRequestEmailDisabled = &errHTTP{40001, http.StatusBadRequest, "e-mail notifications are not enabled", "https://ntfy.sh/docs/config/#e-mail-notifications"} errHTTPBadRequestDelayNoCache = &errHTTP{40002, http.StatusBadRequest, "cannot disable cache for delayed message", ""} errHTTPBadRequestDelayNoEmail = &errHTTP{40003, http.StatusBadRequest, "delayed e-mail notifications are not supported", ""} @@ -139,22 +134,27 @@ var ( errHTTPBadRequestTopicInvalid = &errHTTP{40009, http.StatusBadRequest, "invalid topic: path invalid", ""} errHTTPBadRequestTopicDisallowed = &errHTTP{40010, http.StatusBadRequest, "invalid topic: topic name is disallowed", ""} errHTTPBadRequestMessageNotUTF8 = &errHTTP{40011, http.StatusBadRequest, "invalid message: message must be UTF-8 encoded", ""} - errHTTPBadRequestAttachmentTooLarge = &errHTTP{40012, http.StatusBadRequest, "invalid request: attachment too large, or traffic limit reached", ""} + errHTTPBadRequestAttachmentTooLarge = &errHTTP{40012, http.StatusBadRequest, "invalid request: attachment too large, or bandwidth limit reached", ""} errHTTPBadRequestAttachmentURLInvalid = &errHTTP{40013, http.StatusBadRequest, "invalid request: attachment URL is invalid", ""} errHTTPBadRequestAttachmentURLPeakGeneral = &errHTTP{40014, http.StatusBadRequest, "invalid request: attachment URL peak failed", ""} errHTTPBadRequestAttachmentURLPeakNon2xx = &errHTTP{40015, http.StatusBadRequest, "invalid request: attachment URL peak failed with non-2xx status code", ""} errHTTPBadRequestAttachmentsDisallowed = &errHTTP{40016, http.StatusBadRequest, "invalid request: attachments not allowed", ""} errHTTPBadRequestAttachmentsExpiryBeforeDelivery = &errHTTP{40017, http.StatusBadRequest, "invalid request: attachment expiry before delayed delivery date", ""} - errHTTPTooManyRequestsAttachmentTrafficLimit = &errHTTP{42901, http.StatusTooManyRequests, "too many requests: daily traffic limit reached", "https://ntfy.sh/docs/publish/#limitations"} + errHTTPNotFound = &errHTTP{40401, http.StatusNotFound, "page not found", ""} + errHTTPTooManyRequestsLimitRequests = &errHTTP{42901, http.StatusTooManyRequests, "limit reached: too many requests, please be nice", "https://ntfy.sh/docs/publish/#limitations"} + errHTTPTooManyRequestsLimitEmails = &errHTTP{42902, http.StatusTooManyRequests, "limit reached: too many emails, please be nice", "https://ntfy.sh/docs/publish/#limitations"} + errHTTPTooManyRequestsLimitSubscriptions = &errHTTP{42903, http.StatusTooManyRequests, "limit reached: too many active subscriptions, please be nice", "https://ntfy.sh/docs/publish/#limitations"} + errHTTPTooManyRequestsLimitTotalTopics = &errHTTP{42904, http.StatusTooManyRequests, "limit reached: the total number of topics on the server has been reached, please contact the admin", "https://ntfy.sh/docs/publish/#limitations"} + errHTTPTooManyRequestsAttachmentBandwidthLimit = &errHTTP{42905, http.StatusTooManyRequests, "too many requests: daily bandwidth limit reached", "https://ntfy.sh/docs/publish/#limitations"} errHTTPInternalError = &errHTTP{50001, http.StatusInternalServerError, "internal server error", ""} errHTTPInternalErrorInvalidFilePath = &errHTTP{50002, http.StatusInternalServerError, "internal server error: invalid file path", ""} ) const ( - firebaseControlTopic = "~control" // See Android if changed - emptyMessageBody = "triggered" - fcmMessageLimit = 4000 // see maybeTruncateFCMMessage for details - defaultAttachmentMessage = "You received a file: %s" + firebaseControlTopic = "~control" // See Android if changed + emptyMessageBody = "triggered" // Used if message body is empty + defaultAttachmentMessage = "You received a file: %s" // Used if message body is empty, and there is an attachment + fcmMessageLimit = 4000 // see maybeTruncateFCMMessage for details ) // New instantiates a new Server. It creates the cache and adds a Firebase @@ -432,8 +432,8 @@ func (s *Server) handleFile(w http.ResponseWriter, r *http.Request, v *visitor) if err != nil { return errHTTPNotFound } - if err := v.TrafficLimiter().Allow(stat.Size()); err != nil { - return errHTTPTooManyRequestsAttachmentTrafficLimit + if err := v.BandwidthLimiter().Allow(stat.Size()); err != nil { + return errHTTPTooManyRequestsAttachmentBandwidthLimit } w.Header().Set("Content-Length", fmt.Sprintf("%d", stat.Size())) f, err := os.Open(file) @@ -652,7 +652,7 @@ func (s *Server) handleBodyAsAttachment(r *http.Request, v *visitor, m *message, if m.Message == "" { m.Message = fmt.Sprintf(defaultAttachmentMessage, m.Attachment.Name) } - m.Attachment.Size, err = s.fileCache.Write(m.ID, body, v.TrafficLimiter(), util.NewFixedLimiter(remainingVisitorAttachmentSize)) + m.Attachment.Size, err = s.fileCache.Write(m.ID, body, v.BandwidthLimiter(), util.NewFixedLimiter(remainingVisitorAttachmentSize)) if err == util.ErrLimitReached { return errHTTPBadRequestAttachmentTooLarge } else if err != nil { diff --git a/server/server_test.go b/server/server_test.go index 03b7759e..04ac5586 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -844,11 +844,11 @@ func TestServer_PublishAttachmentAndPrune(t *testing.T) { require.Equal(t, 404, response.Code) } -func TestServer_PublishAttachmentTrafficLimit(t *testing.T) { +func TestServer_PublishAttachmentBandwidthLimit(t *testing.T) { content := util.RandomString(5000) // > 4096 c := newTestConfig(t) - c.VisitorAttachmentDailyTrafficLimit = 5*5000 + 123 // A little more than 1 upload and 3 downloads + c.VisitorAttachmentDailyBandwidthLimit = 5*5000 + 123 // A little more than 1 upload and 3 downloads s := newTestServer(t, c) // Publish attachment @@ -868,14 +868,14 @@ func TestServer_PublishAttachmentTrafficLimit(t *testing.T) { response = request(t, s, "GET", path, "", nil) err := toHTTPError(t, response.Body.String()) require.Equal(t, 429, response.Code) - require.Equal(t, 42901, err.Code) + require.Equal(t, 42905, err.Code) } -func TestServer_PublishAttachmentTrafficLimitUploadOnly(t *testing.T) { +func TestServer_PublishAttachmentBandwidthLimitUploadOnly(t *testing.T) { content := util.RandomString(5000) // > 4096 c := newTestConfig(t) - c.VisitorAttachmentDailyTrafficLimit = 5*5000 + 500 // 5 successful uploads + c.VisitorAttachmentDailyBandwidthLimit = 5*5000 + 500 // 5 successful uploads s := newTestServer(t, c) // 5 successful uploads diff --git a/server/visitor.go b/server/visitor.go index 8ae56dcb..948fe44c 100644 --- a/server/visitor.go +++ b/server/visitor.go @@ -26,7 +26,7 @@ type visitor struct { requests *rate.Limiter emails *rate.Limiter subscriptions util.Limiter - traffic util.Limiter + bandwidth util.Limiter seen time.Time mu sync.Mutex } @@ -38,7 +38,7 @@ func newVisitor(conf *Config, ip string) *visitor { requests: rate.NewLimiter(rate.Every(conf.VisitorRequestLimitReplenish), conf.VisitorRequestLimitBurst), emails: rate.NewLimiter(rate.Every(conf.VisitorEmailLimitReplenish), conf.VisitorEmailLimitBurst), subscriptions: util.NewFixedLimiter(int64(conf.VisitorSubscriptionLimit)), - traffic: util.NewBytesLimiter(conf.VisitorAttachmentDailyTrafficLimit, 24*time.Hour), + bandwidth: util.NewBytesLimiter(conf.VisitorAttachmentDailyBandwidthLimit, 24*time.Hour), seen: time.Now(), } } @@ -82,8 +82,8 @@ func (v *visitor) Keepalive() { v.seen = time.Now() } -func (v *visitor) TrafficLimiter() util.Limiter { - return v.traffic +func (v *visitor) BandwidthLimiter() util.Limiter { + return v.bandwidth } func (v *visitor) Stale() bool {