Resolve merge conflict: combine PR 1536 media tempdir with PR 1535 size limit

This commit is contained in:
OpenClaw-User 2026-03-14 16:51:25 +08:00
commit 3d300c57bb
7 changed files with 60 additions and 16 deletions

View file

@ -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{

View file

@ -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))
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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,