Docs docs docs
This commit is contained in:
		
							parent
							
								
									762333c28f
								
							
						
					
					
						commit
						034c81288c
					
				
					 11 changed files with 346 additions and 141 deletions
				
			
		|  | @ -25,7 +25,7 @@ var cmdPublish = &cli.Command{ | ||||||
| 		&cli.StringFlag{Name: "delay", Aliases: []string{"at", "in", "D"}, Usage: "delay/schedule message"}, | 		&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: "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: "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: "filename", Aliases: []string{"name", "n"}, Usage: "Filename for the attachment"}, | ||||||
| 		&cli.StringFlag{Name: "file", Aliases: []string{"f"}, Usage: "File to upload as an 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.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-cache", Aliases: []string{"C"}, Usage: "do not cache message server-side"}, | ||||||
|  |  | ||||||
|  | @ -36,13 +36,13 @@ Subscribers can retrieve cached messaging using the [`poll=1` parameter](subscri | ||||||
| [`since=` parameter](subscribe/api.md#fetch-cached-messages). | [`since=` parameter](subscribe/api.md#fetch-cached-messages). | ||||||
| 
 | 
 | ||||||
| ## Attachments | ## Attachments | ||||||
| If desired, you may allow users to upload and [attach files to notifications](publish.md#attachments-send-files). To enable | If desired, you may allow users to upload and [attach files to notifications](publish.md#attachments). To enable | ||||||
| this feature, you have to simply configure an attachment cache directory and a base URL (`attachment-cache-dir`, `base-url`).  | this feature, you have to simply configure an attachment cache directory and a base URL (`attachment-cache-dir`, `base-url`).  | ||||||
| Once these options are set and the directory is writable by the server user, you can upload attachments via PUT. | Once these options are set and the directory is writable by the server user, you can upload attachments via PUT. | ||||||
| 
 | 
 | ||||||
| By default, attachments are stored in the disk-case **for only 3 hours**. The main reason for this is to avoid legal issues | By default, attachments are stored in the disk-cache **for only 3 hours**. The main reason for this is to avoid legal issues | ||||||
| and such when hosting user controlled content. Typically, this is more than enough time for the user (or the phone) to download  | and such when hosting user controlled content. Typically, this is more than enough time for the user (or the auto download  | ||||||
| the file. The following config options are relevant to attachments: | feature) to download the file. The following config options are relevant to attachments: | ||||||
| 
 | 
 | ||||||
| * `base-url` is the root URL for the ntfy server; this is needed for the generated attachment URLs | * `base-url` is the root URL for the ntfy server; this is needed for the generated attachment URLs | ||||||
| * `attachment-cache-dir` is the cache directory for attached files | * `attachment-cache-dir` is the cache directory for attached files | ||||||
|  | @ -356,8 +356,15 @@ request every 10s (defined by `visitor-request-limit-replenish`) | ||||||
| * `visitor-request-limit-replenish` is the rate at which the bucket is refilled (one request per x). Defaults to 10s. | * `visitor-request-limit-replenish` is the rate at which the bucket is refilled (one request per x). Defaults to 10s. | ||||||
| 
 | 
 | ||||||
| ### Attachment limits | ### Attachment limits | ||||||
|  | Aside from the global file size and total attachment cache limits (see [above](#attachments)), there are two relevant  | ||||||
|  | per-visitor limits: | ||||||
| 
 | 
 | ||||||
| XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx | * `visitor-attachment-total-size-limit` is the total storage limit used for attachments per visitor. It defaults to 100M. | ||||||
|  |   The per-visitor storage is automatically decreased as attachments expire. External attachments (attached via `X-Attach`,  | ||||||
|  |   see [publishing docs](publish.md#attachments)) do not count here.  | ||||||
|  | * `visitor-attachment-daily-bandwidth-limit` is the total daily attachment download/upload bandwidth limit per visitor,  | ||||||
|  |   including PUT and GET requests. This is to protect your precious bandwidth from abuse, since egress costs money in | ||||||
|  |   most cloud providers. This defaults to 500M. | ||||||
| 
 | 
 | ||||||
| ### E-mail limits | ### E-mail limits | ||||||
| Similarly to the request limit, there is also an e-mail limit (only relevant if [e-mail notifications](#e-mail-notifications)  | Similarly to the request limit, there is also an e-mail limit (only relevant if [e-mail notifications](#e-mail-notifications)  | ||||||
|  | @ -471,7 +478,7 @@ CLI option (e.g. `--listen-http :80`. Here's a list of all available options. Al | ||||||
| variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`). | variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`). | ||||||
| 
 | 
 | ||||||
| | Config option                              | Env variable                                    | Format           | Default | Description                                                                                                                                                                                                                     | | | Config option                              | Env variable                                    | Format           | Default | Description                                                                                                                                                                                                                     | | ||||||
| |---|---|---|---|---| | |--------------------------------------------|-------------------------------------------------|------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ||||||
| | `base-url`                                 | `NTFY_BASE_URL`                                 | *URL*            | -       | Public facing base URL of the service (e.g. `https://ntfy.sh`)                                                                                                                                                                  | | | `base-url`                                 | `NTFY_BASE_URL`                                 | *URL*            | -       | Public facing base URL of the service (e.g. `https://ntfy.sh`)                                                                                                                                                                  | | ||||||
| | `listen-http`                              | `NTFY_LISTEN_HTTP`                              | `[host]:port`    | `:80`   | Listen address for the HTTP web server                                                                                                                                                                                          | | | `listen-http`                              | `NTFY_LISTEN_HTTP`                              | `[host]:port`    | `:80`   | Listen address for the HTTP web server                                                                                                                                                                                          | | ||||||
| | `listen-https`                             | `NTFY_LISTEN_HTTPS`                             | `[host]:port`    | -       | Listen address for the HTTPS web server. If set, you also need to set `key-file` and `cert-file`.                                                                                                                               | | | `listen-https`                             | `NTFY_LISTEN_HTTPS`                             | `[host]:port`    | -       | Listen address for the HTTPS web server. If set, you also need to set `key-file` and `cert-file`.                                                                                                                               | | ||||||
|  | @ -480,12 +487,11 @@ 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).                        | | | `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-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.                                        | | | `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-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-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-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`.                                                                                               | | | `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-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-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                                                                                                                                                                           | | | `smtp-sender-pass`                         | `NTFY_SMTP_SENDER_PASS`                         | *string*         | -       | SMTP password; only used if e-mail sending is enabled                                                                                                                                                                           | | ||||||
|  | @ -493,15 +499,16 @@ 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-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-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-`                                                                                                                                                          | | | `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.                                                                                                                                                     | | | `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-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-total-size-limit`      | `NTFY_VISITOR_ATTACHMENT_TOTAL_SIZE_LIMIT`      | *size*           | 100M    | Rate limiting: 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-attachment-daily-bandwidth-limit` | `NTFY_VISITOR_ATTACHMENT_DAILY_BANDWIDTH_LIMIT` | *size*           | 500M    | Rate limiting: 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-burst`              | `NTFY_VISITOR_REQUEST_LIMIT_BURST`              | *number*         | 60      | Rate limiting: 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-request-limit-replenish`          | `NTFY_VISITOR_REQUEST_LIMIT_REPLENISH`          | *duration*       | 10s     | Rate limiting: 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      | Rate limiting: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 | | | `visitor-email-limit-replenish`            | `NTFY_VISITOR_EMAIL_LIMIT_REPLENISH`            | *duration*       | 1h      | Rate limiting: Strongly related to `visitor-email-limit-burst`: The rate at which the bucket is refilled                                                                                                                        | | ||||||
| | `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: `<number>(smh)`, e.g. 30s, 20m or 1h.    | The format for a *duration* is: `<number>(smh)`, e.g. 30s, 20m or 1h.    | ||||||
| The format for a *size* is: `<number>(GMK)`, e.g. 1G, 200M or 4000k. | The format for a *size* is: `<number>(GMK)`, e.g. 1G, 200M or 4000k. | ||||||
|  |  | ||||||
							
								
								
									
										150
									
								
								docs/publish.md
									
										
									
									
									
								
							
							
						
						
									
										150
									
								
								docs/publish.md
									
										
									
									
									
								
							|  | @ -659,26 +659,33 @@ Here's an example that will open Reddit when the notification is clicked: | ||||||
|     ])); |     ])); | ||||||
|     ``` |     ``` | ||||||
| 
 | 
 | ||||||
| ## Attachments (send files) | ## Attachments | ||||||
| You can send images and other files to your phone as attachments to a notification. The attachments are then downloaded | 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. | 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.   | There are two different ways to send attachments:  | ||||||
| 
 | 
 | ||||||
| **Upload attachments from your computer**: To send an attachment from your computer as a file, you can send it as the  | * sending [a local file](#attach-local-file) via PUT, e.g. from `~/Flowers/flower.jpg` or `ringtone.mp3` | ||||||
| PUT request body. If a message is greater than the maximum message size or consists of non-UTF-8 characters, the ntfy  | * or by [passing an external URL](#attach-file-from-a-url) as an attachment, e.g. `https://f-droid.org/F-Droid.apk`  | ||||||
| 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 | ### Attach local file | ||||||
| or query parameter (or any of its aliases `Filename`, `File` or `f`).  | 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 (4,096 bytes) 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. To send smaller text-only messages or files  | ||||||
|  | as attachments, you must pass a filename by passing the `X-Filename` header or query parameter (or any of its aliases  | ||||||
|  | `Filename`, `File` or `f`).  | ||||||
|  | 
 | ||||||
|  | By default, and how ntfy.sh is configured, the **max attachment size is 15 MB** (with 100 MB total per visitor).  | ||||||
|  | Attachments **expire after 3 hours**, which typically is plenty of time for the user to download it, or for the Android app | ||||||
|  | to auto-download it. Please also check out the [other limits below](#limitations). | ||||||
| 
 | 
 | ||||||
| Here's an example showing how to upload an image: | Here's an example showing how to upload an image: | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| === "Command line (curl)" | === "Command line (curl)" | ||||||
|     ``` |     ``` | ||||||
|     curl \ |     curl \ | ||||||
|         -T flower.jpg \ |         -T flower.jpg \ | ||||||
|  |         -H "Filename: flower.jpg" \ | ||||||
|         ntfy.sh/flowers |         ntfy.sh/flowers | ||||||
|     ``` |     ``` | ||||||
| 
 | 
 | ||||||
|  | @ -693,6 +700,7 @@ Here's an example showing how to upload an image: | ||||||
|     ``` http |     ``` http | ||||||
|     PUT /flowers HTTP/1.1 |     PUT /flowers HTTP/1.1 | ||||||
|     Host: ntfy.sh |     Host: ntfy.sh | ||||||
|  |     Filename: flower.jpg | ||||||
| 
 | 
 | ||||||
|     <binary JPEG data> |     <binary JPEG data> | ||||||
|     ``` |     ``` | ||||||
|  | @ -701,7 +709,8 @@ Here's an example showing how to upload an image: | ||||||
|     ``` javascript |     ``` javascript | ||||||
|     fetch('https://ntfy.sh/flowers', { |     fetch('https://ntfy.sh/flowers', { | ||||||
|         method: 'PUT', |         method: 'PUT', | ||||||
|         body: document.getElementById("file").files[0] |         body: document.getElementById("file").files[0], | ||||||
|  |         headers: { 'Filename': 'flower.jpg' } | ||||||
|     }) |     }) | ||||||
|     ``` |     ``` | ||||||
| 
 | 
 | ||||||
|  | @ -709,45 +718,109 @@ Here's an example showing how to upload an image: | ||||||
|     ``` go |     ``` go | ||||||
|     file, _ := os.Open("flower.jpg") |     file, _ := os.Open("flower.jpg") | ||||||
|     req, _ := http.NewRequest("PUT", "https://ntfy.sh/flowers", file) |     req, _ := http.NewRequest("PUT", "https://ntfy.sh/flowers", file) | ||||||
|  |     req.Header.Set("Filename", "flower.jpg") | ||||||
|     http.DefaultClient.Do(req) |     http.DefaultClient.Do(req) | ||||||
|     ``` |     ``` | ||||||
| 
 | 
 | ||||||
| === "Python" | === "Python" | ||||||
|     ``` python |     ``` python | ||||||
|     requests.put("https://ntfy.sh/flowers", |     requests.put("https://ntfy.sh/flowers", | ||||||
|         data=open("flower.jpg", 'rb')) |         data=open("flower.jpg", 'rb'), | ||||||
|  |         headers={ "Filename": "flower.jpg" }) | ||||||
|     ``` |     ``` | ||||||
| 
 | 
 | ||||||
| === "PHP" | === "PHP" | ||||||
|     ``` php-inline |     ``` php-inline | ||||||
|     file_get_contents('https://ntfy.sh/reddit_alerts', false, stream_context_create([ |     file_get_contents('https://ntfy.sh/flowers', false, stream_context_create([ | ||||||
|         'http' => [ |         'http' => [ | ||||||
|             'method' => 'PUT', |             'method' => 'PUT', | ||||||
|             'content' => XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXxx  |             'header' => | ||||||
|  |                 "Content-Type: application/octet-stream\r\n" . // Does not matter | ||||||
|  |                 "Filename: flower.jpg", | ||||||
|  |             'content' => file_get_contents('flower.jpg') // Dangerous for large files  | ||||||
|         ] |         ] | ||||||
|     ])); |     ])); | ||||||
|     ``` |     ``` | ||||||
| 
 | 
 | ||||||
|  | Here's what that looks like on Android: | ||||||
|  | 
 | ||||||
|  | <figure markdown> | ||||||
|  |   { width=500 } | ||||||
|  |   <figcaption>Image attachment sent from a local file</figcaption> | ||||||
|  | </figure> | ||||||
|  | 
 | ||||||
|  | ### Attach file from a URL | ||||||
|  | Instead of sending a local file to your phone, you can use an external URL to specify where the attachment is hosted. | ||||||
|  | This could be a Google Drive or Dropbox link, or any other publicly available URL. The ntfy server will briefly probe | ||||||
|  | the URL to retrieve type and size for you. Since the files are externally hosted, the expiration or size limits from  | ||||||
|  | above do not apply here. | ||||||
|  | 
 | ||||||
|  | To attach an external file, simple pass the `X-Attach` header or query parameter (or any of its aliases `Attach` or `a`) | ||||||
|  | to specify the attachment URL. It can be any type of file. | ||||||
|  | 
 | ||||||
|  | Here's an example showing how to upload an image: | ||||||
|  | 
 | ||||||
|  | === "Command line (curl)" | ||||||
|     ``` |     ``` | ||||||
| - Uploaded attachment |  | ||||||
| - External attachment |  | ||||||
| - Preview without attachment  |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| # Upload and send attachment with custom message and filename |  | ||||||
|     curl \ |     curl \ | ||||||
|     -T flower.jpg \ |         -X POST \ | ||||||
|     -H "Message: Here's a flower for you" \ |         -H "Attach: https://f-droid.org/F-Droid.apk" \ | ||||||
|     -H "Filename: flower.jpg" \ |         ntfy.sh/mydownloads | ||||||
|     ntfy.sh/howdy |  | ||||||
| 
 |  | ||||||
| # Send external attachment from other URL, with custom message  |  | ||||||
| curl \ |  | ||||||
|     -H "Attachment: https://example.com/files.zip" \ |  | ||||||
|     "ntfy.sh/howdy?m=Important+documents+attached" |  | ||||||
| 
 |  | ||||||
|     ``` |     ``` | ||||||
| 
 | 
 | ||||||
|  | === "ntfy CLI" | ||||||
|  |     ``` | ||||||
|  |     ntfy publish \ | ||||||
|  |         --attach="https://f-droid.org/F-Droid.apk" \ | ||||||
|  |         mydownloads | ||||||
|  |     ``` | ||||||
|  | 
 | ||||||
|  | === "HTTP" | ||||||
|  |     ``` http | ||||||
|  |     POST /mydownloads HTTP/1.1 | ||||||
|  |     Host: ntfy.sh | ||||||
|  |     Attach: https://f-droid.org/F-Droid.apk | ||||||
|  |     ``` | ||||||
|  | 
 | ||||||
|  | === "JavaScript" | ||||||
|  |     ``` javascript | ||||||
|  |     fetch('https://ntfy.sh/mydownloads', { | ||||||
|  |         method: 'POST', | ||||||
|  |         headers: { 'Attach': 'https://f-droid.org/F-Droid.apk' } | ||||||
|  |     }) | ||||||
|  |     ``` | ||||||
|  | 
 | ||||||
|  | === "Go" | ||||||
|  |     ``` go | ||||||
|  |     req, _ := http.NewRequest("POST", "https://ntfy.sh/mydownloads", file) | ||||||
|  |     req.Header.Set("Attach", "https://f-droid.org/F-Droid.apk") | ||||||
|  |     http.DefaultClient.Do(req) | ||||||
|  |     ``` | ||||||
|  | 
 | ||||||
|  | === "Python" | ||||||
|  |     ``` python | ||||||
|  |     requests.put("https://ntfy.sh/mydownloads", | ||||||
|  |         headers={ "Attach": "https://f-droid.org/F-Droid.apk" }) | ||||||
|  |     ``` | ||||||
|  | 
 | ||||||
|  | === "PHP" | ||||||
|  |     ``` php-inline | ||||||
|  |     file_get_contents('https://ntfy.sh/mydownloads', false, stream_context_create([ | ||||||
|  |         'http' => [ | ||||||
|  |         'method' => 'PUT', | ||||||
|  |         'header' => | ||||||
|  |             "Content-Type: text/plain\r\n" . // Does not matter | ||||||
|  |             "Attach: https://f-droid.org/F-Droid.apk", | ||||||
|  |         ] | ||||||
|  |     ])); | ||||||
|  |     ``` | ||||||
|  | 
 | ||||||
|  | <figure markdown> | ||||||
|  |   { width=500 } | ||||||
|  |   <figcaption>File attachment sent from an external URL</figcaption> | ||||||
|  | </figure> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| ## E-mail notifications | ## E-mail notifications | ||||||
| You can forward messages to e-mail by specifying an address in the header. This can be useful for messages that  | You can forward messages to e-mail by specifying an address in the header. This can be useful for messages that  | ||||||
| you'd like to persist longer, or to blast-notify yourself on all possible channels.  | you'd like to persist longer, or to blast-notify yourself on all possible channels.  | ||||||
|  | @ -1029,16 +1102,19 @@ parameter (or any of its aliases `unifiedpush` or `up`) to `1` to [disable Fireb | ||||||
| option is equivalent to `Firebase: no`, but was introduced to allow future flexibility. | option is equivalent to `Firebase: no`, but was introduced to allow future flexibility. | ||||||
| 
 | 
 | ||||||
| ## Limitations | ## 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, | There are a few limitations to the API to prevent abuse and to keep the server healthy. Almost all of these settings  | ||||||
|  | are configurable via the server side [rate limiting settings](config.md#rate-limiting). Most of these limits you won't run into, | ||||||
| but just in case, let's list them all: | but just in case, let's list them all: | ||||||
| 
 | 
 | ||||||
| | Limit                     | Description                                                                                                                                                               | | | Limit                     | Description                                                                                                                                                               | | ||||||
| |---|---| | |---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ||||||
| | **Message length** | Each message can be up to 4,096 bytes long. Longer messages are truncated. | | | **Message length**        | Each message can be up to 4,096 bytes long. Longer messages are treated as [attachments](#attachments).                                                                   | | ||||||
| | **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. | | | **Requests**              | By default, the server is configured to allow 60 requests per visitor at once, and then refills the your allowed requests bucket at a rate of one request per 10 seconds. | | ||||||
| | **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. | | | **E-mails**               | By default, the server is configured to allow sending 16 e-mails per visitor at once, and then refills the your allowed e-mail bucket at a rate of one per hour.          | | ||||||
| | **Subscription limits** | By default, the server allows each visitor to keep 30 connections to the server open. | | | **Subscription limit**    | By default, the server allows each visitor to keep 30 connections to the server open.                                                                                     | | ||||||
| | **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. | | | **Attachment size limit** | By default, the server allows attachments up to 15 MB in size, up to 100 MB in total per visitor and up to 5 GB across all visitors.                                      | | ||||||
|  | | **Attachment expiry**     | By default, the server deletes attachments after 3 hours and thereby frees up space from the total visitor attachment limit.                                              | | ||||||
|  | | **Attachment 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.                                                                 | | | **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 | ## List of all parameters | ||||||
|  | @ -1053,8 +1129,8 @@ 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-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-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-Click` | `Click` | URL to open when [notification is clicked](#click-action) | | ||||||
| | `X-Attach` | `Attach`, `a` | URL to send as an [attachment](#attachments-send-files), as an alternative to PUT/POST-ing an attachment | | | `X-Attach` | `Attach`, `a` | URL to send as an [attachment](#attachments), 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-Filename` | `Filename`, `file`, `f` | Optional [attachment](#attachments) 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-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-Cache` | `Cache` | Allows disabling [message caching](#message-caching) | | ||||||
| | `X-Firebase` | `Firebase` | Allows disabling [sending to Firebase](#disable-firebase) | | | `X-Firebase` | `Firebase` | Allows disabling [sending to Firebase](#disable-firebase) | | ||||||
|  |  | ||||||
							
								
								
									
										
											BIN
										
									
								
								docs/static/img/android-screenshot-attachment-file.png
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/static/img/android-screenshot-attachment-file.png
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 52 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/static/img/android-screenshot-attachment-image.png
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/static/img/android-screenshot-attachment-image.png
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 156 KiB | 
|  | @ -131,7 +131,8 @@ func (c *memCache) AttachmentsSize(owner string) (int64, error) { | ||||||
| 	var size int64 | 	var size int64 | ||||||
| 	for topic := range c.messages { | 	for topic := range c.messages { | ||||||
| 		for _, m := range c.messages[topic] { | 		for _, m := range c.messages[topic] { | ||||||
| 			if m.Attachment != nil && m.Attachment.Owner == owner { | 			counted := m.Attachment != nil && m.Attachment.Owner == owner && m.Attachment.Expires > time.Now().Unix() | ||||||
|  | 			if counted { | ||||||
| 				size += m.Attachment.Size | 				size += m.Attachment.Size | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -25,6 +25,10 @@ func TestMemCache_Prune(t *testing.T) { | ||||||
| 	testCachePrune(t, newMemCache()) | 	testCachePrune(t, newMemCache()) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func TestMemCache_Attachments(t *testing.T) { | ||||||
|  | 	testCacheAttachments(t, newMemCache()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func TestMemCache_NopCache(t *testing.T) { | func TestMemCache_NopCache(t *testing.T) { | ||||||
| 	c := newNopCache() | 	c := newNopCache() | ||||||
| 	assert.Nil(t, c.AddMessage(newDefaultMessage("mytopic", "my message"))) | 	assert.Nil(t, c.AddMessage(newDefaultMessage("mytopic", "my message"))) | ||||||
|  |  | ||||||
|  | @ -29,6 +29,10 @@ func TestSqliteCache_Prune(t *testing.T) { | ||||||
| 	testCachePrune(t, newSqliteTestCache(t)) | 	testCachePrune(t, newSqliteTestCache(t)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func TestSqliteCache_Attachments(t *testing.T) { | ||||||
|  | 	testCacheAttachments(t, newSqliteTestCache(t)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func TestSqliteCache_Migration_From0(t *testing.T) { | func TestSqliteCache_Migration_From0(t *testing.T) { | ||||||
| 	filename := newSqliteTestCacheFile(t) | 	filename := newSqliteTestCacheFile(t) | ||||||
| 	db, err := sql.Open("sqlite3", filename) | 	db, err := sql.Open("sqlite3", filename) | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| package server | package server | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/require" | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
|  | @ -13,71 +13,71 @@ func testCacheMessages(t *testing.T, c cache) { | ||||||
| 	m2 := newDefaultMessage("mytopic", "my other message") | 	m2 := newDefaultMessage("mytopic", "my other message") | ||||||
| 	m2.Time = 2 | 	m2.Time = 2 | ||||||
| 
 | 
 | ||||||
| 	assert.Nil(t, c.AddMessage(m1)) | 	require.Nil(t, c.AddMessage(m1)) | ||||||
| 	assert.Nil(t, c.AddMessage(newDefaultMessage("example", "my example message"))) | 	require.Nil(t, c.AddMessage(newDefaultMessage("example", "my example message"))) | ||||||
| 	assert.Nil(t, c.AddMessage(m2)) | 	require.Nil(t, c.AddMessage(m2)) | ||||||
| 
 | 
 | ||||||
| 	// Adding invalid | 	// Adding invalid | ||||||
| 	assert.Equal(t, errUnexpectedMessageType, c.AddMessage(newKeepaliveMessage("mytopic"))) // These should not be added! | 	require.Equal(t, errUnexpectedMessageType, c.AddMessage(newKeepaliveMessage("mytopic"))) // These should not be added! | ||||||
| 	assert.Equal(t, errUnexpectedMessageType, c.AddMessage(newOpenMessage("example")))      // These should not be added! | 	require.Equal(t, errUnexpectedMessageType, c.AddMessage(newOpenMessage("example")))      // These should not be added! | ||||||
| 
 | 
 | ||||||
| 	// mytopic: count | 	// mytopic: count | ||||||
| 	count, err := c.MessageCount("mytopic") | 	count, err := c.MessageCount("mytopic") | ||||||
| 	assert.Nil(t, err) | 	require.Nil(t, err) | ||||||
| 	assert.Equal(t, 2, count) | 	require.Equal(t, 2, count) | ||||||
| 
 | 
 | ||||||
| 	// mytopic: since all | 	// mytopic: since all | ||||||
| 	messages, _ := c.Messages("mytopic", sinceAllMessages, false) | 	messages, _ := c.Messages("mytopic", sinceAllMessages, false) | ||||||
| 	assert.Equal(t, 2, len(messages)) | 	require.Equal(t, 2, len(messages)) | ||||||
| 	assert.Equal(t, "my message", messages[0].Message) | 	require.Equal(t, "my message", messages[0].Message) | ||||||
| 	assert.Equal(t, "mytopic", messages[0].Topic) | 	require.Equal(t, "mytopic", messages[0].Topic) | ||||||
| 	assert.Equal(t, messageEvent, messages[0].Event) | 	require.Equal(t, messageEvent, messages[0].Event) | ||||||
| 	assert.Equal(t, "", messages[0].Title) | 	require.Equal(t, "", messages[0].Title) | ||||||
| 	assert.Equal(t, 0, messages[0].Priority) | 	require.Equal(t, 0, messages[0].Priority) | ||||||
| 	assert.Nil(t, messages[0].Tags) | 	require.Nil(t, messages[0].Tags) | ||||||
| 	assert.Equal(t, "my other message", messages[1].Message) | 	require.Equal(t, "my other message", messages[1].Message) | ||||||
| 
 | 
 | ||||||
| 	// mytopic: since none | 	// mytopic: since none | ||||||
| 	messages, _ = c.Messages("mytopic", sinceNoMessages, false) | 	messages, _ = c.Messages("mytopic", sinceNoMessages, false) | ||||||
| 	assert.Empty(t, messages) | 	require.Empty(t, messages) | ||||||
| 
 | 
 | ||||||
| 	// mytopic: since 2 | 	// mytopic: since 2 | ||||||
| 	messages, _ = c.Messages("mytopic", sinceTime(time.Unix(2, 0)), false) | 	messages, _ = c.Messages("mytopic", sinceTime(time.Unix(2, 0)), false) | ||||||
| 	assert.Equal(t, 1, len(messages)) | 	require.Equal(t, 1, len(messages)) | ||||||
| 	assert.Equal(t, "my other message", messages[0].Message) | 	require.Equal(t, "my other message", messages[0].Message) | ||||||
| 
 | 
 | ||||||
| 	// example: count | 	// example: count | ||||||
| 	count, err = c.MessageCount("example") | 	count, err = c.MessageCount("example") | ||||||
| 	assert.Nil(t, err) | 	require.Nil(t, err) | ||||||
| 	assert.Equal(t, 1, count) | 	require.Equal(t, 1, count) | ||||||
| 
 | 
 | ||||||
| 	// example: since all | 	// example: since all | ||||||
| 	messages, _ = c.Messages("example", sinceAllMessages, false) | 	messages, _ = c.Messages("example", sinceAllMessages, false) | ||||||
| 	assert.Equal(t, "my example message", messages[0].Message) | 	require.Equal(t, "my example message", messages[0].Message) | ||||||
| 
 | 
 | ||||||
| 	// non-existing: count | 	// non-existing: count | ||||||
| 	count, err = c.MessageCount("doesnotexist") | 	count, err = c.MessageCount("doesnotexist") | ||||||
| 	assert.Nil(t, err) | 	require.Nil(t, err) | ||||||
| 	assert.Equal(t, 0, count) | 	require.Equal(t, 0, count) | ||||||
| 
 | 
 | ||||||
| 	// non-existing: since all | 	// non-existing: since all | ||||||
| 	messages, _ = c.Messages("doesnotexist", sinceAllMessages, false) | 	messages, _ = c.Messages("doesnotexist", sinceAllMessages, false) | ||||||
| 	assert.Empty(t, messages) | 	require.Empty(t, messages) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func testCacheTopics(t *testing.T, c cache) { | func testCacheTopics(t *testing.T, c cache) { | ||||||
| 	assert.Nil(t, c.AddMessage(newDefaultMessage("topic1", "my example message"))) | 	require.Nil(t, c.AddMessage(newDefaultMessage("topic1", "my example message"))) | ||||||
| 	assert.Nil(t, c.AddMessage(newDefaultMessage("topic2", "message 1"))) | 	require.Nil(t, c.AddMessage(newDefaultMessage("topic2", "message 1"))) | ||||||
| 	assert.Nil(t, c.AddMessage(newDefaultMessage("topic2", "message 2"))) | 	require.Nil(t, c.AddMessage(newDefaultMessage("topic2", "message 2"))) | ||||||
| 	assert.Nil(t, c.AddMessage(newDefaultMessage("topic2", "message 3"))) | 	require.Nil(t, c.AddMessage(newDefaultMessage("topic2", "message 3"))) | ||||||
| 
 | 
 | ||||||
| 	topics, err := c.Topics() | 	topics, err := c.Topics() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	assert.Equal(t, 2, len(topics)) | 	require.Equal(t, 2, len(topics)) | ||||||
| 	assert.Equal(t, "topic1", topics["topic1"].ID) | 	require.Equal(t, "topic1", topics["topic1"].ID) | ||||||
| 	assert.Equal(t, "topic2", topics["topic2"].ID) | 	require.Equal(t, "topic2", topics["topic2"].ID) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func testCachePrune(t *testing.T, c cache) { | func testCachePrune(t *testing.T, c cache) { | ||||||
|  | @ -90,23 +90,23 @@ func testCachePrune(t *testing.T, c cache) { | ||||||
| 	m3 := newDefaultMessage("another_topic", "and another one") | 	m3 := newDefaultMessage("another_topic", "and another one") | ||||||
| 	m3.Time = 1 | 	m3.Time = 1 | ||||||
| 
 | 
 | ||||||
| 	assert.Nil(t, c.AddMessage(m1)) | 	require.Nil(t, c.AddMessage(m1)) | ||||||
| 	assert.Nil(t, c.AddMessage(m2)) | 	require.Nil(t, c.AddMessage(m2)) | ||||||
| 	assert.Nil(t, c.AddMessage(m3)) | 	require.Nil(t, c.AddMessage(m3)) | ||||||
| 	assert.Nil(t, c.Prune(time.Unix(2, 0))) | 	require.Nil(t, c.Prune(time.Unix(2, 0))) | ||||||
| 
 | 
 | ||||||
| 	count, err := c.MessageCount("mytopic") | 	count, err := c.MessageCount("mytopic") | ||||||
| 	assert.Nil(t, err) | 	require.Nil(t, err) | ||||||
| 	assert.Equal(t, 1, count) | 	require.Equal(t, 1, count) | ||||||
| 
 | 
 | ||||||
| 	count, err = c.MessageCount("another_topic") | 	count, err = c.MessageCount("another_topic") | ||||||
| 	assert.Nil(t, err) | 	require.Nil(t, err) | ||||||
| 	assert.Equal(t, 0, count) | 	require.Equal(t, 0, count) | ||||||
| 
 | 
 | ||||||
| 	messages, err := c.Messages("mytopic", sinceAllMessages, false) | 	messages, err := c.Messages("mytopic", sinceAllMessages, false) | ||||||
| 	assert.Nil(t, err) | 	require.Nil(t, err) | ||||||
| 	assert.Equal(t, 1, len(messages)) | 	require.Equal(t, 1, len(messages)) | ||||||
| 	assert.Equal(t, "my other message", messages[0].Message) | 	require.Equal(t, "my other message", messages[0].Message) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func testCacheMessagesTagsPrioAndTitle(t *testing.T, c cache) { | func testCacheMessagesTagsPrioAndTitle(t *testing.T, c cache) { | ||||||
|  | @ -114,12 +114,12 @@ func testCacheMessagesTagsPrioAndTitle(t *testing.T, c cache) { | ||||||
| 	m.Tags = []string{"tag1", "tag2"} | 	m.Tags = []string{"tag1", "tag2"} | ||||||
| 	m.Priority = 5 | 	m.Priority = 5 | ||||||
| 	m.Title = "some title" | 	m.Title = "some title" | ||||||
| 	assert.Nil(t, c.AddMessage(m)) | 	require.Nil(t, c.AddMessage(m)) | ||||||
| 
 | 
 | ||||||
| 	messages, _ := c.Messages("mytopic", sinceAllMessages, false) | 	messages, _ := c.Messages("mytopic", sinceAllMessages, false) | ||||||
| 	assert.Equal(t, []string{"tag1", "tag2"}, messages[0].Tags) | 	require.Equal(t, []string{"tag1", "tag2"}, messages[0].Tags) | ||||||
| 	assert.Equal(t, 5, messages[0].Priority) | 	require.Equal(t, 5, messages[0].Priority) | ||||||
| 	assert.Equal(t, "some title", messages[0].Title) | 	require.Equal(t, "some title", messages[0].Title) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func testCacheMessagesScheduled(t *testing.T, c cache) { | func testCacheMessagesScheduled(t *testing.T, c cache) { | ||||||
|  | @ -130,20 +130,93 @@ func testCacheMessagesScheduled(t *testing.T, c cache) { | ||||||
| 	m3.Time = time.Now().Add(time.Minute).Unix() // earlier than m2! | 	m3.Time = time.Now().Add(time.Minute).Unix() // earlier than m2! | ||||||
| 	m4 := newDefaultMessage("mytopic2", "message 4") | 	m4 := newDefaultMessage("mytopic2", "message 4") | ||||||
| 	m4.Time = time.Now().Add(time.Minute).Unix() | 	m4.Time = time.Now().Add(time.Minute).Unix() | ||||||
| 	assert.Nil(t, c.AddMessage(m1)) | 	require.Nil(t, c.AddMessage(m1)) | ||||||
| 	assert.Nil(t, c.AddMessage(m2)) | 	require.Nil(t, c.AddMessage(m2)) | ||||||
| 	assert.Nil(t, c.AddMessage(m3)) | 	require.Nil(t, c.AddMessage(m3)) | ||||||
| 
 | 
 | ||||||
| 	messages, _ := c.Messages("mytopic", sinceAllMessages, false) // exclude scheduled | 	messages, _ := c.Messages("mytopic", sinceAllMessages, false) // exclude scheduled | ||||||
| 	assert.Equal(t, 1, len(messages)) | 	require.Equal(t, 1, len(messages)) | ||||||
| 	assert.Equal(t, "message 1", messages[0].Message) | 	require.Equal(t, "message 1", messages[0].Message) | ||||||
| 
 | 
 | ||||||
| 	messages, _ = c.Messages("mytopic", sinceAllMessages, true) // include scheduled | 	messages, _ = c.Messages("mytopic", sinceAllMessages, true) // include scheduled | ||||||
| 	assert.Equal(t, 3, len(messages)) | 	require.Equal(t, 3, len(messages)) | ||||||
| 	assert.Equal(t, "message 1", messages[0].Message) | 	require.Equal(t, "message 1", messages[0].Message) | ||||||
| 	assert.Equal(t, "message 3", messages[1].Message) // Order! | 	require.Equal(t, "message 3", messages[1].Message) // Order! | ||||||
| 	assert.Equal(t, "message 2", messages[2].Message) | 	require.Equal(t, "message 2", messages[2].Message) | ||||||
| 
 | 
 | ||||||
| 	messages, _ = c.MessagesDue() | 	messages, _ = c.MessagesDue() | ||||||
| 	assert.Empty(t, messages) | 	require.Empty(t, messages) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func testCacheAttachments(t *testing.T, c cache) { | ||||||
|  | 	expires1 := time.Now().Add(-4 * time.Hour).Unix() | ||||||
|  | 	m := newDefaultMessage("mytopic", "flower for you") | ||||||
|  | 	m.ID = "m1" | ||||||
|  | 	m.Attachment = &attachment{ | ||||||
|  | 		Name:    "flower.jpg", | ||||||
|  | 		Type:    "image/jpeg", | ||||||
|  | 		Size:    5000, | ||||||
|  | 		Expires: expires1, | ||||||
|  | 		URL:     "https://ntfy.sh/file/AbDeFgJhal.jpg", | ||||||
|  | 		Owner:   "1.2.3.4", | ||||||
|  | 	} | ||||||
|  | 	require.Nil(t, c.AddMessage(m)) | ||||||
|  | 
 | ||||||
|  | 	expires2 := time.Now().Add(2 * time.Hour).Unix() // Future | ||||||
|  | 	m = newDefaultMessage("mytopic", "sending you a car") | ||||||
|  | 	m.ID = "m2" | ||||||
|  | 	m.Attachment = &attachment{ | ||||||
|  | 		Name:    "car.jpg", | ||||||
|  | 		Type:    "image/jpeg", | ||||||
|  | 		Size:    10000, | ||||||
|  | 		Expires: expires2, | ||||||
|  | 		URL:     "https://ntfy.sh/file/aCaRURL.jpg", | ||||||
|  | 		Owner:   "1.2.3.4", | ||||||
|  | 	} | ||||||
|  | 	require.Nil(t, c.AddMessage(m)) | ||||||
|  | 
 | ||||||
|  | 	expires3 := time.Now().Add(1 * time.Hour).Unix() // Future | ||||||
|  | 	m = newDefaultMessage("another-topic", "sending you another car") | ||||||
|  | 	m.ID = "m3" | ||||||
|  | 	m.Attachment = &attachment{ | ||||||
|  | 		Name:    "another-car.jpg", | ||||||
|  | 		Type:    "image/jpeg", | ||||||
|  | 		Size:    20000, | ||||||
|  | 		Expires: expires3, | ||||||
|  | 		URL:     "https://ntfy.sh/file/zakaDHFW.jpg", | ||||||
|  | 		Owner:   "1.2.3.4", | ||||||
|  | 	} | ||||||
|  | 	require.Nil(t, c.AddMessage(m)) | ||||||
|  | 
 | ||||||
|  | 	messages, err := c.Messages("mytopic", sinceAllMessages, false) | ||||||
|  | 	require.Nil(t, err) | ||||||
|  | 	require.Equal(t, 2, len(messages)) | ||||||
|  | 
 | ||||||
|  | 	require.Equal(t, "flower for you", messages[0].Message) | ||||||
|  | 	require.Equal(t, "flower.jpg", messages[0].Attachment.Name) | ||||||
|  | 	require.Equal(t, "image/jpeg", messages[0].Attachment.Type) | ||||||
|  | 	require.Equal(t, int64(5000), messages[0].Attachment.Size) | ||||||
|  | 	require.Equal(t, expires1, messages[0].Attachment.Expires) | ||||||
|  | 	require.Equal(t, "https://ntfy.sh/file/AbDeFgJhal.jpg", messages[0].Attachment.URL) | ||||||
|  | 	require.Equal(t, "1.2.3.4", messages[0].Attachment.Owner) | ||||||
|  | 
 | ||||||
|  | 	require.Equal(t, "sending you a car", messages[1].Message) | ||||||
|  | 	require.Equal(t, "car.jpg", messages[1].Attachment.Name) | ||||||
|  | 	require.Equal(t, "image/jpeg", messages[1].Attachment.Type) | ||||||
|  | 	require.Equal(t, int64(10000), messages[1].Attachment.Size) | ||||||
|  | 	require.Equal(t, expires2, messages[1].Attachment.Expires) | ||||||
|  | 	require.Equal(t, "https://ntfy.sh/file/aCaRURL.jpg", messages[1].Attachment.URL) | ||||||
|  | 	require.Equal(t, "1.2.3.4", messages[1].Attachment.Owner) | ||||||
|  | 
 | ||||||
|  | 	size, err := c.AttachmentsSize("1.2.3.4") | ||||||
|  | 	require.Nil(t, err) | ||||||
|  | 	require.Equal(t, int64(30000), size) | ||||||
|  | 
 | ||||||
|  | 	size, err = c.AttachmentsSize("5.6.7.8") | ||||||
|  | 	require.Nil(t, err) | ||||||
|  | 	require.Equal(t, int64(0), size) | ||||||
|  | 
 | ||||||
|  | 	ids, err := c.AttachmentsExpired() | ||||||
|  | 	require.Nil(t, err) | ||||||
|  | 	require.Equal(t, []string{"m1"}, ids) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -36,7 +36,7 @@ | ||||||
| # | # | ||||||
| # You can disable the cache entirely by setting this to 0. | # You can disable the cache entirely by setting this to 0. | ||||||
| # | # | ||||||
| # cache-duration: 12h | # cache-duration: "12h" | ||||||
| 
 | 
 | ||||||
| # If set, the X-Forwarded-For header is used to determine the visitor IP address | # If set, the X-Forwarded-For header is used to determine the visitor IP address | ||||||
| # instead of the remote address of the connection. | # instead of the remote address of the connection. | ||||||
|  | @ -46,6 +46,19 @@ | ||||||
| # | # | ||||||
| # behind-proxy: false | # behind-proxy: false | ||||||
| 
 | 
 | ||||||
|  | # If enabled, clients can attach files to notifications as attachments. Minimum settings to enable attachments | ||||||
|  | # are "attachment-cache-dir" and "base-url". | ||||||
|  | # | ||||||
|  | # - attachment-cache-dir is the cache directory for attached files | ||||||
|  | # - attachment-total-size-limit is the limit of the on-disk attachment cache directory (total size) | ||||||
|  | # - attachment-file-size-limit is the per-file attachment size limit (e.g. 300k, 2M, 100M) | ||||||
|  | # - attachment-expiry-duration is the duration after which uploaded attachments will be deleted (e.g. 3h, 20h) | ||||||
|  | # | ||||||
|  | # attachment-cache-dir: | ||||||
|  | # attachment-total-size-limit: "5G" | ||||||
|  | # attachment-file-size-limit: "15M" | ||||||
|  | # attachment-expiry-duration: "3h" | ||||||
|  | 
 | ||||||
| # If enabled, allow outgoing e-mail notifications via the 'X-Email' header. If this header is set, | # If enabled, allow outgoing e-mail notifications via the 'X-Email' header. If this header is set, | ||||||
| # messages will additionally be sent out as e-mail using an external SMTP server. As of today, only | # messages will additionally be sent out as e-mail using an external SMTP server. As of today, only | ||||||
| # SMTP servers with plain text auth and STARTLS are supported. Please also refer to the rate limiting settings | # SMTP servers with plain text auth and STARTLS are supported. Please also refer to the rate limiting settings | ||||||
|  | @ -78,12 +91,12 @@ | ||||||
| # | # | ||||||
| # Note that the Android app has a hardcoded timeout at 77s, so it should be less than that. | # Note that the Android app has a hardcoded timeout at 77s, so it should be less than that. | ||||||
| # | # | ||||||
| # keepalive-interval: 30s | # keepalive-interval: "30s" | ||||||
| 
 | 
 | ||||||
| # Interval in which the manager prunes old messages, deletes topics | # Interval in which the manager prunes old messages, deletes topics | ||||||
| # and prints the stats. | # and prints the stats. | ||||||
| # | # | ||||||
| # manager-interval: 1m | # manager-interval: "1m" | ||||||
| 
 | 
 | ||||||
| # Rate limiting: Total number of topics before the server rejects new topics. | # Rate limiting: Total number of topics before the server rejects new topics. | ||||||
| # | # | ||||||
|  | @ -98,11 +111,18 @@ | ||||||
| # - visitor-request-limit-replenish is the rate at which the bucket is refilled | # - visitor-request-limit-replenish is the rate at which the bucket is refilled | ||||||
| # | # | ||||||
| # visitor-request-limit-burst: 60 | # visitor-request-limit-burst: 60 | ||||||
| # visitor-request-limit-replenish: 10s | # visitor-request-limit-replenish: "10s" | ||||||
| 
 | 
 | ||||||
| # Rate limiting: Allowed emails per visitor: | # Rate limiting: Allowed emails per visitor: | ||||||
| # - visitor-email-limit-burst is the initial bucket of emails each visitor has | # - visitor-email-limit-burst is the initial bucket of emails each visitor has | ||||||
| # - visitor-email-limit-replenish is the rate at which the bucket is refilled | # - visitor-email-limit-replenish is the rate at which the bucket is refilled | ||||||
| # | # | ||||||
| # visitor-email-limit-burst: 16 | # visitor-email-limit-burst: 16 | ||||||
| # visitor-email-limit-replenish: 1h | # visitor-email-limit-replenish: "1h" | ||||||
|  | 
 | ||||||
|  | # Rate limiting: Attachment size and bandwidth limits per visitor: | ||||||
|  | # - visitor-attachment-total-size-limit is the total storage limit used for attachments per visitor | ||||||
|  | # - visitor-attachment-daily-bandwidth-limit is the total daily attachment download/upload traffic limit per visitor | ||||||
|  | # | ||||||
|  | # visitor-attachment-total-size-limit: "100M" | ||||||
|  | # visitor-attachment-daily-bandwidth-limit: "500M" | ||||||
|  |  | ||||||
|  | @ -699,12 +699,21 @@ func TestServer_PublishAttachment(t *testing.T) { | ||||||
| 	require.Equal(t, 200, response.Code) | 	require.Equal(t, 200, response.Code) | ||||||
| 	require.Equal(t, "5000", response.Header().Get("Content-Length")) | 	require.Equal(t, "5000", response.Header().Get("Content-Length")) | ||||||
| 	require.Equal(t, content, response.Body.String()) | 	require.Equal(t, content, response.Body.String()) | ||||||
|  | 
 | ||||||
|  | 	// Slightly unrelated cross-test: make sure we add an owner for internal attachments | ||||||
|  | 	size, err := s.cache.AttachmentsSize("9.9.9.9") // See request() | ||||||
|  | 	require.Nil(t, err) | ||||||
|  | 	require.Equal(t, int64(5000), size) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestServer_PublishAttachmentShortWithFilename(t *testing.T) { | func TestServer_PublishAttachmentShortWithFilename(t *testing.T) { | ||||||
| 	s := newTestServer(t, newTestConfig(t)) | 	c := newTestConfig(t) | ||||||
|  | 	c.BehindProxy = true | ||||||
|  | 	s := newTestServer(t, c) | ||||||
| 	content := "this is an ATTACHMENT" | 	content := "this is an ATTACHMENT" | ||||||
| 	response := request(t, s, "PUT", "/mytopic?f=myfile.txt", content, nil) | 	response := request(t, s, "PUT", "/mytopic?f=myfile.txt", content, map[string]string{ | ||||||
|  | 		"X-Forwarded-For": "1.2.3.4", | ||||||
|  | 	}) | ||||||
| 	msg := toMessage(t, response.Body.String()) | 	msg := toMessage(t, response.Body.String()) | ||||||
| 	require.Equal(t, "myfile.txt", msg.Attachment.Name) | 	require.Equal(t, "myfile.txt", msg.Attachment.Name) | ||||||
| 	require.Equal(t, "text/plain; charset=utf-8", msg.Attachment.Type) | 	require.Equal(t, "text/plain; charset=utf-8", msg.Attachment.Type) | ||||||
|  | @ -719,6 +728,11 @@ func TestServer_PublishAttachmentShortWithFilename(t *testing.T) { | ||||||
| 	require.Equal(t, 200, response.Code) | 	require.Equal(t, 200, response.Code) | ||||||
| 	require.Equal(t, "21", response.Header().Get("Content-Length")) | 	require.Equal(t, "21", response.Header().Get("Content-Length")) | ||||||
| 	require.Equal(t, content, response.Body.String()) | 	require.Equal(t, content, response.Body.String()) | ||||||
|  | 
 | ||||||
|  | 	// Slightly unrelated cross-test: make sure we add an owner for internal attachments | ||||||
|  | 	size, err := s.cache.AttachmentsSize("1.2.3.4") | ||||||
|  | 	require.Nil(t, err) | ||||||
|  | 	require.Equal(t, int64(21), size) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestServer_PublishAttachmentExternalWithoutFilename(t *testing.T) { | func TestServer_PublishAttachmentExternalWithoutFilename(t *testing.T) { | ||||||
|  | @ -734,6 +748,11 @@ func TestServer_PublishAttachmentExternalWithoutFilename(t *testing.T) { | ||||||
| 	require.Equal(t, int64(0), msg.Attachment.Expires) | 	require.Equal(t, int64(0), msg.Attachment.Expires) | ||||||
| 	require.Equal(t, "https://upload.wikimedia.org/wikipedia/commons/f/fd/Pink_flower.jpg", msg.Attachment.URL) | 	require.Equal(t, "https://upload.wikimedia.org/wikipedia/commons/f/fd/Pink_flower.jpg", msg.Attachment.URL) | ||||||
| 	require.Equal(t, "", msg.Attachment.Owner) | 	require.Equal(t, "", msg.Attachment.Owner) | ||||||
|  | 
 | ||||||
|  | 	// Slightly unrelated cross-test: make sure we don't add an owner for external attachments | ||||||
|  | 	size, err := s.cache.AttachmentsSize("127.0.0.1") | ||||||
|  | 	require.Nil(t, err) | ||||||
|  | 	require.Equal(t, int64(0), size) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestServer_PublishAttachmentExternalWithFilename(t *testing.T) { | func TestServer_PublishAttachmentExternalWithFilename(t *testing.T) { | ||||||
|  | @ -914,6 +933,7 @@ func request(t *testing.T, s *Server, method, url, body string, headers map[stri | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|  | 	req.RemoteAddr = "9.9.9.9" // Used for tests | ||||||
| 	for k, v := range headers { | 	for k, v := range headers { | ||||||
| 		req.Header.Set(k, v) | 		req.Header.Set(k, v) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue