shadow1ng/fscan · error · ErrInvalidURL

%w: empty host (ErrInvalidURL)

Error message

%w: empty host (ErrInvalidURL)

What it means

buildTargetURL parses a target URL and returns a wrapped ErrInvalidURL when the parsed URL has no host component. This happens when info.URL parses successfully (url.Parse rarely errors) but contains no hostname, e.g. a relative path or scheme-only string. The sentinel ErrInvalidURL lets callers check errors.Is(err, ErrInvalidURL).

Source

Thrown at webscan/web_scan.go:115

func buildTargetURL(info *common.HostInfo) (string, error) {
	// 自动构建URL
	if info.URL == "" {
		protocol := protocolHTTP
		if isTLSPort(info.Port) {
			protocol = protocolHTTPS
		}
		info.URL = protocol + net.JoinHostPort(info.Host, fmt.Sprint(info.Port))
	} else if !hasProtocolPrefix(info.URL) {
		info.URL = protocolHTTP + normalizeSchemelessWebTarget(info.URL)
	}

	// 解析URL以提取基础部分
	parsedURL, err := url.Parse(info.URL)
	if err != nil {
		return "", fmt.Errorf("%w: %w", ErrInvalidURL, err)
	}
	if parsedURL.Hostname() == "" {
		return "", fmt.Errorf("%w: empty host", ErrInvalidURL)
	}
	portStr := parsedURL.Port()
	if portStr == "" {
		if hasMalformedWebURLPort(parsedURL.Host) {
			return "", fmt.Errorf("%w: invalid port", ErrInvalidURL)
		}
	} else {
		port, err := strconv.Atoi(portStr)
		if err != nil || port < 1 || port > 65535 {
			return "", fmt.Errorf("%w: invalid port %q", ErrInvalidURL, portStr)
		}
	}
	parsedURL.Host = normalizeWebURLHost(parsedURL.Host)

	return fmt.Sprintf("%s://%s", parsedURL.Scheme, parsedURL.Host), nil
}

// hasProtocolPrefix 检查URL是否包含协议前缀

View on GitHub (pinned to 95cc12e753)

Solutions

  1. Ensure info.URL includes scheme and host, e.g. http://example.com
  2. Validate with url.Parse and check parsedURL.Hostname() != "" before calling WebScan
  3. Normalize bare hosts by prefixing a default scheme (http://) in the input pipeline
  4. Handle errors.Is(err, ErrInvalidURL) by rejecting/flagging the target

Example fix

// before
WebScan(target)
// after
u, err := url.Parse(target)
if err != nil || u.Hostname() == "" {
    target = "http://" + strings.TrimPrefix(target, "//")
}
WebScan(target)
Defensive patterns

Strategy: validation

Validate before calling

u, err := url.Parse(target)
if err != nil || u.Hostname() == "" {
    return fmt.Errorf("target %q has no host", target)
}

Type guard

func hasHost(raw string) bool {
    u, err := url.Parse(raw)
    return err == nil && u.Hostname() != ""
}

Try / catch

if err := WebScan(target); err != nil {
    if errors.Is(err, ErrInvalidURL) {
        log.Printf("skipping invalid target %q: %v", target, err)
        return nil
    }
    return err
}

Prevention

When it happens

Trigger: WebScan calls buildTargetURL with info.URL that yields parsedURL.Hostname() == "": e.g. "/admin", "http:///path", "example.com/path" (no scheme), or "".

Common situations: Scanner config where the URL column lost its scheme ("example.com" instead of "http://example.com"), CSV/Excel imports trimming the scheme, or hand-built URLs from host+path concatenation missing the scheme.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of shadow1ng/fscan@95cc12e753 (2026-09-06). Data as JSON: /api/errors/6cb7aaf5094fcdd7. Report an issue: GitHub.