mirror of
https://github.com/mudler/LocalAI.git
synced 2026-10-28 17:18:49 -04:00
fix(httpclient): validate redirect targets and pin dialed addresses for user URLs Clients that fetch user-supplied URLs followed redirects without checking the target, so a public host could send them to a loopback, link-local or private address. The host was also resolved twice, once to validate it and once to connect, so a changing DNS answer could bypass the check. Add httpclient.WithExternalOnly. It validates every redirect hop against the external URL policy and, for the default transport, resolves the host, refuses it if any address is non-public, and dials the validated address. Environment proxies are not used for such clients. Move the policy (ValidateExternalURL, IsPublicIP) into pkg/httpclient so it can be used without an import cycle; pkg/utils keeps delegating to it. IsPublicIP now also refuses 100.64.0.0/10, 0.0.0.0/8, 192.0.0.0/24, 198.18.0.0/15, 240.0.0.0/4, multicast, and NAT64 addresses that wrap a non-public IPv4 address. Use the option for the base64, image, audio and video downloads, and drop the video client's own per-hop check. Assisted-by: Claude Code:claude-haiku-5-5 Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
61 lines
1.8 KiB
Go
61 lines
1.8 KiB
Go
package utils
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"io"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/mudler/xlog"
|
|
|
|
"github.com/mudler/LocalAI/pkg/httpclient"
|
|
)
|
|
|
|
var base64DownloadClient = httpclient.NewWithTimeout(30*time.Second, httpclient.WithFollowRedirects(), httpclient.WithExternalOnly())
|
|
|
|
// Match `data:<mime>[;param=value...];base64,` — browser-produced data URIs
|
|
// often carry codec/charset params between the mime type and `;base64,`
|
|
// (e.g. MediaRecorder's `data:audio/webm;codecs=opus;base64,...`). The old
|
|
// `([^;]+)` form only tolerated exactly one segment, so anything with
|
|
// extra params failed the strip and tripped the downstream base64 decoder
|
|
// on the `data:` literal.
|
|
var dataURIPattern = regexp.MustCompile(`^data:[^,]+?;base64,`)
|
|
|
|
// GetContentURIAsBase64 checks if the string is an URL, if it's an URL downloads the content in memory encodes it in base64 and returns the base64 string, otherwise returns the string by stripping base64 data headers
|
|
func GetContentURIAsBase64(s string) (string, error) {
|
|
if strings.HasPrefix(s, "http") || strings.HasPrefix(s, "https") {
|
|
if err := ValidateExternalURL(s); err != nil {
|
|
return "", fmt.Errorf("URL validation failed: %w", err)
|
|
}
|
|
|
|
// download the image
|
|
resp, err := base64DownloadClient.Get(s)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// read the image data into memory
|
|
data, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// encode the image data in base64
|
|
encoded := base64.StdEncoding.EncodeToString(data)
|
|
|
|
// return the base64 string
|
|
return encoded, nil
|
|
}
|
|
|
|
// Match any data URI prefix pattern
|
|
if match := dataURIPattern.FindString(s); match != "" {
|
|
xlog.Debug("Found data URI prefix", "prefix", match)
|
|
return strings.Replace(s, match, "", 1), nil
|
|
}
|
|
|
|
return "", fmt.Errorf("not valid base64 data type string")
|
|
}
|