Fix PR review feedback

- Preserve SSRF protection by wrapping the existing transport
  instead of replacing it with a bare http.Transport
- Strip DSN from url.Parse error to avoid leaking credentials
- Gate CommonThirdPartyCombobox on search length to prevent
  showing stale results when input is shortened
- Handle multi-value and uppercase sizes attributes in
  parseSizeAttr for correct icon-size ranking
- Match rel tokens containing "icon" (e.g. "shortcut icon")
  instead of requiring an exact match
- Limit HTML response body to 10 MiB before parsing
- Reject sslmode=prefer explicitly in both import tools

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-05-12 10:12:02 +04:00
parent 6383457e6b
commit 10ac52eff3
5 changed files with 32 additions and 19 deletions

View File

@@ -98,7 +98,7 @@ export function CreateVendorDialog({
<Dialog ref={dialogRef} trigger={children} title={__("Add a vendor")}> <Dialog ref={dialogRef} trigger={children} title={__("Add a vendor")}>
<DialogContent className="p-6"> <DialogContent className="p-6">
<Combobox onSearch={handleSearch} placeholder={__("Type vendor's name")}> <Combobox onSearch={handleSearch} placeholder={__("Type vendor's name")}>
{queryRef && ( {searchQuery.trim().length >= 2 && queryRef && (
<Suspense> <Suspense>
<CommonThirdPartyCombobox <CommonThirdPartyCombobox
queryRef={queryRef} queryRef={queryRef}

View File

@@ -454,7 +454,7 @@ func parseCategory(tp thirdPartyData) coredata.VendorCategory {
func newPgClientFromDSN(dsn string) (*pg.Client, error) { func newPgClientFromDSN(dsn string) (*pg.Client, error) {
u, err := url.Parse(dsn) u, err := url.Parse(dsn)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot parse DSN: %w", err) return nil, fmt.Errorf("cannot parse DSN (check URL format)")
} }
var opts []pg.Option var opts []pg.Option
@@ -462,10 +462,12 @@ func newPgClientFromDSN(dsn string) (*pg.Client, error) {
switch u.Query().Get("sslmode") { switch u.Query().Get("sslmode") {
case "", "disable": case "", "disable":
// plain connection, no TLS // plain connection, no TLS
case "require", "prefer": case "require":
opts = append(opts, pg.WithUnsecureTLS()) opts = append(opts, pg.WithUnsecureTLS())
case "prefer":
return nil, fmt.Errorf("unsupported sslmode %q (prefer fallback semantics are not supported)", u.Query().Get("sslmode"))
default: default:
return nil, fmt.Errorf("unsupported sslmode %q (only disable, require, prefer are supported)", u.Query().Get("sslmode")) return nil, fmt.Errorf("unsupported sslmode %q", u.Query().Get("sslmode"))
} }
if u.Host != "" { if u.Host != "" {

View File

@@ -82,10 +82,12 @@ func newPgClientFromDSN(dsn string) (*pg.Client, error) {
switch u.Query().Get("sslmode") { switch u.Query().Get("sslmode") {
case "", "disable": case "", "disable":
// plain connection, no TLS // plain connection, no TLS
case "require", "prefer": case "require":
opts = append(opts, pg.WithUnsecureTLS()) opts = append(opts, pg.WithUnsecureTLS())
case "prefer":
return nil, fmt.Errorf("unsupported sslmode %q (prefer fallback semantics are not supported)", u.Query().Get("sslmode"))
default: default:
return nil, fmt.Errorf("unsupported sslmode %q (only disable, require, prefer are supported)", u.Query().Get("sslmode")) return nil, fmt.Errorf("unsupported sslmode %q", u.Query().Get("sslmode"))
} }
if u.Host != "" { if u.Host != "" {

View File

@@ -45,7 +45,7 @@ func FindLogoURL(info *PageInfo) (string, error) {
} }
switch { switch {
case rel == "icon" && attrVal(n, "type") == "image/svg+xml": case strings.Contains(rel, "icon") && !strings.Contains(rel, "apple-touch-icon") && attrVal(n, "type") == "image/svg+xml":
svgIcon = href svgIcon = href
case strings.Contains(rel, "apple-touch-icon"): case strings.Contains(rel, "apple-touch-icon"):
size := parseSizeAttr(attrVal(n, "sizes")) size := parseSizeAttr(attrVal(n, "sizes"))
@@ -53,7 +53,7 @@ func FindLogoURL(info *PageInfo) (string, error) {
appleTouchIcon = href appleTouchIcon = href
appleTouchSize = size appleTouchSize = size
} }
case rel == "icon": case strings.Contains(rel, "icon") && !strings.Contains(rel, "apple-touch-icon"):
size := parseSizeAttr(attrVal(n, "sizes")) size := parseSizeAttr(attrVal(n, "sizes"))
if largestIcon == "" || size > largestSize { if largestIcon == "" || size > largestSize {
largestIcon = href largestIcon = href
@@ -87,21 +87,29 @@ func FindLogoURL(info *PageInfo) (string, error) {
} }
func parseSizeAttr(sizes string) int { func parseSizeAttr(sizes string) int {
if sizes == "" || strings.ToLower(sizes) == "any" { if sizes == "" || strings.EqualFold(sizes, "any") {
return 0 return 0
} }
parts := strings.SplitN(sizes, "x", 2) best := 0
if len(parts) == 0 { for _, token := range strings.Fields(sizes) {
return 0 token = strings.ToLower(token)
parts := strings.SplitN(token, "x", 2)
if len(parts) != 2 {
continue
} }
w, err := strconv.Atoi(parts[0]) w, err := strconv.Atoi(parts[0])
if err != nil { if err != nil {
return 0 continue
} }
return w if w > best {
best = w
}
}
return best
} }
func ExtensionForMIME(contentType string) string { func ExtensionForMIME(contentType string) string {

View File

@@ -50,7 +50,8 @@ func Parse(ctx context.Context, client *http.Client, websiteURL string) (*PageIn
return nil, fmt.Errorf("cannot fetch page: status %d", resp.StatusCode) return nil, fmt.Errorf("cannot fetch page: status %d", resp.StatusCode)
} }
return ParseHTML(parsed, resp.Body) const maxHTMLSize = 10 << 20 // 10 MiB
return ParseHTML(parsed, io.LimitReader(resp.Body, maxHTMLSize))
} }
func ParseHTML(baseURL *url.URL, r io.Reader) (*PageInfo, error) { func ParseHTML(baseURL *url.URL, r io.Reader) (*PageInfo, error) {