Preview URL

pull/82/head
Philipp Heckel 2022-01-04 19:45:29 +01:00
parent 38788bb2e9
commit 2930c4ff62
5 changed files with 63 additions and 38 deletions

View File

@ -26,6 +26,7 @@ const (
attachment_type TEXT NOT NULL,
attachment_size INT NOT NULL,
attachment_expires INT NOT NULL,
attachment_preview_url TEXT NOT NULL,
attachment_url TEXT NOT NULL,
published INT NOT NULL
);
@ -33,24 +34,24 @@ const (
COMMIT;
`
insertMessageQuery = `
INSERT INTO messages (id, time, topic, message, title, priority, tags, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url, published)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
INSERT INTO messages (id, time, topic, message, title, priority, tags, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_preview_url, attachment_url, published)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`
pruneMessagesQuery = `DELETE FROM messages WHERE time < ? AND published = 1`
selectMessagesSinceTimeQuery = `
SELECT id, time, topic, message, title, priority, tags, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url
SELECT id, time, topic, message, title, priority, tags, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_preview_url, attachment_url
FROM messages
WHERE topic = ? AND time >= ? AND published = 1
ORDER BY time ASC
`
selectMessagesSinceTimeIncludeScheduledQuery = `
SELECT id, time, topic, message, title, priority, tags, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url
SELECT id, time, topic, message, title, priority, tags, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_preview_url, attachment_url
FROM messages
WHERE topic = ? AND time >= ?
ORDER BY time ASC
`
selectMessagesDueQuery = `
SELECT id, time, topic, message, title, priority, tags, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_url
SELECT id, time, topic, message, title, priority, tags, attachment_name, attachment_type, attachment_size, attachment_expires, attachment_preview_url, attachment_url
FROM messages
WHERE time <= ? AND published = 0
`
@ -124,13 +125,14 @@ func (c *sqliteCache) AddMessage(m *message) error {
}
published := m.Time <= time.Now().Unix()
tags := strings.Join(m.Tags, ",")
var attachmentName, attachmentType, attachmentURL string
var attachmentName, attachmentType, attachmentPreviewURL, attachmentURL string
var attachmentSize, attachmentExpires int64
if m.Attachment != nil {
attachmentName = m.Attachment.Name
attachmentType = m.Attachment.Type
attachmentSize = m.Attachment.Size
attachmentExpires = m.Attachment.Expires
attachmentPreviewURL = m.Attachment.PreviewURL
attachmentURL = m.Attachment.URL
}
_, err := c.db.Exec(
@ -146,6 +148,7 @@ func (c *sqliteCache) AddMessage(m *message) error {
attachmentType,
attachmentSize,
attachmentExpires,
attachmentPreviewURL,
attachmentURL,
published,
)
@ -231,8 +234,8 @@ func readMessages(rows *sql.Rows) ([]*message, error) {
for rows.Next() {
var timestamp, attachmentSize, attachmentExpires int64
var priority int
var id, topic, msg, title, tagsStr, attachmentName, attachmentType, attachmentURL string
if err := rows.Scan(&id, &timestamp, &topic, &msg, &title, &priority, &tagsStr, &attachmentName, &attachmentType, &attachmentSize, &attachmentExpires, &attachmentURL); err != nil {
var id, topic, msg, title, tagsStr, attachmentName, attachmentType, attachmentPreviewURL, attachmentURL string
if err := rows.Scan(&id, &timestamp, &topic, &msg, &title, &priority, &tagsStr, &attachmentName, &attachmentType, &attachmentSize, &attachmentExpires, &attachmentPreviewURL, &attachmentURL); err != nil {
return nil, err
}
var tags []string
@ -246,6 +249,7 @@ func readMessages(rows *sql.Rows) ([]*message, error) {
Type: attachmentType,
Size: attachmentSize,
Expires: attachmentExpires,
PreviewURL: attachmentPreviewURL,
URL: attachmentURL,
}
}

View File

@ -13,8 +13,9 @@ const (
DefaultAtSenderInterval = 10 * time.Second
DefaultMinDelay = 10 * time.Second
DefaultMaxDelay = 3 * 24 * time.Hour
DefaultMessageLimit = 4096
DefaultAttachmentSizeLimit = 5 * 1024 * 1024
DefaultMessageLimit = 4096 // Bytes
DefaultAttachmentSizeLimit = 15 * 1024 * 1024
DefaultAttachmentSizePreviewMax = 20 * 1024 * 1024 // Bytes
DefaultAttachmentExpiryDuration = 3 * time.Hour
DefaultFirebaseKeepaliveInterval = 3 * time.Hour // Not too frequently to save battery
)
@ -48,6 +49,7 @@ type Config struct {
CacheDuration time.Duration
AttachmentCacheDir string
AttachmentSizeLimit int64
AttachmentSizePreviewMax int64
AttachmentExpiryDuration time.Duration
KeepaliveInterval time.Duration
ManagerInterval time.Duration
@ -88,6 +90,7 @@ func NewConfig() *Config {
CacheDuration: DefaultCacheDuration,
AttachmentCacheDir: "",
AttachmentSizeLimit: DefaultAttachmentSizeLimit,
AttachmentSizePreviewMax: DefaultAttachmentSizePreviewMax,
AttachmentExpiryDuration: DefaultAttachmentExpiryDuration,
KeepaliveInterval: DefaultKeepaliveInterval,
ManagerInterval: DefaultManagerInterval,

View File

@ -34,6 +34,7 @@ type attachment struct {
Type string `json:"type"`
Size int64 `json:"size"`
Expires int64 `json:"expires"`
PreviewURL string `json:"preview_url"`
URL string `json:"url"`
}

View File

@ -16,7 +16,6 @@ import (
"html/template"
"io"
"log"
"mime"
"net"
"net/http"
"net/http/httptest"
@ -233,6 +232,7 @@ func createFirebaseSubscriber(conf *Config) (subscriber, error) {
data["attachment_type"] = m.Attachment.Type
data["attachment_size"] = fmt.Sprintf("%d", m.Attachment.Size)
data["attachment_expires"] = fmt.Sprintf("%d", m.Attachment.Expires)
data["attachment_preview_url"] = m.Attachment.PreviewURL
data["attachment_url"] = m.Attachment.URL
}
}
@ -326,9 +326,9 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request) error {
} else if r.Method == http.MethodGet && docsRegex.MatchString(r.URL.Path) {
return s.handleDocs(w, r)
} else if r.Method == http.MethodGet && fileRegex.MatchString(r.URL.Path) && s.config.AttachmentCacheDir != "" {
return s.handleFile(w, r)
return s.withRateLimit(w, r, s.handleFile)
} else if r.Method == http.MethodGet && previewRegex.MatchString(r.URL.Path) && s.config.AttachmentCacheDir != "" {
return s.handlePreview(w, r)
return s.withRateLimit(w, r, s.handlePreview)
} else if r.Method == http.MethodOptions {
return s.handleOptions(w, r)
} else if r.Method == http.MethodGet && topicPathRegex.MatchString(r.URL.Path) {
@ -384,7 +384,7 @@ func (s *Server) handleDocs(w http.ResponseWriter, r *http.Request) error {
return nil
}
func (s *Server) handleFile(w http.ResponseWriter, r *http.Request) error {
func (s *Server) handleFile(w http.ResponseWriter, r *http.Request, _ *visitor) error {
if s.config.AttachmentCacheDir == "" {
return errHTTPInternalError
}
@ -408,7 +408,7 @@ func (s *Server) handleFile(w http.ResponseWriter, r *http.Request) error {
return err
}
func (s *Server) handlePreview(w http.ResponseWriter, r *http.Request) error {
func (s *Server) handlePreview(w http.ResponseWriter, r *http.Request, _ *visitor) error {
if s.config.AttachmentCacheDir == "" {
return errHTTPInternalError
}
@ -422,12 +422,12 @@ func (s *Server) handlePreview(w http.ResponseWriter, r *http.Request) error {
if err != nil {
return errHTTPNotFound
}
if stat.Size() > 20*1024*1024 {
return errHTTPInternalError
if stat.Size() > s.config.AttachmentSizePreviewMax {
return errHTTPNotFoundTooLarge
}
img, err := imaging.Open(file)
if err != nil {
return errHTTPNotFoundTooLarge
return err
}
var width, height int
if width >= height {
@ -438,7 +438,7 @@ func (s *Server) handlePreview(w http.ResponseWriter, r *http.Request) error {
width = int(float32(img.Bounds().Dx()) / float32(img.Bounds().Dy()) * float32(height))
}
preview := imaging.Resize(img, width, height, imaging.Lanczos)
return imaging.Encode(w, preview, imaging.PNG)
return imaging.Encode(w, preview, imaging.JPEG, imaging.JPEGQuality(80))
}
func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, v *visitor) error {
@ -575,10 +575,11 @@ func (s *Server) writeAttachment(r *http.Request, v *visitor, m *message, body *
return errHTTPBadRequestInvalidMessage
}
contentType := http.DetectContentType(body.PeakedBytes)
ext := ".bin"
exts, err := mime.ExtensionsByType(contentType)
if err == nil && len(exts) > 0 {
ext = exts[0]
ext := util.ExtensionByType(contentType)
fileURL := fmt.Sprintf("%s/file/%s%s", s.config.BaseURL, m.ID, ext)
previewURL := ""
if strings.HasPrefix(contentType, "image/") {
previewURL = fmt.Sprintf("%s/preview/%s%s", s.config.BaseURL, m.ID, ext)
}
filename := readParam(r, "x-filename", "filename", "file", "f")
if filename == "" {
@ -610,7 +611,8 @@ func (s *Server) writeAttachment(r *http.Request, v *visitor, m *message, body *
Type: contentType,
Size: size,
Expires: time.Now().Add(s.config.AttachmentExpiryDuration).Unix(),
URL: fmt.Sprintf("%s/file/%s%s", s.config.BaseURL, m.ID, ext),
PreviewURL: previewURL,
URL: fileURL,
}
return nil
}

View File

@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"math/rand"
"mime"
"os"
"strings"
"sync"
@ -163,3 +164,17 @@ func ExpandHome(path string) string {
func ShortTopicURL(s string) string {
return strings.TrimPrefix(strings.TrimPrefix(s, "https://"), "http://")
}
// ExtensionByType is a wrapper around mime.ExtensionByType with a few sensible corrections
func ExtensionByType(contentType string) string {
switch contentType {
case "image/jpeg":
return ".jpg"
default:
exts, err := mime.ExtensionsByType(contentType)
if err == nil && len(exts) > 0 {
return exts[0]
}
return ".bin"
}
}