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#resultsQuery 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