304 lines
11 KiB
OCaml
304 lines
11 KiB
OCaml
(*
|
|
* Copyright (c) 2012-2013 Anil Madhavapeddy <anil@recoil.org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*
|
|
*)
|
|
|
|
(** Uniform Resource Identifier handling that is RFC3986-compliant. *)
|
|
|
|
(** A single URI that is a compact sequence of characters that identifies
|
|
an abstract or physical resource. *)
|
|
type t
|
|
|
|
type component = [
|
|
`Scheme
|
|
| `Authority
|
|
| `Userinfo (** subcomponent of authority in some schemes *)
|
|
| `Host (** subcomponent of authority in some schemes *)
|
|
| `Path
|
|
| `Query
|
|
| `Query_key
|
|
| `Query_value
|
|
| `Fragment
|
|
| `Generic
|
|
| `Custom of (component * string * string) (* (component * safe chars * unsafe chars) *)
|
|
]
|
|
|
|
(** For pct encoding customization when converting a URI to a string. *)
|
|
type pct_encoder
|
|
|
|
(** {2 Core functionality } *)
|
|
|
|
(** The empty (zero length) URI reference. Useful for constructing
|
|
URIs piece-by-piece. *)
|
|
val empty : t
|
|
|
|
(** Comparator ordering by host, scheme, port, userinfo, path, query,
|
|
and finally fragment. Designed to produce a reasonable sort order. *)
|
|
val compare : t -> t -> int
|
|
|
|
(** [equal a b] is [compare a b = 0]. *)
|
|
val equal : t -> t -> bool
|
|
|
|
(** Percent-encode a string. The [component] argument defaults to `Path *)
|
|
val pct_encode : ?scheme:string -> ?component:component -> string -> string
|
|
|
|
(** Construct a pct_encoder. *)
|
|
val pct_encoder :
|
|
?scheme:component ->
|
|
?userinfo:component ->
|
|
?host:component ->
|
|
?path:component ->
|
|
?query_key:component ->
|
|
?query_value:component ->
|
|
?fragment:component ->
|
|
unit ->
|
|
pct_encoder
|
|
|
|
(** Percent-decode a percent-encoded string *)
|
|
val pct_decode : string -> string
|
|
|
|
(** Parse a URI string literal into a URI structure. A bare string will be
|
|
interpreted as a path; a string prefixed with `//` will be interpreted as a
|
|
host.
|
|
*)
|
|
val of_string : string -> t
|
|
|
|
(** Convert a URI structure into a percent-encoded URI string *)
|
|
val to_string : ?pct_encoder:pct_encoder -> t -> string
|
|
|
|
(** Resolve a URI against a default scheme and base URI *)
|
|
val resolve : string -> t -> t -> t
|
|
|
|
(** Canonicalize a URI according to Sec 6.2.3 "Scheme-Based
|
|
Normalization". This transform is more aggressive than the
|
|
standard URI-generic normalization automatically done. In
|
|
particular, HTTP(S) URIs with empty path components will have
|
|
their path components set to "/". Some applications like web
|
|
servers may rely on the distinction between a path-less and a
|
|
root-path URI to distinguish request URIs (e.g. OPTIONS * vs
|
|
OPTIONS /).
|
|
|
|
@see <https://tools.ietf.org/html/rfc3986#section-6.2.3> RFC 3986.6.2.3
|
|
*)
|
|
val canonicalize : t -> t
|
|
|
|
(** Make a URI from supplied components. If userinfo or port are
|
|
supplied without host, an empty host is added. If path is supplied
|
|
and userinfo, host, or port is also supplied, path is made
|
|
absolute but not resolved. *)
|
|
val make : ?scheme:string -> ?userinfo:string -> ?host:string ->
|
|
?port:int -> ?path:string -> ?query:(string * string list) list ->
|
|
?fragment:string -> unit -> t
|
|
|
|
(** Functional update for a URI using the supplied components. If a component
|
|
is unspecified then it will be unchanged. If a component is supplied as
|
|
[None] then the component will be removed in the returned URI. If a
|
|
component is supplied as [Some x] then [x] will be added if it does not
|
|
exist in the source URI or replaced if it does exist. *)
|
|
val with_uri : ?scheme:string option -> ?userinfo:string option ->
|
|
?host:string option -> ?port:int option -> ?path:string option ->
|
|
?query:(string * string list) list option -> ?fragment:string option -> t -> t
|
|
|
|
(** {2 Query functions }
|
|
|
|
The query string API attempts to accommodate conventional query
|
|
string representations (i.e. [?key0=value0&key1=value1]) while
|
|
maximally exposing any meaning in those representations. For
|
|
example, it is not necessarily the case that [/] and [/?] are
|
|
equivalent to a web server. In the former case, we observe a zero
|
|
query string whereas in the latter case, we observe a query string
|
|
with a single key, [""] and a zero value. Compare this with [/?=]
|
|
which has a single key and a single empty value,
|
|
[""]. Additionally, some query functions return lists of values
|
|
for a key. These list values are extracted from a {b single} key
|
|
with a comma-separated value list. If a query string has multiple
|
|
identical keys, you must use {! query} to retrieve the entirety of
|
|
the structured query string.
|
|
*)
|
|
|
|
(** Get a query string from a URI *)
|
|
val query : t -> (string * string list) list
|
|
|
|
(** Get a verbatim query string from a URI. If the provenance of the
|
|
URI is a string and its query component has not been updated, this
|
|
is the literal query string as parsed. Otherwise, this is the
|
|
composition of {!query} and {!encoded_of_query} *)
|
|
val verbatim_query : ?pct_encoder:pct_encoder -> t -> string option
|
|
|
|
(** Make a percent-encoded query string from percent-decoded query tuple *)
|
|
val encoded_of_query :
|
|
?scheme:string ->
|
|
?pct_encoder:pct_encoder ->
|
|
(string * string list) list ->
|
|
string
|
|
|
|
(** Parse a percent-encoded query string into a percent-decoded query tuple *)
|
|
val query_of_encoded : string -> (string * string list) list
|
|
|
|
(** Replace the query URI with the supplied list.
|
|
Input URI is not modified
|
|
*)
|
|
val with_query : t -> (string * string list) list -> t
|
|
|
|
(** Replace the query URI with the supplied singleton query list.
|
|
Input URI is not modified
|
|
*)
|
|
val with_query' : t -> (string * string) list -> t
|
|
|
|
(** [get_query_param' q key] returns the list of values for the
|
|
[key] parameter in query [q]. Note that an empty list is not the
|
|
same as a [None] return value. For a query [foo], the mapping is:
|
|
- [/] returns None
|
|
- [/?foo] returns Some []
|
|
- [/?foo=] returns [Some [""]]
|
|
- [/?foo=bar] returns [Some ["bar"]]
|
|
- [/?foo=bar,chi] returns [Some ["bar","chi"]]
|
|
|
|
Query keys can be duplicated in the URI, in which case the first
|
|
one is returned. If you want to resolve duplicate keys, obtain
|
|
the full result set with {! query } instead.
|
|
*)
|
|
val get_query_param' : t -> string -> string list option
|
|
|
|
(** [get_query_param q key] returns the value found for a [key] in
|
|
query [q]. If there are multiple values for the key, then the
|
|
first one is returned. *)
|
|
val get_query_param: t -> string -> string option
|
|
|
|
(** Add a query parameter to the input query URI.
|
|
Input URI is not modified
|
|
*)
|
|
val add_query_param : t -> (string * string list) -> t
|
|
|
|
(** Add a query parameter to the input singleton query URI.
|
|
Input URI is not modified
|
|
*)
|
|
val add_query_param' : t -> (string * string) -> t
|
|
|
|
(** Add a query parameter list to the input query URI.
|
|
Input URI is not modified
|
|
*)
|
|
val add_query_params : t -> (string * string list) list -> t
|
|
|
|
(** Add a query singleton parameter list to the input query URI.
|
|
Input URI is not modified
|
|
*)
|
|
val add_query_params' : t -> (string * string) list -> t
|
|
|
|
(** Remove a query key from the input query URI.
|
|
Input URI is not modified, and no error is generated if the
|
|
key does not already exist in the URI.
|
|
*)
|
|
val remove_query_param : t -> string -> t
|
|
|
|
(** {2 Component getters and setters } *)
|
|
|
|
(** Get the encoded path component of a URI *)
|
|
val path : ?pct_encoder:pct_encoder -> t -> string
|
|
|
|
(** Get the encoded path and query components of a URI *)
|
|
val path_and_query : t -> string
|
|
|
|
(** Replace the path URI with the supplied encoded path.
|
|
If a host is present in the supplied URI, the path is made absolute but not
|
|
resolved. If the path is empty, the path component is removed.
|
|
Input URI is not modified *)
|
|
val with_path : t -> string -> t
|
|
|
|
(** Get the scheme component of a URI *)
|
|
val scheme : t -> string option
|
|
|
|
(** Replace the scheme portion of the URI with the supplied [scheme].
|
|
Input URI is not modified *)
|
|
val with_scheme : t -> string option -> t
|
|
|
|
(** Get the userinfo component of a URI *)
|
|
val userinfo : ?pct_encoder:pct_encoder -> t -> string option
|
|
|
|
(** Replace the userinfo portion of the URI with the supplied [string option].
|
|
If no host is present in the supplied URI, an empty host is added.
|
|
Input URI is not modified. *)
|
|
val with_userinfo : t -> string option -> t
|
|
|
|
(** Get the username component of a URI *)
|
|
val user : t -> string option
|
|
|
|
(** Get the password component of a URI *)
|
|
val password : t -> string option
|
|
|
|
(** Replace the password portion of the URI with the supplied [string option].
|
|
If no host is present in the supplied URI, an empty host is added.
|
|
Input URI is not modified.
|
|
*)
|
|
val with_password : t -> string option -> t
|
|
|
|
(** Get the host component of a URI *)
|
|
val host : t -> string option
|
|
|
|
(** Replace the host component of the URI.
|
|
Input URI is not modified. *)
|
|
val with_host: t -> string option -> t
|
|
|
|
(** Get the host component of a URI, with a default supplied if one is
|
|
not present *)
|
|
val host_with_default: ?default:string -> t -> string
|
|
|
|
(** Get the port component of a URI *)
|
|
val port : t -> int option
|
|
|
|
(** Replace the port component of the URI with the supplied port.
|
|
If no host is present in the supplied URI, an empty host is added.
|
|
Input URI is not modified. *)
|
|
val with_port : t -> int option -> t
|
|
|
|
(** Get the fragment component of a URI *)
|
|
val fragment : t -> string option
|
|
|
|
(** Replace the fragment component of a URI with the supplied fragment.
|
|
Input URI is not modified *)
|
|
val with_fragment : t -> string option -> t
|
|
|
|
(** {2 Formatters } *)
|
|
|
|
(** [pp ppf t] will output a human readable version of the Uri [t]
|
|
to the formatter [ppf] *)
|
|
val pp : Format.formatter -> t -> unit [@@ocaml.toplevel_printer]
|
|
|
|
(** [pp_hum] is now an alias for the {!pp} function. *)
|
|
val pp_hum : Format.formatter -> t -> unit
|
|
|
|
module Parser : sig
|
|
val ipv6 : string Angstrom.t
|
|
val uri_reference : t Angstrom.t
|
|
end
|
|
|
|
(** Specializations for HTTP and HTTPS schemes as per RFC9110 *)
|
|
module Absolute_http : sig
|
|
type uri := t
|
|
type t
|
|
|
|
val of_uri : uri -> (t, [ `Msg of string ]) result
|
|
val to_uri : t -> uri
|
|
|
|
val of_string : string -> t
|
|
val to_string : ?pct_encoder:pct_encoder -> t -> string
|
|
|
|
val make : scheme:[ `Http | `Https ]-> host:string ->
|
|
?userinfo:string -> ?port:int -> ?path:string ->
|
|
?query:(string * string list) list -> ?fragment:string -> unit -> t
|
|
|
|
val host : t -> string
|
|
val scheme : t -> [`Http | `Https]
|
|
end
|