Resolve merge conflict: combine PR 1536 media tempdir with PR 1535 size limit
This commit is contained in:
commit
3d300c57bb
7 changed files with 60 additions and 16 deletions
|
|
@ -636,7 +636,9 @@ func (c *FeishuChannel) downloadResource(
|
|||
return ""
|
||||
}
|
||||
|
||||
if _, copyErr := io.Copy(out, resp.File); copyErr != nil {
|
||||
maxSize := int64(utils.MaxMediaDownloadSize)
|
||||
written, copyErr := io.CopyN(out, resp.File, maxSize)
|
||||
if copyErr != nil && copyErr != io.EOF {
|
||||
out.Close()
|
||||
os.Remove(localPath)
|
||||
logger.ErrorCF("feishu", "Failed to write resource to file", map[string]any{
|
||||
|
|
@ -644,6 +646,14 @@ func (c *FeishuChannel) downloadResource(
|
|||
})
|
||||
return ""
|
||||
}
|
||||
if written >= maxSize {
|
||||
out.Close()
|
||||
os.Remove(localPath)
|
||||
logger.ErrorCF("feishu", "Resource exceeds size limit, download aborted", map[string]any{
|
||||
"limit_mb": maxSize / (1 << 20),
|
||||
})
|
||||
return ""
|
||||
}
|
||||
out.Close()
|
||||
|
||||
ref, err := store.Store(localPath, media.MediaMeta{
|
||||
|
|
|
|||
|
|
@ -663,7 +663,7 @@ func (c *LINEChannel) callAPI(ctx context.Context, endpoint string, payload any)
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
respBody, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||
if err != nil {
|
||||
return channels.ClassifySendError(resp.StatusCode, fmt.Errorf("reading LINE API error response: %w", err))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -725,13 +725,15 @@ func (c *MatrixChannel) downloadMedia(
|
|||
reqCtx, cancel := context.WithTimeout(dlCtx, 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
const maxMediaSize = 50 << 20 // 50 MB (PR #1535: prevent disk exhaustion)
|
||||
resp, err := c.client.Download(reqCtx, parsed)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
reader := resp.Body
|
||||
limitedBody := io.LimitReader(resp.Body, maxMediaSize)
|
||||
reader := limitedBody
|
||||
readerClose := func() error { return nil }
|
||||
|
||||
// Encrypted attachments put URL in msgEvt.File and require client-side decryption.
|
||||
|
|
@ -739,7 +741,7 @@ func (c *MatrixChannel) downloadMedia(
|
|||
if err = msgEvt.File.PrepareForDecryption(); err != nil {
|
||||
return "", fmt.Errorf("decrypt matrix media: %w", err)
|
||||
}
|
||||
decryptReader := msgEvt.File.DecryptStream(resp.Body)
|
||||
decryptReader := msgEvt.File.DecryptStream(limitedBody)
|
||||
reader = decryptReader
|
||||
readerClose = decryptReader.Close
|
||||
}
|
||||
|
|
|
|||
|
|
@ -793,7 +793,7 @@ func (c *WeComAIBotChannel) sendViaResponseURL(responseURL, content string) erro
|
|||
return nil
|
||||
}
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
respBody, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading response_url body: %w: %w", channels.ErrTemporary, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -320,8 +320,9 @@ func (c *WeComAppChannel) uploadMedia(ctx context.Context, accessToken, mediaTyp
|
|||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
const maxRespSize = 1 << 20 // 1 MB
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
respBody, readErr := io.ReadAll(resp.Body)
|
||||
respBody, readErr := io.ReadAll(io.LimitReader(resp.Body, maxRespSize))
|
||||
if readErr != nil {
|
||||
return "", channels.ClassifySendError(
|
||||
resp.StatusCode,
|
||||
|
|
@ -379,8 +380,9 @@ func (c *WeComAppChannel) sendWeComMessage(ctx context.Context, accessToken stri
|
|||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
const maxSendRespSize = 1 << 20 // 1 MB
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
respBody, readErr := io.ReadAll(resp.Body)
|
||||
respBody, readErr := io.ReadAll(io.LimitReader(resp.Body, maxSendRespSize))
|
||||
if readErr != nil {
|
||||
return channels.ClassifySendError(
|
||||
resp.StatusCode,
|
||||
|
|
@ -393,7 +395,7 @@ func (c *WeComAppChannel) sendWeComMessage(ctx context.Context, accessToken stri
|
|||
)
|
||||
}
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
respBody, err := io.ReadAll(io.LimitReader(resp.Body, maxSendRespSize))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
|
|
@ -550,13 +552,18 @@ func (c *WeComAppChannel) handleMessageCallback(ctx context.Context, w http.Resp
|
|||
return
|
||||
}
|
||||
|
||||
// Read request body
|
||||
body, err := io.ReadAll(r.Body)
|
||||
// Read request body (limit to 4 MB to prevent memory exhaustion).
|
||||
const maxBodySize = 4 << 20 // 4 MB
|
||||
body, err := io.ReadAll(io.LimitReader(r.Body, maxBodySize+1))
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to read body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
if len(body) > maxBodySize {
|
||||
http.Error(w, "Request body too large", http.StatusRequestEntityTooLarge)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse XML to get encrypted message
|
||||
var encryptedMsg struct {
|
||||
|
|
@ -697,7 +704,7 @@ func (c *WeComAppChannel) refreshAccessToken() error {
|
|||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -253,13 +253,18 @@ func (c *WeComBotChannel) handleMessageCallback(ctx context.Context, w http.Resp
|
|||
return
|
||||
}
|
||||
|
||||
// Read request body
|
||||
body, err := io.ReadAll(r.Body)
|
||||
// Read request body (limit to 4 MB to prevent memory exhaustion).
|
||||
const maxBodySize = 4 << 20 // 4 MB
|
||||
body, err := io.ReadAll(io.LimitReader(r.Body, maxBodySize+1))
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to read body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
if len(body) > maxBodySize {
|
||||
http.Error(w, "Request body too large", http.StatusRequestEntityTooLarge)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse XML to get encrypted message
|
||||
var encryptedMsg struct {
|
||||
|
|
@ -452,8 +457,9 @@ func (c *WeComBotChannel) sendWebhookReply(ctx context.Context, userID, content
|
|||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
const maxRespSize = 1 << 20 // 1 MB
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, readErr := io.ReadAll(resp.Body)
|
||||
body, readErr := io.ReadAll(io.LimitReader(resp.Body, maxRespSize))
|
||||
if readErr != nil {
|
||||
return channels.ClassifySendError(
|
||||
resp.StatusCode,
|
||||
|
|
@ -466,7 +472,7 @@ func (c *WeComBotChannel) sendWebhookReply(ctx context.Context, userID, content
|
|||
)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(io.LimitReader(resp.Body, maxRespSize))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,12 +49,17 @@ func SanitizeFilename(filename string) string {
|
|||
return base
|
||||
}
|
||||
|
||||
// MaxMediaDownloadSize is the upper bound for media file downloads (50 MB).
|
||||
// Prevents disk exhaustion from oversized or malicious attachments.
|
||||
const MaxMediaDownloadSize = 50 << 20
|
||||
|
||||
// DownloadOptions holds optional parameters for downloading files
|
||||
type DownloadOptions struct {
|
||||
Timeout time.Duration
|
||||
ExtraHeaders map[string]string
|
||||
LoggerPrefix string
|
||||
ProxyURL string
|
||||
MaxSize int64 // 0 = use MaxMediaDownloadSize default
|
||||
}
|
||||
|
||||
// DownloadFile downloads a file from URL to a local temp directory.
|
||||
|
|
@ -135,7 +140,12 @@ func DownloadFile(urlStr, filename string, opts DownloadOptions) string {
|
|||
}
|
||||
defer out.Close()
|
||||
|
||||
if _, err := io.Copy(out, resp.Body); err != nil {
|
||||
maxSize := opts.MaxSize
|
||||
if maxSize <= 0 {
|
||||
maxSize = MaxMediaDownloadSize
|
||||
}
|
||||
written, err := io.CopyN(out, resp.Body, maxSize)
|
||||
if err != nil && err != io.EOF {
|
||||
out.Close()
|
||||
os.Remove(localPath)
|
||||
logger.ErrorCF(opts.LoggerPrefix, "Failed to write file", map[string]any{
|
||||
|
|
@ -143,6 +153,15 @@ func DownloadFile(urlStr, filename string, opts DownloadOptions) string {
|
|||
})
|
||||
return ""
|
||||
}
|
||||
// Check if there is more data beyond the limit (oversized file).
|
||||
if written >= maxSize {
|
||||
out.Close()
|
||||
os.Remove(localPath)
|
||||
logger.ErrorCF(opts.LoggerPrefix, "File exceeds size limit, download aborted", map[string]any{
|
||||
"limit_mb": maxSize / (1 << 20),
|
||||
})
|
||||
return ""
|
||||
}
|
||||
|
||||
logger.DebugCF(opts.LoggerPrefix, "File downloaded successfully", map[string]any{
|
||||
"path": localPath,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue