Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a75f74b471 | ||
|
|
e50779664d |
@@ -26,21 +26,21 @@ deb/rpm packages.
|
||||
|
||||
=== "x86_64/amd64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.12.0/ntfy_1.12.0_linux_x86_64.tar.gz
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.12.1/ntfy_1.12.1_linux_x86_64.tar.gz
|
||||
sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy
|
||||
sudo ./ntfy serve
|
||||
```
|
||||
|
||||
=== "armv7/armhf"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.12.0/ntfy_1.12.0_linux_armv7.tar.gz
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.12.1/ntfy_1.12.1_linux_armv7.tar.gz
|
||||
sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy
|
||||
sudo ./ntfy serve
|
||||
```
|
||||
|
||||
=== "arm64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.12.0/ntfy_1.12.0_linux_arm64.tar.gz
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.12.1/ntfy_1.12.1_linux_arm64.tar.gz
|
||||
sudo tar -C /usr/bin -zxf ntfy_*.tar.gz ntfy
|
||||
sudo ./ntfy serve
|
||||
```
|
||||
@@ -88,7 +88,7 @@ Manually installing the .deb file:
|
||||
|
||||
=== "x86_64/amd64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.12.0/ntfy_1.12.0_linux_amd64.deb
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.12.1/ntfy_1.12.1_linux_amd64.deb
|
||||
sudo dpkg -i ntfy_*.deb
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
@@ -96,7 +96,7 @@ Manually installing the .deb file:
|
||||
|
||||
=== "armv7/armhf"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.12.0/ntfy_1.12.0_linux_armv7.deb
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.12.1/ntfy_1.12.1_linux_armv7.deb
|
||||
sudo dpkg -i ntfy_*.deb
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
@@ -104,7 +104,7 @@ Manually installing the .deb file:
|
||||
|
||||
=== "arm64"
|
||||
```bash
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.12.0/ntfy_1.12.0_linux_arm64.deb
|
||||
wget https://github.com/binwiederhier/ntfy/releases/download/v1.12.1/ntfy_1.12.1_linux_arm64.deb
|
||||
sudo dpkg -i ntfy_*.deb
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
@@ -114,21 +114,21 @@ Manually installing the .deb file:
|
||||
|
||||
=== "x86_64/amd64"
|
||||
```bash
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.12.0/ntfy_1.12.0_linux_amd64.rpm
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.12.1/ntfy_1.12.1_linux_amd64.rpm
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
```
|
||||
|
||||
=== "armv7/armhf"
|
||||
```bash
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.12.0/ntfy_1.12.0_linux_armv7.rpm
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.12.1/ntfy_1.12.1_linux_armv7.rpm
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
```
|
||||
|
||||
=== "arm64"
|
||||
```bash
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.12.0/ntfy_1.12.0_linux_arm64.rpm
|
||||
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v1.12.1/ntfy_1.12.1_linux_arm64.rpm
|
||||
sudo systemctl enable ntfy
|
||||
sudo systemctl start ntfy
|
||||
```
|
||||
|
||||
@@ -752,9 +752,8 @@ Here's what that looks like on Android:
|
||||
|
||||
### 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.
|
||||
This could be a Dropbox link, a file from social media, or any other publicly available URL. 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:
|
||||
|
||||
@@ -18,7 +18,9 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@@ -136,10 +138,8 @@ var (
|
||||
errHTTPBadRequestMessageNotUTF8 = &errHTTP{40011, http.StatusBadRequest, "invalid message: message must be UTF-8 encoded", ""}
|
||||
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", ""}
|
||||
errHTTPBadRequestAttachmentsDisallowed = &errHTTP{40014, http.StatusBadRequest, "invalid request: attachments not allowed", ""}
|
||||
errHTTPBadRequestAttachmentsExpiryBeforeDelivery = &errHTTP{40015, http.StatusBadRequest, "invalid request: attachment expiry before delayed delivery date", ""}
|
||||
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"}
|
||||
@@ -459,9 +459,6 @@ func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, v *visito
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := maybePeakAttachmentURL(m); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.handlePublishBody(r, v, m, body); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -507,19 +504,31 @@ func (s *Server) parsePublishParams(r *http.Request, v *visitor, m *message) (ca
|
||||
firebase = readParam(r, "x-firebase", "firebase") != "no"
|
||||
m.Title = readParam(r, "x-title", "title", "t")
|
||||
m.Click = readParam(r, "x-click", "click")
|
||||
attach := readParam(r, "x-attach", "attach", "a")
|
||||
filename := readParam(r, "x-filename", "filename", "file", "f")
|
||||
attach := readParam(r, "x-attach", "attach", "a")
|
||||
if attach != "" || filename != "" {
|
||||
m.Attachment = &attachment{}
|
||||
}
|
||||
if filename != "" {
|
||||
m.Attachment.Name = filename
|
||||
}
|
||||
if attach != "" {
|
||||
if !attachURLRegex.MatchString(attach) {
|
||||
return false, false, "", errHTTPBadRequestAttachmentURLInvalid
|
||||
}
|
||||
m.Attachment.URL = attach
|
||||
}
|
||||
if filename != "" {
|
||||
m.Attachment.Name = filename
|
||||
if m.Attachment.Name == "" {
|
||||
u, err := url.Parse(m.Attachment.URL)
|
||||
if err == nil {
|
||||
m.Attachment.Name = path.Base(u.Path)
|
||||
if m.Attachment.Name == "." || m.Attachment.Name == "/" {
|
||||
m.Attachment.Name = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
if m.Attachment.Name == "" {
|
||||
m.Attachment.Name = "attachment"
|
||||
}
|
||||
}
|
||||
email = readParam(r, "x-email", "x-e-mail", "email", "e-mail", "mail", "e")
|
||||
if email != "" {
|
||||
|
||||
@@ -743,10 +743,10 @@ func TestServer_PublishAttachmentExternalWithoutFilename(t *testing.T) {
|
||||
msg := toMessage(t, response.Body.String())
|
||||
require.Equal(t, "You received a file: Pink_flower.jpg", msg.Message)
|
||||
require.Equal(t, "Pink_flower.jpg", msg.Attachment.Name)
|
||||
require.Equal(t, "image/jpeg", msg.Attachment.Type)
|
||||
require.Equal(t, int64(190173), msg.Attachment.Size)
|
||||
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, "", msg.Attachment.Type)
|
||||
require.Equal(t, int64(0), msg.Attachment.Size)
|
||||
require.Equal(t, int64(0), msg.Attachment.Expires)
|
||||
require.Equal(t, "", msg.Attachment.Owner)
|
||||
|
||||
// Slightly unrelated cross-test: make sure we don't add an owner for external attachments
|
||||
@@ -764,10 +764,10 @@ func TestServer_PublishAttachmentExternalWithFilename(t *testing.T) {
|
||||
msg := toMessage(t, response.Body.String())
|
||||
require.Equal(t, "This is a custom message", msg.Message)
|
||||
require.Equal(t, "some file.jpg", msg.Attachment.Name)
|
||||
require.Equal(t, "image/jpeg", msg.Attachment.Type)
|
||||
require.Equal(t, int64(190173), msg.Attachment.Size)
|
||||
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, "", msg.Attachment.Type)
|
||||
require.Equal(t, int64(0), msg.Attachment.Size)
|
||||
require.Equal(t, int64(0), msg.Attachment.Expires)
|
||||
require.Equal(t, "", msg.Attachment.Owner)
|
||||
}
|
||||
|
||||
@@ -814,7 +814,7 @@ func TestServer_PublishAttachmentExpiryBeforeDelivery(t *testing.T) {
|
||||
err := toHTTPError(t, response.Body.String())
|
||||
require.Equal(t, 400, response.Code)
|
||||
require.Equal(t, 400, err.HTTPCode)
|
||||
require.Equal(t, 40017, err.Code)
|
||||
require.Equal(t, 40015, err.Code)
|
||||
}
|
||||
|
||||
func TestServer_PublishAttachmentTooLargeBodyVisitorAttachmentTotalSizeLimit(t *testing.T) {
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"heckel.io/ntfy/util"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
peakAttachmentTimeout = 2500 * time.Millisecond
|
||||
peakAttachmentReadBytes = 128
|
||||
)
|
||||
|
||||
func maybePeakAttachmentURL(m *message) error {
|
||||
return maybePeakAttachmentURLInternal(m, peakAttachmentTimeout)
|
||||
}
|
||||
|
||||
func maybePeakAttachmentURLInternal(m *message, timeout time.Duration) error {
|
||||
if m.Attachment == nil || m.Attachment.URL == "" {
|
||||
return nil
|
||||
}
|
||||
client := http.Client{
|
||||
Timeout: timeout,
|
||||
Transport: &http.Transport{
|
||||
DisableCompression: true, // Disable "Accept-Encoding: gzip", otherwise we won't get the Content-Length
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
},
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodGet, m.Attachment.URL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("User-Agent", "ntfy")
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return errHTTPBadRequestAttachmentURLPeakGeneral
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||
return errHTTPBadRequestAttachmentURLPeakNon2xx
|
||||
}
|
||||
if size, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64); err == nil {
|
||||
m.Attachment.Size = size
|
||||
}
|
||||
buf := make([]byte, peakAttachmentReadBytes)
|
||||
io.ReadFull(resp.Body, buf) // Best effort: We don't care about the error
|
||||
mimeType, ext := util.DetectContentType(buf, m.Attachment.URL)
|
||||
m.Attachment.Type = resp.Header.Get("Content-Type")
|
||||
if m.Attachment.Type == "" {
|
||||
m.Attachment.Type = mimeType
|
||||
}
|
||||
if m.Attachment.Name == "" {
|
||||
u, err := url.Parse(m.Attachment.URL)
|
||||
if err != nil {
|
||||
m.Attachment.Name = fmt.Sprintf("attachment%s", ext)
|
||||
} else {
|
||||
m.Attachment.Name = path.Base(u.Path)
|
||||
if m.Attachment.Name == "." || m.Attachment.Name == "/" {
|
||||
m.Attachment.Name = fmt.Sprintf("attachment%s", ext)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMaybePeakAttachmentURL_Success(t *testing.T) {
|
||||
m := &message{
|
||||
Attachment: &attachment{
|
||||
URL: "https://ntfy.sh/static/img/ntfy.png",
|
||||
},
|
||||
}
|
||||
require.Nil(t, maybePeakAttachmentURL(m))
|
||||
require.Equal(t, "ntfy.png", m.Attachment.Name)
|
||||
require.Equal(t, int64(3627), m.Attachment.Size)
|
||||
require.Equal(t, "image/png", m.Attachment.Type)
|
||||
require.Equal(t, int64(0), m.Attachment.Expires)
|
||||
}
|
||||
Reference in New Issue
Block a user