Haiku BUrl: Parsing, Components, and Relative Resolution
Use Haiku BUrl to parse URI components, resolve relative references, encode individual components, and validate syntax without confusing it with reachability.
BUrl is a Support Kit value type for parsing and composing URL/URI fields. Its API exposes protocol, authority, user information, host, port, path, request/query, and fragment, and includes a constructor that resolves a relative reference against a base URL. It does not make a network request, determine whether a hostname resolves, or establish that a server accepts the URL.
The central rule is to keep syntax and component boundaries explicit. A URL string is not one flat piece of text: separators such as :, /, ?, and # have structural meaning. Encoding an entire URL as a single component destroys that structure; inserting arbitrary input into a component without encoding can alter its meaning.
Parse and inspect deliberately
The current constructor accepts a C string and an optional encoding flag. SetUrlString() parses into the URL fields. Use the current IsValid() method for the class’s validity result; older documentation text still mentions InitCheck(), which is not present in the current public header. Always compile against the SDK you target and use its declared method rather than copying an outdated method name.
#include <Url.h>
BUrl url("https://docs.haiku-os.org:8443/guide/start?mode=wide#layout", false);
if (!url.IsValid())
return B_BAD_VALUE;
const BString& scheme = url.Protocol();
const BString& host = url.Host();
const BString& path = url.Path();
const BString& request = url.Request();
The example passes false because the input is already a structured, percent-encoded URL. The accessors return references to fields owned by the BUrl; copy them if they must outlive the URL or survive a mutation. Check HasPort() or other Has...() methods instead of treating a numeric default as proof that a component was explicitly supplied. A missing port and an explicit default port can be distinct inputs even if a network client later chooses the same effective endpoint.
IsValid() is not a policy or connectivity check. The current source validates protocol syntax and applies protocol-specific host-presence rules for a list of schemes; it cannot tell whether DNS, routing, TLS, an application endpoint, or a remote resource will work. A parser-accepted custom scheme may not be usable by any installed handler. Keep address syntax, application policy, and an eventual request result as three separate checks.
Resolve a reference relative to a base
The BUrl(base, relative) constructor implements the relative-reference resolution algorithm described by RFC 3986 section 5. This is more subtle than concatenating strings: a reference can replace the authority, replace the path, inherit a path, or replace the query depending on its form. Let the API do that resolution instead of appending path text manually.
BUrl base("https://example.invalid/manual/chapter/page.html", false);
if (!base.IsValid())
return B_BAD_VALUE;
BUrl next(base, "../appendix/index.html");
if (!next.IsValid())
return B_BAD_VALUE;
The .invalid top-level domain is reserved for examples; this snippet only parses and resolves. For a real application, validate the base first, and separately apply any product-specific scheme or host rules. Test references with a new authority, an absolute path, a relative path, a query-only reference, a fragment, . and .., and an empty reference. A URL library’s implementation should be tested against the exact RFC examples relevant to the application.
Do not confuse a relative URL reference with a filesystem path. A filesystem BPath constructor creates a file URL representation. It is not a generic way to resolve arbitrary network paths, and path normalization rules differ from URL reference rules.
Encode one component, not the whole address
BUrl::UrlEncode() is a static helper that returns a new BString; it accepts strict and directory flags. In current upstream code, strict mode represents spaces as %20, while non-strict mode represents spaces with +. Directory mode leaves / and \ unescaped in addition to unreserved bytes, so use it only when those separators are part of a path structure that should remain separators.
BString userQuery("two words & symbols");
BString encoded = BUrl::UrlEncode(userQuery, true, false);
BString assembled("https://example.invalid/search?q=");
assembled << encoded;
BUrl parsed(assembled.String(), false);
if (!parsed.IsValid())
return B_BAD_VALUE;
This demonstrates the important return-value behavior: keep the encoded BString, then combine it with the URL’s delimiters. At the current upstream master source inspected on 2026-10-03, the SetUrlString(url, encode) implementation calls UrlEncode(url, true, true) but does not use the returned encoded string before parsing url. The user documentation says the input is encoded when the flag is true, so this is a documentation/implementation discrepancy. Do not rely on encode=true to transform a preassembled URL without checking the exact Haiku revision you ship. Explicitly encode individual components and pass the assembled result as already encoded (false), then add a regression test for your target build.
The helper does not know whether a string is a path segment, query parameter, fragment, or full URL. It is not a substitute for a component-aware builder. Encode parameter names and values according to the protocol’s query rules, then insert structural separators in the right place. Do not encode a full URL and then expect BUrl to recover the separators. Likewise, do not decode repeatedly: a second decoding pass can turn a literal encoded sequence into a delimiter and change the parsed meaning.
Mutate fields and preserve the intended representation
The API has setters such as SetProtocol(), SetHost(), SetPort(), SetPath(), SetRequest(), and SetFragment(). Prefer them to hand-editing slices of UrlString(). The generated string is cached and recomposed from fields as needed. When changing a path or request, establish whether the value is already percent-encoded and apply the correct component treatment once.
Authority contains more than host: it can include user information and a port. Setting the entire authority string is a lower-level operation than setting separate fields. If an application does not use credentials in URLs, keep user and password components absent. Avoid logging complete URLs if they may contain secrets or private query values; log a redacted host/path or a request identifier instead. This is ordinary data-handling hygiene, not proof that parsing alone provides authentication or confidentiality.
Port handling deserves an explicit check. Use HasPort() before distinguishing an explicit numeric port from the default. Validate any user-entered port against the supported range before passing it to SetPort(), and confirm behavior against the target release. A valid syntactic port says nothing about whether a service is listening there.
Use BUrl at the networking boundary
The Network Kit can consume URL-oriented request data, but BUrl itself is the representation layer. Keep request construction separate from transport. A parsed URL can still be disallowed by application policy, and a transport operation can fail after parsing succeeds. Capture those outcomes separately: parse/validation error, local transport error, and remote HTTP or protocol response.
For redirected requests, re-evaluate which components change and what the application permits before reusing request state. Do not assume that a URL object automatically preserves or strips headers, cookies, or credentials in a way suitable for every redirect policy. Those behaviors belong to the HTTP client/request layer. Keep URL generation and network dispatch in different functions so a unit test can verify the exact serialized URL without contacting a server.
For internationalized host names, the API exposes IDNA conversion methods with status codes. Apply conversion at a deliberate boundary and test Unicode and ASCII round trips using the target Haiku implementation. Do not assume percent-encoding a host name is equivalent to IDNA conversion; those are different mechanisms.
Verify parsing and round trips
Build a table of valid and invalid inputs for the schemes your application supports. Assert protocol, host, explicit-port presence, path, request, and fragment individually. Include empty authority cases only if your product needs them. Run RFC-relative-reference cases against known expected results and test both encoded and unencoded forms.
Add regression cases for the current encode discrepancy: a raw space in an input URL, an already percent-encoded component, literal plus signs, encoded separators, UTF-8 bytes, and directory mode. Confirm that each component is encoded exactly once and that query delimiters are preserved. Compare UrlString() to an expected serialized value after setters, not only IsValid().
Finally, test malformed strings and policy rejection independently from network failure. A URL can be syntactically valid but unreachable; a server can be reachable and still reject a request. This separation keeps logs and user-facing diagnostics accurate and prevents a parser result from being mistaken for end-to-end verification.
Related:
- Haiku’s Network Kit HTTP Client: Requests, Listeners, Cookies, and TLS Boundaries
- BMessage Flattening and IPC: How Haiku Moves Typed Data Between Processes
Sources: