fix(matrix): add size limit for media downloads
Replace DownloadBytes with streaming Download to avoid loading large files into memory. Add 10MB size limit for downloaded media files. Fixes #1405
This commit is contained in:
parent
95716b106b
commit
252ed37ac3
1 changed files with 54 additions and 11 deletions
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
|
"io"
|
||||||
"mime"
|
"mime"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -458,7 +459,7 @@ func (c *MatrixChannel) SendPlaceholder(ctx context.Context, chatID string) (str
|
||||||
|
|
||||||
text := strings.TrimSpace(c.config.Placeholder.Text)
|
text := strings.TrimSpace(c.config.Placeholder.Text)
|
||||||
if text == "" {
|
if text == "" {
|
||||||
text = "Thinking... 💭"
|
text = "Thinking..."
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := c.client.SendMessageEvent(ctx, roomID, event.EventMessage, &event.MessageEventContent{
|
resp, err := c.client.SendMessageEvent(ctx, roomID, event.EventMessage, &event.MessageEventContent{
|
||||||
|
|
@ -693,6 +694,9 @@ func (c *MatrixChannel) storeMedia(localPath string, meta media.MediaMeta, scope
|
||||||
return localPath
|
return localPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// defaultMaxMediaSize is the default maximum size for downloaded media (10MB).
|
||||||
|
const defaultMaxMediaSize = 10 * 1024 * 1024
|
||||||
|
|
||||||
func (c *MatrixChannel) downloadMedia(
|
func (c *MatrixChannel) downloadMedia(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
msgEvt *event.MessageEventContent,
|
msgEvt *event.MessageEventContent,
|
||||||
|
|
@ -714,17 +718,16 @@ func (c *MatrixChannel) downloadMedia(
|
||||||
reqCtx, cancel := context.WithTimeout(dlCtx, 20*time.Second)
|
reqCtx, cancel := context.WithTimeout(dlCtx, 20*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
data, err := c.client.DownloadBytes(reqCtx, parsed)
|
// Use streaming download instead of DownloadBytes to avoid loading large files into memory
|
||||||
|
resp, err := c.client.Download(reqCtx, parsed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// Encrypted attachments put URL in msgEvt.File and require client-side decryption.
|
// Check Content-Length header if available
|
||||||
if msgEvt != nil && msgEvt.File != nil && msgEvt.URL == "" {
|
if resp.ContentLength > defaultMaxMediaSize {
|
||||||
err = msgEvt.File.DecryptInPlace(data)
|
return "", fmt.Errorf("media file too large: %d bytes (max %d)", resp.ContentLength, defaultMaxMediaSize)
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("decrypt matrix media: %w", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
label := matrixMediaLabel(msgEvt, mediaKind)
|
label := matrixMediaLabel(msgEvt, mediaKind)
|
||||||
|
|
@ -739,10 +742,50 @@ func (c *MatrixChannel) downloadMedia(
|
||||||
}
|
}
|
||||||
defer tmp.Close()
|
defer tmp.Close()
|
||||||
|
|
||||||
if _, err = tmp.Write(data); err != nil {
|
// Stream data to file with size limit
|
||||||
|
var totalBytes int64
|
||||||
|
buf := make([]byte, 32*1024) // 32KB buffer
|
||||||
|
for {
|
||||||
|
n, err := resp.Body.Read(buf)
|
||||||
|
if n > 0 {
|
||||||
|
totalBytes += int64(n)
|
||||||
|
if totalBytes > defaultMaxMediaSize {
|
||||||
|
_ = os.Remove(tmp.Name())
|
||||||
|
return "", fmt.Errorf("media file too large (exceeded %d bytes)", defaultMaxMediaSize)
|
||||||
|
}
|
||||||
|
if _, writeErr := tmp.Write(buf[:n]); writeErr != nil {
|
||||||
|
_ = os.Remove(tmp.Name())
|
||||||
|
return "", writeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
_ = os.Remove(tmp.Name())
|
_ = os.Remove(tmp.Name())
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypted attachments put URL in msgEvt.File and require client-side decryption.
|
||||||
|
if msgEvt != nil && msgEvt.File != nil && msgEvt.URL == "" {
|
||||||
|
// For encrypted files, we need to read and decrypt in memory
|
||||||
|
data, err := os.ReadFile(tmp.Name())
|
||||||
|
if err != nil {
|
||||||
|
_ = os.Remove(tmp.Name())
|
||||||
|
return "", fmt.Errorf("read encrypted file: %w", err)
|
||||||
|
}
|
||||||
|
err = msgEvt.File.DecryptInPlace(data)
|
||||||
|
if err != nil {
|
||||||
|
_ = os.Remove(tmp.Name())
|
||||||
|
return "", fmt.Errorf("decrypt matrix media: %w", err)
|
||||||
|
}
|
||||||
|
// Write decrypted data back
|
||||||
|
if err := os.WriteFile(tmp.Name(), data, 0600); err != nil {
|
||||||
|
_ = os.Remove(tmp.Name())
|
||||||
|
return "", fmt.Errorf("write decrypted file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return tmp.Name(), nil
|
return tmp.Name(), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue