Documentation/Reference

URLs, paths and query parameters

On this page

Implemented native BASIL URI values, builders and HTTP adapters. Parsing follows RFC 3986 ; IPv6 zone syntax follows RFC 6874 . This is an explicit URI library, not a browser address-bar parser or an authorization policy.

Configured base plus path and query

base = "https://api.example.com/v1/?locale=en#results"
DIM parameters AS DICT OF STRING TO STRING = DICT()
Put(BYREF parameters, "search", "Fred & Jane")
Put(BYREF parameters, "page", "2")
url = JoinURLPath(base, "/customers/", Query=parameters)
PRINT url
' https://api.example.com/v1/customers/?locale=en&search=Fred+%26+Jane&page=2#results

Query is optional through overloads. A dictionary supplies one value per name; DICT OF STRING TO LIST OF STRING supplies repeated values. An ordered LIST OF URLParameter also represents repeated names, bare flags, empty names and explicit empty values. Dictionaries retain insertion order; lists retain their exact order. An empty multivalue replacement removes existing occurrences of that key.

JoinURLPath appends decoded path data, removes duplicate slashes at the join boundary, removes literal dot segments and preserves internal repeated slashes and the final trailing slash. A leading slash does not discard the base path. Empty path leaves it unchanged. A supplied %, ? or # is data and is escaped; existing base escapes such as %2F retain their delimiter identity. Dot segments may walk above the base path; this is not a filesystem confinement/security boundary.

Base query/fragment survive. Supplied query names replace all existing occurrences of those names; unrelated entries retain relative order and replacement entries follow them. Replacement values retain duplicates. With no supplied entries the original raw query is untouched. Query updates use form encoding: spaces become +, literal plus becomes %2B. No credentials, signing or network access occurs.

STRING input returns STRING. URL input returns an independent URL. Typed URLs can be passed directly to HTTPGet/HTTPGetPlain, HTTPPost and RequestHTTP/RequestPlainHTTP. ParseHTTPURL also accepts URL for explicit streaming.

Values and components

ParseURL(text) / URL(text) return an independent opaque URL or checked UrlError. FormatURL(value) returns URI text; IsValidURL is a Boolean syntax probe. IsAbsoluteURL checks for a scheme. Relative references, including the empty reference, are valid; syntactic validity does not imply an HTTP destination.

Read-only public field Meaning
Scheme Lowercase ASCII scheme, or empty for a relative reference
HasAuthority Presence of //; empty host is allowed generically, e.g. file:///
HasUserInfo / RawUserInfo Presence of @, and escaped userinfo without @
Host / HostIsLiteral ASCII registered name or bracket contents; flag distinguishes IPv6/IPvFuture
HasPort / RawPort Presence of :, and decimal text preserving leading zeros/empty port
RawPath Escaped path; %2F is not a separator
HasQuery / RawQuery Presence of ?, and escaped text without ?
HasFragment / RawFragment Presence of #, and escaped text without #

URLAuthority includes userinfo/brackets/port. URLHostname decodes the host, including %25 in an IPv6 zone. URLPort returns OPTIONAL INTEGER: absent/empty is NONE, otherwise checked range 0..65535. Generic URI parsing preserves larger digit-only ports; the network accessor rejects them. URLUsername/URLPassword return checked optional decoded text, distinguishing absence from an explicit empty value. URLPath and URLFragment decode UTF-8 data. URLPathSegments splits before decoding, so /a%2Fb has one nonempty segment a/b. Empty boundary segments are retained. RedactedURL replaces a present password with xxxxx for display; FormatURL includes it.

Raw components round-trip escaped octets, including non-UTF-8 %FF. A decoded STRING accessor rejects invalid UTF-8. NUL is valid encoded component data (%00) in the generic library; the existing HTTP DecodeURLComponent rejects NUL.

All fields are read-only outside the prelude; factories enforce representation invariants. Assignment, collection storage and task arguments retain ordinary independent-value ownership. A read-only field loan prevents another alias/task from mutating its root. Values do not hold pointers, mutable registries or resources. Equality/hash/order use the stored components; different spellings are not implicitly treated as the same destination. NormalizeURL is explicit.

Creating and updating values

url = BuildURL("https", "api.example.com", path="v1/customers", port=8443)
url = WithURLFragment(url, "results")
SetURLQueryParameter(BYREF url, "search", "Fred & Jane")
PRINT FormatURL(url)

BuildURL accepts a separate scheme/host, decoded path (default /), optional Query and port (-1 means absent). Port values 0..65535 are accepted generically. Host may be bare IPv6 or bracketed; pass any port separately. Scoped IPv6 hosts use escaped %25 zone syntax. Supplying userinfo/path/query/fragment as a host is rejected.

WithURLPath/Scheme/Host/Port/Fragment/Credentials return independent values. WithURLCredentials has username-only and username/password overloads. WithURLHost also accepts port=-1; changing a host drops the old port unless supplied explicitly. WithoutURLPort/Fragment/Credentials remove those components. WithURLQuery replaces the entire query, including an explicitly empty ?; WithoutURLQuery removes ?. MergeURLQuery performs the same key-replacement policy as JoinURLPath. These query functions accept the ordered list and both dictionary forms.

SetURLQueryParameter(BYREF url,name,value) replaces duplicates with one value; AddURLQueryParameter appends a value; RemoveURLQueryParameter removes every match and returns a count. The same operations accept a mutable parameter list. Removal retains existing query-marker presence; WithoutURLQuery removes it explicitly. Construction/decoding completes before committing URL mutation. Recoverable failure leaves the old value unchanged; normal cleanup drops partial values and task owners. Fresh owned builder inputs can be adopted through BYVAL; borrowed inputs copy once for independent results. No move is inferred from last use.

Query lookup and encoding

API Result
URLParameters(dictionary) Ordered independent LIST OF URLParameter
ParseURLQuery(text,form=TRUE) / URLQuery(url,form=TRUE) Decode an ordered query list; checked percent/UTF-8 errors
FormatURLQuery(parameters,form=TRUE) Encode a list; checked inconsistent bare-flag state
URLQueryValue(parameters-or-url,name) OPTIONAL STRING; first value, NONE when missing
URLQueryValueOr(parameters-or-url,name,fallback) First value or fallback; present empty is not missing
URLQueryValues(parameters-or-url,name) Every value in order
HasURLQueryParameter(parameters-or-url,name) BOOLEAN presence test
EscapeURLComponent(text,form=FALSE) Encode UTF-8 bytes, preserving only unreserved characters
EscapeURLPath(text) Encode path data, retaining / and path-reserved characters
UnescapeURLComponent(text,form=FALSE) Checked percent and UTF-8 decoding

URLParameter has Name AS STRING, Value AS STRING="", HasEquals AS BOOLEAN=TRUE. Set HasEquals=FALSE for a bare flag; a nonempty Value with that flag is rejected. Empty query text produces no entries. Delimited empty fields such as & remain empty-name bare entries. Only & separates entries and the first = separates a name/value; semicolons are data. Case is significant. With form=FALSE, + is data and spaces encode as %20. Lookup cannot silently discard malformed values.

The existing EncodeURLComponent/DecodeURLComponent HTTP convenience APIs use the same engine with their existing StringError/HTTPError contracts and NUL policy.

Resolution, normalization and limits

ResolveURL(base,reference) implements RFC 3986 section 5.2. Base must be absolute. STRING/STRING returns text; URL/URL or URL/STRING returns URL. /customers replaces the base path; customers replaces its last segment. Empty reference inherits the base path/query but not its fragment. Empty ? replaces a base query. Query and fragment dots are data. Encoded dot segments are not interpreted during resolution. HTTP redirects use this same resolver, followed by existing scheme, downgrade, credential-forwarding and destination checks.

NormalizeURL uppercases escapes, decodes unreserved escapes, lowercases the registered host (preserving case-sensitive IPv6 zone identifiers), removes dot segments and preserves delimiter presence. removeDefaultPort=TRUE additionally removes HTTP 80/HTTPS 443. It does not sort queries, resolve DNS or establish resource identity. Relative paths remain unambiguous after scheme/dot removal.

Parsing accepts ASCII URI syntax. Builders encode decoded UTF-8 path/query/userinfo/ fragment data. Internationalized hosts require externally supplied IDNA A-labels; there is no implicit IDNA/browser host rewriting, backslash repair, whitespace trimming or Unicode normalization. IPv6/IPvFuture and scoped IPv6 are represented; the current HTTP transport accepts unscoped IPv6 and its existing ASCII DNS policy, rejecting userinfo, empty/zero/out-of-range ports and non-HTTP schemes. Generic validity never bypasses transport restrictions or an application’s host allowlist.

UrlError fields: Code, zero-based byte Offset (-1 when inapplicable), Message. Codes: 1 syntax/character policy, 2 percent escape, 3 authority/host, 4 network port range, 5 decoded UTF-8, 6 incompatible builder/operation state. The shared/JSON default encodes URI text; wire errors become JsonError with enclosing path context. Standalone operations retain UrlError.

Algorithms scan bytes linearly, stage query updates, and retain normal automatic/ library checkpoints and cleanup. Existing prebuilt automatic-scheduling limits remain C2/S6; no additional worker or hidden shared mutation is introduced.

python3 tools/test_url.py build/selfhost/stage3
python3 tools/check.py related --feature network

From the BASIL documentation. View source ↗

Find your way around BASIL

Type to search the manual.