siyuan-note/siyuan · error

invalid custom emoji URL

Error message

invalid custom emoji URL

What it means

Returned by downloadCustomEmojiData (system.go:331) when the `url` POST field is not a valid HTTP/HTTPS URL with a host. url.Parse failed, the scheme was something other than http/https (e.g. file://, javascript:, ftp:), or the host was empty. This guard runs before any network request to block non-HTTP schemes and malformed URLs.

Source

Thrown at kernel/api/system.go:331

		file, err := fileHeader.Open()
		if err != nil {
			return nil, err
		}
		defer file.Close()
		return io.ReadAll(io.LimitReader(file, maxCustomEmojiSize+1))
	}

	rawURL := strings.TrimSpace(c.PostForm("url"))
	if rawURL == "" {
		return nil, fmt.Errorf("field [file] or [url] must not be empty")
	}
	return downloadCustomEmojiData(rawURL)
}

func downloadCustomEmojiData(rawURL string) ([]byte, error) {
	parsedURL, err := url.Parse(rawURL)
	if err != nil || (parsedURL.Scheme != "http" && parsedURL.Scheme != "https") || parsedURL.Host == "" {
		return nil, fmt.Errorf("invalid custom emoji URL")
	}

	response, err := util.NewCustomReqClient().R().Get(parsedURL.String())
	if err != nil {
		return nil, fmt.Errorf("download custom emoji failed: %w", err)
	}
	defer response.Body.Close()
	if response.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("download custom emoji failed with status %d", response.StatusCode)
	}
	if response.ContentLength > maxCustomEmojiSize {
		return nil, fmt.Errorf("custom emoji file is too large")
	}

	data, err := io.ReadAll(io.LimitReader(response.Body, maxCustomEmojiSize+1))
	if err != nil {
		return nil, fmt.Errorf("read custom emoji response failed: %w", err)
	}

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Supply an absolute http:// or https:// URL with a non-empty host.
  2. Trim/validate the URL client-side and reject non-http(s) schemes before submitting.
  3. If the user selected a local file, use the 'file' upload mode instead of the URL mode.

Example fix

// before
fd.append('url', '/local/emoji.png')
// after
fd.append('url', 'https://cdn.example.com/emoji.png')
Defensive patterns

Strategy: validation

Validate before calling

function validEmojiUrl(u) {
  try { const p = new URL(u); return (p.protocol === 'http:' || p.protocol === 'https:') && !!p.host; } catch { return false; }
}

Prevention

When it happens

Trigger: Submitting url='ftp://host/x', url='file:///etc/passwd', url='javascript:alert(1)', url='/local/path' (relative), url='example.com/x' (no scheme), or an unparseable string. The check at system.go:330 fires and returns the error at line 331.

Common situations: User pasted a local file path or relative URL. A non-http scheme was used (intentionally or by mistake). Input contained control characters or was not trimmed. Security-relevant: this guard blocks SSRF-via-scheme and local-file exfiltration.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/474a84308589e4cf. Report an issue: GitHub.