pax_global_header00006660000000000000000000000064141463746530014527gustar00rootroot0000000000000052 comment=2e62d0f117c95a43e61ddb018dcacba8036aa3d5 raku-uri-0.3.5/000077500000000000000000000000001414637465300132735ustar00rootroot00000000000000raku-uri-0.3.5/.github/000077500000000000000000000000001414637465300146335ustar00rootroot00000000000000raku-uri-0.3.5/.github/workflows/000077500000000000000000000000001414637465300166705ustar00rootroot00000000000000raku-uri-0.3.5/.github/workflows/test.yml000066400000000000000000000012061414637465300203710ustar00rootroot00000000000000name: test on: push: branches: - '*' tags-ignore: - '*' pull_request: jobs: raku: strategy: matrix: os: - ubuntu-latest - macOS-latest - windows-latest raku-version: - 'latest' - '2020.05.1' runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - uses: Raku/setup-raku@v1 with: raku-version: ${{ matrix.raku-version }} - name: Install Dependencies run: | zef install --/test App::Prove6 zef install --deps-only . - name: Run Tests run: prove6 -I. t raku-uri-0.3.5/.gitignore000066400000000000000000000000111414637465300152530ustar00rootroot00000000000000.precomp raku-uri-0.3.5/ChangeLog000066400000000000000000000026471414637465300150560ustar00rootroot000000000000002021-02-22 v0.3.1 * Remove the meta test (Test::META depends on this module.) 2019-08-25 v0.3.0 * Allow mutation of a URI in user code This contains potentially incompatible changes. Please see README.md 2019-06-29 v0.2.2 * Fix double encoding of path parts 2019-06-12 Juan J. merelo * lib/URI/Escape.pm: Eliminates a debug "say" that was bugging some other stuff. New features for June 2011 upgrade include: 1) Make "new" method a useful parsing constructor. 2) Add "parse" method to replace "init". 3) Formalize access to result parse tree created by grammar parsing. URI object has grammar attribute which in turn has a parse_result attribute, with full parse tree, giving caller access to all URI components identified in IETF RFC. 4) Add query_form method providing access to hash based on "?var1=abc&var2=def" query section for CGI type applications. 5) Add 'validating' attribute that, when set to true, tells the URI module to fail parsing unless the entire string it is asked to parse is a URI. Default is to provide Perl 5 URI behavior and just try to parse URI from passed parse string. "is_validating"" attribute can be set as named param to new. 6) Pluggable grammar design with "grammar" attribute that identifies grammar used to parse URI, provides access to result parse tree, and (theoretically for now) allows for use of other grammars like IRI. raku-uri-0.3.5/LICENSE000066400000000000000000000213061414637465300143020ustar00rootroot00000000000000 The Artistic License 2.0 Copyright (c) 2000-2006, The Perl Foundation. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble This license establishes the terms under which a given free software Package may be copied, modified, distributed, and/or redistributed. The intent is that the Copyright Holder maintains some artistic control over the development of that Package while still keeping the Package available as open source and free software. You are always permitted to make arrangements wholly outside of this license directly with the Copyright Holder of a given Package. If the terms of this license do not permit the full use that you propose to make of the Package, you should contact the Copyright Holder and seek a different licensing arrangement. Definitions "Copyright Holder" means the individual(s) or organization(s) named in the copyright notice for the entire Package. "Contributor" means any party that has contributed code or other material to the Package, in accordance with the Copyright Holder's procedures. "You" and "your" means any person who would like to copy, distribute, or modify the Package. "Package" means the collection of files distributed by the Copyright Holder, and derivatives of that collection and/or of those files. A given Package may consist of either the Standard Version, or a Modified Version. "Distribute" means providing a copy of the Package or making it accessible to anyone else, or in the case of a company or organization, to others outside of your company or organization. "Distributor Fee" means any fee that you charge for Distributing this Package or providing support for this Package to another party. It does not mean licensing fees. "Standard Version" refers to the Package if it has not been modified, or has been modified only in ways explicitly requested by the Copyright Holder. "Modified Version" means the Package, if it has been changed, and such changes were not explicitly requested by the Copyright Holder. "Original License" means this Artistic License as Distributed with the Standard Version of the Package, in its current version or as it may be modified by The Perl Foundation in the future. "Source" form means the source code, documentation source, and configuration files for the Package. "Compiled" form means the compiled bytecode, object code, binary, or any other form resulting from mechanical transformation or translation of the Source form. Permission for Use and Modification Without Distribution (1) You are permitted to use the Standard Version and create and use Modified Versions for any purpose without restriction, provided that you do not Distribute the Modified Version. Permissions for Redistribution of the Standard Version (2) You may Distribute verbatim copies of the Source form of the Standard Version of this Package in any medium without restriction, either gratis or for a Distributor Fee, provided that you duplicate all of the original copyright notices and associated disclaimers. At your discretion, such verbatim copies may or may not include a Compiled form of the Package. (3) You may apply any bug fixes, portability changes, and other modifications made available from the Copyright Holder. The resulting Package will still be considered the Standard Version, and as such will be subject to the Original License. Distribution of Modified Versions of the Package as Source (4) You may Distribute your Modified Version as Source (either gratis or for a Distributor Fee, and with or without a Compiled form of the Modified Version) provided that you clearly document how it differs from the Standard Version, including, but not limited to, documenting any non-standard features, executables, or modules, and provided that you do at least ONE of the following: (a) make the Modified Version available to the Copyright Holder of the Standard Version, under the Original License, so that the Copyright Holder may include your modifications in the Standard Version. (b) ensure that installation of your Modified Version does not prevent the user installing or running the Standard Version. In addition, the Modified Version must bear a name that is different from the name of the Standard Version. (c) allow anyone who receives a copy of the Modified Version to make the Source form of the Modified Version available to others under (i) the Original License or (ii) a license that permits the licensee to freely copy, modify and redistribute the Modified Version using the same licensing terms that apply to the copy that the licensee received, and requires that the Source form of the Modified Version, and of any works derived from it, be made freely available in that license fees are prohibited but Distributor Fees are allowed. Distribution of Compiled Forms of the Standard Version or Modified Versions without the Source (5) You may Distribute Compiled forms of the Standard Version without the Source, provided that you include complete instructions on how to get the Source of the Standard Version. Such instructions must be valid at the time of your distribution. If these instructions, at any time while you are carrying out such distribution, become invalid, you must provide new instructions on demand or cease further distribution. If you provide valid instructions or cease distribution within thirty days after you become aware that the instructions are invalid, then you do not forfeit any of your rights under this license. (6) You may Distribute a Modified Version in Compiled form without the Source, provided that you comply with Section 4 with respect to the Source of the Modified Version. Aggregating or Linking the Package (7) You may aggregate the Package (either the Standard Version or Modified Version) with other packages and Distribute the resulting aggregation provided that you do not charge a licensing fee for the Package. Distributor Fees are permitted, and licensing fees for other components in the aggregation are permitted. The terms of this license apply to the use and Distribution of the Standard or Modified Versions as included in the aggregation. (8) You are permitted to link Modified and Standard Versions with other works, to embed the Package in a larger work of your own, or to build stand-alone binary or bytecode versions of applications that include the Package, and Distribute the result without restriction, provided the result does not expose a direct interface to the Package. Items That are Not Considered Part of a Modified Version (9) Works (including, but not limited to, modules and scripts) that merely extend or make use of the Package, do not, by themselves, cause the Package to be a Modified Version. In addition, such works are not considered parts of the Package itself, and are not subject to the terms of this license. General Provisions (10) Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license. (11) If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license. (12) This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder. (13) This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed. (14) Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. raku-uri-0.3.5/META6.json000066400000000000000000000023061414637465300150030ustar00rootroot00000000000000{ "perl" : "6.*", "raku" : "6.*", "name" : "URI", "license" : "Artistic-2.0", "version" : "0.3.5", "description" : "A URI implementation using Raku grammars to implement RFC 3986 BNF", "license" : "Artistic-2.0", "depends" : [ ], "provides" : { "IETF::RFC_Grammar" : "lib/IETF/RFC_Grammar.rakumod", "IETF::RFC_Grammar::IPv6" : "lib/IETF/RFC_Grammar/IPv6.rakumod", "IETF::RFC_Grammar::URI" : "lib/IETF/RFC_Grammar/URI.rakumod", "URI" : "lib/URI.rakumod", "URI::Authority" : "lib/URI.rakumod", "URI::Path" : "lib/URI/Path.rakumod", "URI::Query" : "lib/URI/Query.rakumod", "URI::Escape" : "lib/URI/Escape.rakumod", "URI::DefaultPort" : "lib/URI/DefaultPort.rakumod" }, "source-url" : "git://github.com/raku-community-modules/URI.git", "authors" : ["Perl 6 community"], "meta-version" : 1, "support" : { "source" : "https://github.com/raku-community-modules/URI.git", "bugtracker" : "https://github.com/raku-community-modules/URI/issues" } } raku-uri-0.3.5/README.md000066400000000000000000001005011414637465300145470ustar00rootroot00000000000000# Raku realization of URI - Uniform Resource Identifiers handler A URI implementation using Raku grammars to implement RFC 3986 BNF. Currently only implements parsing. Includes URI::Escape to (un?)escape characters that aren't otherwise allowed in a URI with % and a hex character numbering. use URI; my URI $u .= new('http://her.com/foo/bar?tag=woow#bla'); my $scheme = $u.scheme; my $authority = $u.authority; my $host = $u.host; my $port = $u.port; my $path = $u.path; my $query = $u.query; my $frag = $u.frag; # or $u.fragment; my $tag = $u.query-form; # should be woow # etc. use URI::Escape; my $escaped = uri-escape("10% is enough\n"); my $un-escaped = uri-unescape('10%25%20is%20enough%0A'); # Modify the parts $u.scheme('https'); $u.authority('example.com:8443'); $u.path('/bar/foo'); $u.query('x=1&y=2'); # OR $u.fragment('cool'); say "$u"; #> https://example.com:8443/bar/foo?x=1&y=2#cool # Authority is an object, but may be undefined with $u.authority { say .userinfo; # none set, no output say .host; #> example.com say .port; #> 8443 # It is mutable too .userinfo('bob'); say $u.authority; #> bob@example.com:8443 } # Path is an object, always defined, but immutable with $u.path { say .path; #> /bar/foo .say for .segments; #> bar\nfoo\n } # Query is an object, always defined, mutable with $u.query { say .query; #> x=1&y=2 .query('foo=1&foo=2'); say .query-form[0]; #> 1 say .query-form[1]; #> 2 .query-form.push: 'bar' => 'ok'; say .query; #> foo=1&foo=2&bar=ok .query(''); .query-form = 123; say .query; #> abc=123 } ### POTENTIALLY INCOMPATIBLE CHANGES BETWEEN v0.2.2 and v0.3.0 The v0.3.0 introduced the ability to mutate the parts of an existing URI, which in turn may have introduced changes which are incompatible with existing applications: * URI.query now returns an object of type URI::Query not a Str The object will coerce correctly when interpolated into a string but will need an explicit .Str coercion if being assigned to a Str typed variable. * URI.path now returns an object of type URI::Path not a Str The object will coerce correctly when interpolated into a string but will need an explicit .Str coercion if being assigned to a Str typed variable. * URI.query-form no longer returns a Hash but an object of URI::Query The object does the Associative role so for the most part can be treated like a Hash but an explicit .Hash coercion may be required when comparing with another Hash or when merging with another Hash. Some uses of query-form have been marked as deprecated and should use .query instead. The changes have been tested with the majority of modules that depend on URI and only a few required changes. DESCRIPTION =========== A URI object represents a parsed string that abides by the grammar defined in RFC 3986 for Universal Resource Identifiers. The object then works to enforce the rules defined in the RFC for any modifications made to it. As of this writing, The URI class is scheme agnostic. It will verify that URI is correct, in general, but not correct for a given scheme. For example, `http:foo` would be considered a valid URI even though that is not a valid URI according to the rules specific to the `http` scheme. This class uses "heavy accessors". From the SYNOPSIS, you may have noted that assignment to accessors is not used. This is because nearly all accessors in this class do some extra work of parsing, validating, and cross-referencing with other fields to guarantee that the URI is correct before making a change. my $u = URI.new; $u.path = '/foo/bar'; # ERROR: «Cannot modify an immutable URI::Path» $u.path('/foo/bar'); # WORKS! This mutator pattern is meant to reflect the internal complexity. SCHEME, AUTHORITY, AND PATH --------------------------- In RFC 3986 URIs, the scheme, the authority, and the path are related. This should not matter most of the time, but to avoid problems when setting these three, it is safest to set them in this order: my $u = URI.new; $u.path(''); $u.scheme($my-scheme); $u.authority($my-host); $u.path($my-path); This is because an empty path is permitted in any case, but the format of the path is limited whether an authority and scheme are set. With an authority set (i.e., the URI either starts with "//" or with "scheme://"), a non-empty path must start with a "/" and may start with empty path segments, e.g. "scheme://foo//" is valid. When there's no authority set, but a scheme is used (e.g., it starts with "scheme:", but not "scheme://"), a non-empty path may either start with a "/" or not, but must contain one or more other characters in the first segment of the path. Thus, the following code will fail: my $u = URI.new('scheme:'); $u.path('//'); # ERROR: «Could not parse path "//" as part of URI: scheme://» When there's no authority and no scheme used, a non-empty path may start with a "/" or not, but must contain one or more other characters in the first segment. These rules are enforced whenever setting or clearing the scheme, authority, or path. If the resulting URI object would be invalid a `X::URI::Path::Invalid` exception will be thrown. QUERY ----- The `query` method of this class returns a `URI::Query` object.This is a special object that is `Positional`, `Associative`, and `Iterable`. That is, it can be bound to an array variable, a hash variable, and iterated using a loop. If stringified, it will return a URI-encoded query. If used as an array, will act like somewhat like an array of `Pair`s. If used as a hash, will provide a map from query keys to query values. The native internal representation is a list of `Pair`s as this is the best match for how queries are defined. Because of this, there's a problem when using a query as a hash: duplicate pairs are valid. To handle this, the `URI::Query` object always returns a list of values, sorted in the order they appear for each key. For example: my $u = URI.new('?foo=1&bar=2&foo=3'); say $u.query[0]; #> 1 say $u.query[1]; #> 3 say $u.query[0]; #> 2 Older versions of the URI module handled this differently, using a mixed value representation. In order to gain some backwards compatibility, this is still supported by setting the `hash-format`: # Continues from previous $u.query.hash-format = URI::Query::Mixed; say $u.query[0]; #> 1 say $u.query[1]; #> 3 # The bar value is NOT a list now say $u.query; #> 2 # Return to the default mode $u.query.hash-format = URI::Query::Lists; However, the list version is safer and may likely work with most existing code that worked with mixed before. Another mode is provided to force single values, which is often how applications treat these out of convenience. In that case, only the last value will be kept: # Continues from previous $u.query.hash-format = URI::Query::Singles; # These are never lists now say $u.query; #> 3 say $u.query; #> 2 The `URI::Query::Lists` mode is default and recommended mode. GRAMMAR ------- This class will keep a copy of the result of parsing the URI string for you. If you are interested in precise details of the parse tree, you get them using the `grammar` method: my $host-in-grammar = $u.grammar.parse-result; if $host-in-grammar { say 'Host looks like registered domain name - approved!'; } else { say 'Sorry we do not take ip address hosts at this time.'; say 'Please use registered domain name!'; } The `IETF::RFC_Grammar::URI` grammar sticks close to the BNF defined in RFC 3986. PARTIAL MATCHING ---------------- Many times a URI you are interested in is embedded within another string. This class will allow you to parse URIs out of a larger string, so long as the URI is at the start. This is done by setting the `:match-prefix` option during construction or when calling `parse`: { # require whole string matches URI and throw exception otherwise .. my $u_v = URI.new('http://?#?#'); CATCH { when X::URI::Invalid { ... } } } my $u_pfx = URI.new('http://example.com } function(var mm){', :match-prefix); METHODS ======= method new ---------- multi method new(URI:U: Str() $uri, Bool :$match-prefix) returns URI:D multi method new(URI:U: Str() :$uri, Bool :$match-prefix) returns URI:D These construct a new `URI` object and return it. The given `$uri` value is converted to a string and then parsed using the `parse` method. If `:match-prefix` is set, then the grammar will be allowed to match a prefix of the given input string rather than requiring a total match. The `:match-prefix` given also becomes the default value for any figure calls to `parse`. Throws a `X::URI::Invalid` exception if the URI cannot be parsed. method parse ------------ method parse(URI:D: Str() $str, Bool :$match-prefix = $.match-prefix) This method allows an existing URI object to be reused to parse another string. This parses the given string and replaces all internal state of the object with values for the new parse. The given `:match-prefix` flag becomes the new default when set. Throws a `X::URI::Invalid` exception if the URI cannot be parsed. method grammar -------------- method grammar(URI:D:) returns IETF::RFC_Grammar:D Returns the object used to parse and store the state of the parse. method match-prefix ------------------- method match-prefix(URI:D:) returns Bool:D Returns True if the most recent call to `parse` (or `new`) allowed a prefix match or False if a total match was required. This is the default value of any future call to `parse`. method scheme ------------- multi method scheme(URI:D:) returns URI::Scheme:D multi method scheme(URI:D: Str() $scheme) returns URI::Scheme:D Returns the scheme part of the URI. This is a string that must match the `URI::Scheme` subset type. The second form allows the scheme to be replaced with a new scheme. It is legal for the scheme to be set to an empty string, which has the effect of making the URI scheme-less. This will throw an `X::URI::Path::Invalid` exception if adding or removing the scheme will make the URI invalid. See SCHEME, AUTHORITY, AND PATH section for additional details. method authority ---------------- multi method authority(URI:D:) returns URI::Authority multi method authority(URI:D: Nil) returns URI::Authority:U multi method authority(URI:D: Str() $new) returns URI::Authority:D Returns the `URI::Authority` for the current URI object. This may be an undefined type object if no authority has been set or found during parse. When passed a string, the string will have the authority parsed out of it and a new authority object will be used to store the parsed information. An empty authority is valid. When passed `Nil`, the authority will be cleared. When passing an argument, this will throw an `X::URI::Path::Invalid` exception if setting or clearing the authority on the URI will make the URI invalid. See SCHEME, AUTHORITY, AND PATH section for details. The authority is made up of three components: userinfo, host, and port. Additional methods are provided for accessing each of those parts separately. method userinfo --------------- multi method userinfo(URI:D:) returns URI::Userinfo:D multi method userinfo(URI:D: Str() $new) returns URI::Userinfo:D The userinfo is an optional component of the URI authority. This method returns the current userinfo or an empty string. Setting this method will cause a `URI::Authority` to be constructed and `authority` to be set, if it is not already defined. This may result in a `X::URI::Path::Invalid` exception being thrown if adding an authority will make the path invalid. method host ----------- multi method host(URI:D:) returns URI::Host:D multi method host(URI:D: Str() $new) returns URI::Host:D The host is a component of the URI authority. This method returns the current host or an empty string. Setting this method will cause a `URI::Authority` to be constructed and `authority` to be set, if it is not already defined. This may result in a `X::URI::Path::Invalid` exception being thrown if adding an authority will make the path invalid. method default-port ------------------- method default-port(URI:D:) returns URI::Port This method applies the `scheme-port` method of `URI::DefaultPort` to the scheme set on this object. Basically, a shortcut for: my $u = URI.new("..."); my $port = URI::DefaultPort.scheme-port($u.scheme); It returns the usual port for the named scheme. method _port ------------ multi method _port(URI:D:) returns URI::Port multi method _port(URI:D: Nil) returns URI::Port:U multi method _port(URI:D: Int() $new) returns URI::Port:D When an authority is set on the URI, this gets or sets the authority's port. This differs from `port`, which returns either the port set or the `default-port`. This method returns just the port. If no authority is set or no port is set, this returns an undefined value (i.e., an `Int` type object). Setting this method will cause a `URI::Authority` to be constructed and `authority` to be set, if it is not already defined. This may result in a `X::URI::Path::Invalid` exception being thrown if adding an authority will make the path invalid. method port ----------- multi method port(URI:D:) returns URI::Port multi method port(URI:D: Nil) returns URI::Port multi method port(URI:D: Int() $new) returns URI::Port When retrieving a value from the object, this method returns either the port set on the authority or the default port for the current URI scheme. It may return an undefined value (i.e., an `Int` type object) if there is no port set and no known default port for the current scheme or no scheme set. When setting a value on the object, this method sets the port on the authority. Setting this method will cause a `URI::Authority` to be constructed and `authority` to be set, if it is not already defined. This may result in a `X::URI::Path::Invalid` exception being thrown if adding an authority will make the path invalid. method path ----------- multi method path(URI:D:) returns URI::Path:D multi method path(URI:D: Str() $path) returns URI::Path:D Path is the main required element of a URI, but may be an empty string. This method returns the current setting for the path as a `URI::Path` object. It also allows setting a new path, which will construct a new `URI::Path` object. This method will throw a `X::URI::Path::Invalid` exception if the path is not valid for the current scheme and authority settings. method segments --------------- multi method segments(URI:D:) returns List:D multi method segments(URI:D: @segments where *.elems > 0) returns List:D multi method segments(URI:D: $first-segment, *@remaining-segments) returns List:D Returns the path segments (i.e., the parts between slashes). This is a shortcut for: my $u = URI.new("..."); my @s = $u.path.segments; The number of segments is equal to the number of slashes in the original path plus one. The segments are constructed so that joining the segments by a slash (/) will give you the original path. You may pass a list in to replace the segments with a new value. This will throw a `X::URI::Path::Invalid` exception if any of the segments contain a slash or if the set path will cause the URI to become invalid. Be sure when setting segments to include an initial empty string if you want the path to start with a slash. method query ------------ multi method query(URI:D:) returns URI::Query:D multi method query(URI:D: Str() $new) returns URI::Query:D multi method query(URI:D: *@new) returns URI::Query:D Accesses or updates the query associated with the URI. This is returns as a `URI::Query` object. This will always be defined, but may be empty. When passed a string, the string will be parsed using `split-query` and the query object will be updated. When passed a list, the list of `Pair`s given will be used to setup a new query that way. When the list form is used, any named parameters passed will be ignored (they will not be used to fill the query) and a warning will be issued. method path-query ----------------- method path-query(URI:D:) returns Str:D Returns the path and query as a string joined by a question mark ("?"). It will return just the path as a string if the query is empty. method fragment --------------- multi method fragment(URI:D:) returns URI::Fragment:D multi method fragment(URI:D: Str() $new) returns URI::Fragment:D Returns the URI fragment, which is always defined, but may be an empty string. If passed a value, it will set the fragment. method gist ----------- multi method gist(URI:D:) returns Str:D Reconstructs the URI from the components and returns it as a string. method is-relative ----------------- method is-relative returns Bool A URI is considered relative, if it doesn't include a scheme or a host and and its path generally refers to an HTML document on the same machine as any current document; method rel2abs ------------- method rel2abs(URI:D: URI:D $base) returns URI:D If the URI object is relative, this method constructs a new URI by cloning `$base` with a new cloned from $base, but with path constructed by calling `$base.path.rel2abs(self.path)` If the URI object is not relative, it is returned unaltered. method Str ---------- multi method Str(URI:D:) returns Str:D Reconstructs the URI from the components and returns it as a string. SUBROUTINES =========== sub split-query --------------- sub split-query(Str() $query, :$hash-format = URI::Query::None) This routine will slice and dice a query string, which is useful when parsing URIs and may also be useful when parsing POST entities that are encoded as `application/x-www-form-urlencoded`. This routine is exported with the `:split-query` tag or can be used with the full namespace, `URI::split-query`. With just the required string, this routine will parse that string and return a list of `Pair`s mapping each query form key to its respective value. The order is preserved. This is used by `URI::Query` during construction. For example: my @qf = URI::split-query("foo=1&bar%20=2&foo=3"); dd @qf; #> Array @qf = [:foo("1"), "bar " => ("2"), :foo("3")] Notice that this will perform a `uri-escape` operation of keys and values in the process so the values you receive have had the URI encoded characters decoded. You can retrieve the query string as a hash instead by passing the `:hash-format` option. This works exactly as it does for `URI::Query`. The options for `:hash-format` include: ### URI::Query::Lists Every key is mapped to a list of one or more values. From the example input, a structure like the following is returned: my %qf = URI::split-query("foo=1&bar%20=2&foo=3", hash-format => URI::Query::Lists, ); dd %qf; #> Hash %qf = {"bar " => (["2"]), :foo(["1", "3"])} This is also the default if you just pass `:hash-format` as a boolean option. ### URI::Query::Mixed Every key is mapped to either a list of two or more values or directly to a single value, like the following: my %qf = URI::split-query("foo=1&bar%20=2&foo=3", hash-format => URI::Query::Mixed, ); dd %qf; #> Hash %qf = {"bar " => ("2"), :foo(["1", "3"])} ### URI::Query::Singles Every key is mapped to a single value, which will be the last value encountered in the input, like this: my %qf = URI::split-query("foo=1&bar%20=2&foo=3", hash-format => URI::Query::Mixed, ); dd %qf; #> Hash %qf = {"bar " => ("2"), :foo("3")} HELPER SUBSETS ============== URI::Scheme ----------- This is a subset of `Str` that only accepts valid schemes. URI::Userinfo ------------- This is a subset of `Str` that only accepts valid userinfo. URI::Host --------- This is a subset of `Str` that only accepts valid hosts. URI::Port --------- This is a subset of `UInt`. URI::Query::ValidQuery ---------------------- This is a subset of `Str` that only accepts valid query strings. URI::Fragment ------------- This is a subset of `Str` that only accepts valid URI fragments. HELPER CLASSES ============== URI::Authority -------------- The `authority` method of a URI constructs or returns this object. It is recommended that you do not construct a `URI::Authority` object directly, but let the methods of `URI` handle construction. ### method userinfo method userinfo(URI::Authority:D:) is rw returns URI::Userinfo:D This is a simple setter/getter for the userinfo on the authority. It must be defined, but may be the empty string. If not empty, it must be valid for the userinfo component of a URI. ### method host method host(URI::Authority:D:) is rw returns URI::Host:D This is a simple setter/getter for the host part of the URI authority. It must be defined, but may be the empty string. If not empty, must be a valid host value, which may be an IP address or registered name. ### method port method port(URI::Authority:D:) is rw returns URI::Port This is a simple setter/getter for the port part of the URI authority. It may be set to an undefined value if no explicit port is set in the authority. If defined, it must an unsigned integer. ### method gist multi method gist(URI::Authority:D:) returns Str:D Returns the string representation of the URI authority. For example, my $u = URI.new("http://steve@example.com:8008"); # say calls .gist say $u.authority; #> "steve@example.com:8080"; ### method Str multi method gist(URI::Authority:D:) returns Str:D Stringifies identical to `gist`. URI::Path --------- This class is used to represent URI path components. It is recommended that you do not construct a `URI::Path` object directly, but rely on the `path` setter in `URI` instead. These objects are immutable. Please use methods on `URI` to make changes. ### method path method path(URI::Path:D:) returns Str:D Returns the string representation of the path. ### method segments method segments(URI::Path:D:) returns List:D Returns a list representation of the path segments. In a URI, the path segments are the strings between slashes ("/"). ### method rel2abs method rel2abs(URI::Path:D: URI::Path:D $base) returns URI::Path:D Compute a new path by appending the path object's path to base. If the path object already is absolute (has a leading '/'), it is returned unaltered. ### method gist method gist(URI::Path:D:) returns Str:D Returns the `path`. ### method Str method Str(URI::Path:D:) returns Str:D Returns the `path`. URI::Query ---------- This class is used to represent a URI query component. This class may be safely constructed and used independently of the URI object. It behaves as both a positional and associative object and is iterable. Internally, it is stored as an `Array` of `Pair`s. You must not treat this object purely as you would an `Array` or purely as you would a `Hash` as some methods will not work the way you expect. The performance of the associative methods is not guaranteed and is probably going to be relatively slow. This implementation values simplicity and accuracy of representation to CPU performance. If you need something that is better for CPU performance, you should investigate the use of another library, such as `Hash::MultiValue` or sub-class to provide a higher performance implementation of `URI::Query`'s associative methods. ### method new multi method new(Str() $query, URI::Query::HashFormat :$hash-format = URI::Query::Lists) returns URI::Query:D multi method new(:$query, URI::Query::HashFormat :$hash-format = URI::Query::Lists) returns URI::Query:D Constructs a new `URI::Query` from the given string, which may be empty. Unlike `split-query`, which permits boolean values for the `hash-format` option, `URI::Query` requires a `URI::Query::HashFormat` value and will reject a boolean (because the boolean does not make sense in this case). ### enum HashFormat This enumeration provides the values that may be set on `hash-format` on a `URI::Query`. Possible values are as follows. #### URI::Query::List This is the default and recommended value. When set in `hash-format`, each key in the hash representation will point to a list of one or more values. This is recommended as it is the most accurate representation to what is actually possible in a query string. The values within each key will be sorted in the same order as they appear in the query. #### URI::Query::Mixed When set in `hash-format`, each key in the hash representation may be mapped to either a list of two or more values, or to the value itself if there is only one. This is not recommended because it means that setting a key to an iterable value will be treated as multiple key-value pairs when it comes time to render the query as a string. This could have unintended consequences. #### URI::Query::Singles When set in `hash-format`, each key in the hash representation will be mapped directly to a single value. If there are multiple values that have been set in the query, then only the last will be visible through the hash representation. This is not recommended because it may hide certain values, but may be useful for simple applications that treat the query string as having unique values. Just note that the trade-off is that your application may be confused when multiple values are present. #### URI::Query::None This value should not be used, but will be treated the same as `URI::Query::List` if used here. It is provided for use with `split-query` only. ### method hash-format method hash-format(URI::Query:D) is rw returns URI::Query::HashFormat This is a simple setter/getter for setting the way in which associative lookups are performed. See `enum URI::Query::HashFormat` for a description of each mode. ### method query method query(Query:D:) returns URI::Query::ValidQuery:D method query(Query:D: Str() $new) returns URI::Query::ValidQuery:D This method returns the string representation of the URI query component. When passed a string, it will replace the query with the new value and will use `split-query` to parse that query into an array of `Pair`s. The primary representation of the queryo object is the value returned by `query-form`. The `query` is cached to keep the value stored in it when this method is called to set it or when `URI::Query` is constructed from a string. Any modification to the internal array of pairs, though, will clear this cache. It will be generated the next time the `query` method is called and that string will be cached. ### method query-form method query-form(Query:D:) returns Array:D method query-form(Query:D: *@new, *%new) returns Array:D This method returns the array of `Pair`s that store the internal representation of the URI query component. When passed an array of `Pair`s, it will replace the current value with that array. A quick note about the way pairs are passed as parameters to a method, you most likely want to avoid passing values as named parameters. If values are passed using unquoted strings, they will be treated as named parameters, which is most likely what you want: my $q = URI::Query.new; # Prefer this $q.query-form('foo' => 1, 'bar' => 2, 'foo' => 3); # Avoid these $q.query-form(foo => 1, bar => 2, foo => 3); $q.query-form(:foo(1), :bar(2), :foo(3)); The latter two will result in the first "foo" pair being lost. Named parameters assume unique names and the latter "foo" key will effectively override the former. That said, the method will allow hashes to be passed in, if that is your preference. ### method of method of() Always returns `Pair`. ### method iterator method iterator(Query:D:) returns Iterator:D Returns the iterator on the internal array of `Pair`s. ### method postcircumflex:<[ ]> method postcircumflex:<[ ]> returns Pair This permits positional access to each `Pair` stored internally. You may use this to get a `Pair`, set a `Pair`, test for existence, or delete. ### method postcircumflex:<{ }> method postcircumflex:<{ }> This permits associative access to the values stored internally by key. What is returned here when fetching values depends on the setting in `hash-format`, a list of one or more values or `Nil`, by default. You can use this for getting, setting, existence testing, or deletion. ### method keys method keys(Query:D:) returns Seq:D This method returns all the keys of the query in order. ### method values method values(Query:D:) returns Seq:D This method returns all the values of the query in order. ### method kv method kv(Query:D:) returns Seq:D This method returns a sequence alternating the keys and values of the query in order. ### method pairs method kv(Query:D:) returns Seq:D This method returns a copy of the internal representation of the query string array. ### method pop method pop(Query:D:) returns Pair This method removes the last Pair from the array of pairs and returns it. ### method push method push(Query:D: *@new) This method adds the given pairs to the end of the array of pairs in the query using push semantics. ### method append method append(Query:D: *@new) This method adds the given pairs to the end of the array of pairs in the query using append semantics. ### method shift method shift(Query:D:) returns Pair This method removes the first Pair from the array of pairs and returns it. ### method unshift method unshift(Query:D: *@new) This method adds the given pairs to the front of the array of pairs in the query using unshift semantics. ### method prepend method prepend(Query:D: *@new) This method adds the given pairs to the front of the array of pairs in the query using prepend semantics. ### method splice method splice(Query:D: $start, $elems?, *@replacement) This method removes a `$elems` number of pairs from the array of pairs in the query starting at index `$start`. It then inserts the pairs in `@replacement` into that part of the array (if any are given). ### method elems method elems(Query:D:) returns Int:D Returns the number of pairs stored in the query. ### method end method end(Query:D:) returns Int:D Returns the index of the last pair stored in the query. ### method Bool method Bool(Query:D:) returns Bool:D Returns `True` if the at least one pair is stored in the query or `False` otherwise. ### method Int method Int(Query:D:) returns Int:D Returns `elems`. ### method Numeric method Numeric(Query:D:) returns Int:D Returns `elems`. ### method gist method gist(Query:D:) returns Str:D Returns the `query`. ### method Str method Str(Query:D:) returns Str:D Returns the `query`. EXCEPTIONS ========== X::URI::Invalid --------------- This exception is thrown in many places where the URI is being parsed or manipulated. If the string being parsed is not a valid URI or if certain manipulations of the URI object would cause it to become an invalid URI, this exception may be used. It provides a `source` accessor, which returns the string that was determined to be invalid. X::URI::Path::Invalid --------------------- In some cases where an attempt is made to set `path` to an invalid value, this exception is thrown. The `source` field will name the invalid URI. Strictly speaking, the URI might be valid, but will not parse the same way as given. To make it clear that this is the case, the `path` field names the invalid path part. In cases where the segments have been modified in an invalid way, the first invalid segment will be set in `bad-segment`. AUTHORS ======= Contributors to this module include: * Ronald Schmidt (ronaldxs) * Moritz Lentz (moritz) * Nick Logan (ugexe) * Tobias Leich (FROGGS) * Jonathan Stowe (jonathanstowe) * Justin DeVuyst (jdv) * Solomon Foster (colomon) * Roman Baumer (rba) * Zoffix Znet (zoffixznet) * Ahmad M. Zawawi (azawawi) * Gabor Szabo (szabgab) * Samantha McVey (samcv) * Pawel Pabian (bbkr) * Rob Hoelz (hoelzro) * radiak * Paul Cochrane (paultcochrane) * Steve Mynott (stmuk) * timo * David Warring (dwarring) * Sterling Hanenkamp (zostay) COPYRIGHT & LICENSE =================== Copyright © 2017 Ronald Schmidt. © 2015 - Raku Community Authors This software is licensed under the same license as Raku itself, please see the [LICENSE](LICENSE) file. raku-uri-0.3.5/appveyor.yml000066400000000000000000000015341414637465300156660ustar00rootroot00000000000000# URI AppVeyor YAML control file # essentially copied from the GitHub PDF-p6 project os: Visual Studio 2015 platform: x64 environment: matrix: - test_moar: 2017.05 - test_moar: '' #latest install: - '"C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd" /x64' - choco install strawberryperl --allow-empty-checksums - SET PATH=C:\strawberry\c\bin;C:\strawberry\perl\site\bin;C:\strawberry\perl\bin;%PATH% - git clone https://github.com/tadzik/rakudobrew %USERPROFILE%\rakudobrew - SET PATH=%USERPROFILE%\rakudobrew\bin;%PATH% - rakudobrew build moar %TEST_MOAR% - rakudobrew build zef - cd %APPVEYOR_BUILD_FOLDER% - zef --depsonly install . - zef build . # not sure URI needs this build: off shallow_clone: true test_script: - prove -ve "perl6 -Ilib" - zef install . raku-uri-0.3.5/lib/000077500000000000000000000000001414637465300140415ustar00rootroot00000000000000raku-uri-0.3.5/lib/IETF/000077500000000000000000000000001414637465300145705ustar00rootroot00000000000000raku-uri-0.3.5/lib/IETF/RFC_Grammar.rakumod000066400000000000000000000026311414637465300202360ustar00rootroot00000000000000unit class IETF::RFC_Grammar; # Thanks in part to Aaron Sherman # and his article http://essays.ajs.com/2010/05/writing-perl-6-uri-module.html # for inspiring this. # below should be constant when implemented ... my %rfc_grammar_build = ( 'rfc3986' => 'IETF::RFC_Grammar::URI' ); my %rfc_grammar; # Hack to give hint to ufo/Panda to build in the right order. # remove some day when module builders are upgraded use IETF::RFC_Grammar::URI; has $.rfc; has $.grammar; has $.parse-result; method parse($parse_str, :$rule = 'TOP') { $!parse-result = $!grammar.parse($parse_str, :$rule); } method subparse($parse_str, :$rule = 'TOP') { $!parse-result = $!grammar.subparse($parse_str, :$rule); } submethod BUILD(:$!rfc, :$!grammar) {} method new(Str $rfc, $grammar?) { my $init_grammar = $grammar; if ( (! $init_grammar.can('parse')) and %rfc_grammar_build{$rfc}:exists; ) { unless %rfc_grammar{$rfc}:exists { my $module = %rfc_grammar_build{$rfc}; # less disruptive fix to RT126390 unless ($rfc eq 'rfc3986') { require ::($module); } %rfc_grammar{$rfc} = ::($module); } $init_grammar = %rfc_grammar{$rfc}; } if (! $init_grammar.can('parse')) { die "Need either rfc with known grammar or grammar"; } return self.bless(:$rfc, :grammar($init_grammar)); } method parse_result { $!parse-result } raku-uri-0.3.5/lib/IETF/RFC_Grammar/000077500000000000000000000000001414637465300166505ustar00rootroot00000000000000raku-uri-0.3.5/lib/IETF/RFC_Grammar/IPv6.rakumod000066400000000000000000000026761414637465300210330ustar00rootroot00000000000000use v6; # Taken/Copied with relatively minor translation to Perl6 # from RFC 3986 (http://www.ietf.org/rfc/rfc3986.txt) unit grammar IETF::RFC_Grammar::IPv6:ver<0.02>; token IPv6address { [ <.h16> ':' ] ** 6 <.ls32> | '::' [ <.h16> ':' ] ** 5 <.ls32> | [ <.h16> ]? '::' [ <.h16> ':' ] ** 4 <.ls32> | [ [ <.sep-h16> ]? <.h16> ]? '::' [ <.h16> ':' ] ** 3 <.ls32> | [ [ <.sep-h16> ] ** 0..2 <.h16> ]? '::' [ <.h16> ':' ] ** 2 <.ls32> | [ [ <.sep-h16> ] ** 0..3 <.h16> ]? '::' <.h16> ':' <.ls32> | [ [ <.sep-h16> ] ** 0..4 <.h16> ]? '::' <.ls32> | [ [ <.sep-h16> ] ** 0..5 <.h16> ]? '::' <.h16> | [ [ <.sep-h16> ] ** 0..6 <.h16> ]? '::' }; # token avoiding backtracking happiness token sep-h16 { [ <.h16> ':' ] } token ls32 { [<.h16> ':' <.h16>] | <.IPv4address> }; token h16 { <.xdigit> ** 1..4 }; token IPv4address { <.dec-octet> '.' <.dec-octet> '.' <.dec-octet> '.' <.dec-octet> }; token dec-octet { '25' <[0..5]> | # 250 - 255 '2' <[0..4]> <.digit> | # 200 - 249 '1' <.digit> ** 2 | # 100 - 199 <[1..9]> <.digit> | # 10 - 99 <.digit> # 0 - 9 } # vim:ft=perl6 raku-uri-0.3.5/lib/IETF/RFC_Grammar/URI.rakumod000066400000000000000000000056751414637465300207100ustar00rootroot00000000000000use v6; # Taken/Copied with relatively minor translation to Perl6 # from RFC 3986 (http://www.ietf.org/rfc/rfc3986.txt) # Rule names moved from snake case with _ to kebab case with - # 9/2015. The rfc grammar is specified in kebab case. use IETF::RFC_Grammar::IPv6; unit grammar IETF::RFC_Grammar::URI:ver<0.02> is IETF::RFC_Grammar::IPv6; token TOP { }; token TOP-non-empty { | }; token URI-reference { | }; token absolute-URI { ':' <.hier-part> [ '?' query ]? }; token relative-ref { [ '?' ]? [ '#' ]? }; token relative-part { | '//' | | | }; token relative-ref-non-empty { [ '?' ]? [ '#' ]? }; token relative-part-non-empty { | '//' | | }; token URI { ':' ['?' ]? [ '#' ]? }; token hier-part { | '//' | | | }; token scheme { <.uri-alpha> <[\-+.] +uri-alpha +digit>* }; token authority { [ '@' ]? [ ':' ]? }; token userinfo { [ ':' | ]* }; # the rfc refers to username:password as deprecated token likely-userinfo-component { <+unreserved +sub-delims>+ | <.pct-encoded>+ }; token host { | | }; token port { <.digit>* }; token IP-literal { '[' [ | ] ']' }; token IPvFuture { 'v' <.xdigit>+ '.' <[:] +unreserved +sub-delims>+ }; token reg-name { [ <+unreserved +sub-delims> | <.pct-encoded> ]* }; token path-abempty { [ '/' ]* }; token path-absolute { '/' [ [ '/' ]* ]? }; token path-noscheme { [ '/' ]* }; token path-rootless { [ '/' ]* }; token path-empty { <.pchar> ** 0 }; # yes - zero characters token segment { <.pchar>* }; token segment-nz { <.pchar>+ }; token segment-nz-nc { [ <+unenc-pchar - [:]> | <.pct-encoded> ] + }; token query { <.fragment> }; token fragment { [ <[/?] +unenc-pchar> | <.pct-encoded> ]* }; token pchar { <.unenc-pchar> | <.pct-encoded> }; token unenc-pchar { <[:@] +unreserved +sub-delims> }; token pct-encoded { '%' <.xdigit> <.xdigit> }; token unreserved { <[\-._~] +uri-alphanum> }; token reserved { <+gen-delims +sub-delims> }; token gen-delims { <[:/?\#\[\]@]> }; token sub-delims { <[;!$&'()*+,=]> }; token uri-alphanum { <+uri-alpha +:N +:S> }; token uri-alpha { }; # vim:ft=perl6 raku-uri-0.3.5/lib/URI.rakumod000066400000000000000000001225631414637465300160750ustar00rootroot00000000000000use URI::Path; use URI::Query; unit class URI:auth:ver; use IETF::RFC_Grammar; use IETF::RFC_Grammar::URI; use URI::Escape; need URI::DefaultPort; constant &split-query is export(:split-query) = &URI::Query::split-query; class X::URI::Invalid is Exception { has $.source; method message { "Could not parse URI: $!source" } } class X::URI::Path::Invalid is X::URI::Invalid { has $.path; has $.bad-segment; method message { qq[Could not parse path "$!path" as part of URI: $.source] ~ ($!bad-segment ?? qq[ with bad segment "$!bad-segment"] !! '') } } our subset Scheme of Str where /^ [ '' || ] $/; our subset Userinfo of Str where /^ [ ] $/; our subset Host of Str where /^ [ ] $/; our subset Port of UInt; class Authority { has Userinfo:D $.userinfo is default('') is rw = ''; has Host:D $.host is default('') is rw = ''; has Port $.port is rw; multi method new(Authority:U: Match:D $auth) { my Str $userinfo = do with $auth { .Str } else { '' } my Str $host = "$auth".lc; my UInt $port = do with $auth { .Int } else { UInt } self.new(:$userinfo, :$host, :$port); } multi method gist(Authority:D: --> Str ) { my $authority = ''; $authority ~= "$!userinfo@" if $!userinfo; $authority ~= $!host; $authority ~= ":$!port" if $!port; $authority; } multi method Str(Authority:D: --> Str ) { $.gist } } our subset Fragment of Str where /^ $/; has Str $.query-form-delimiter; has $.grammar; has Bool $.match-prefix = False; has Path $.path = Path.new; has Scheme $.scheme = ''; has Authority $.authority is rw; has Query $.query = Query.new(''); has Fragment $.fragment is rw = ''; has %!query-form; # cache query-form has $!uri; # use of this now deprecated method parse(URI:D: Str() $str, Bool :$match-prefix = $!match-prefix) { # clear string before parsing my Str $c_str = $str; $c_str .= subst(/^ \s* ['<' | '"'] /, ''); $c_str .= subst(/ ['>' | '"'] \s* $/, ''); $!scheme = ''; $!authority = Nil; $!path = Path.new; $!query.query(''); $!fragment = ''; $!uri = Mu; $!match-prefix = $match-prefix; if $!match-prefix { $!grammar.subparse($c_str); } else { $!grammar.parse($c_str); } # hacky but for the time being an improvement if (not $!grammar.parse-result) { X::URI::Invalid.new(source => $str).throw } # now deprecated $!uri = $!grammar.parse-result; my $comp_container = $!grammar.parse-result || $!grammar.parse-result; $!scheme = .lc with $comp_container; $!query.query(.Str) with $comp_container; $!fragment = .lc with $comp_container; $comp_container = $comp_container || $comp_container; with $comp_container -> $auth { $!authority = Authority.new($auth); } $!path = Path.new($comp_container, :$!scheme); } # deprecated old call for parse method init ($str) is DEPRECATED("parse") { $.parse($str); } # new can pass alternate grammars some day ... submethod BUILD(:$match-prefix) { $!match-prefix = ? $match-prefix; $!grammar = IETF::RFC_Grammar.new('rfc3986'); } multi method new(URI:U: $uri, Bool :$match-prefix) { my $obj = self.bless(:$match-prefix); $obj.parse($uri) if $uri.defined; $obj; } multi method new(URI:U: :$uri, Bool :$match-prefix) { self.new($uri, :$match-prefix); } multi method COERCE(Str:D $uri) { self.new($uri) } method has-scheme(URI:D: --> Bool:D ) { $!scheme.chars > 0; } method has-authority(URI:D: --> Bool:D ) { $!authority.defined; } my regex path-authority { [ $ = ] } my regex path-scheme { [ $ = | $ = | $ = ] } my regex path-plain { [ $ = | $ = | $ = ] } method !check-path( :$has-authority = $.has-authority, :$has-scheme = $.has-scheme, :$path = $.path, :$source = $.gist, ) { given X::URI::Path::Invalid.new(:$path, :$source) -> \ex { if $has-authority { ex.throw unless $path ~~ /^ $/; return $; } elsif $has-scheme { ex.throw unless $path ~~ /^ $/; return $; } else { ex.throw unless $path ~~ /^ $/; return $; } } } multi method scheme(URI:D: --> Scheme:D ) { $!scheme } multi method scheme(URI:D: Str() $scheme --> Scheme:D ) { self!check-path(:has-scheme, :source(self!gister(:$scheme))); $!scheme = $scheme; } multi method userinfo(URI:D: --> Userinfo ) { return .userinfo with $!authority; ''; } multi method userinfo(URI:D: Str() $userinfo --> Userinfo ) { with $!authority { .userinfo = $userinfo; } elsif $userinfo { self!check-path(:has-authority, :source(self!gister(:authority("$userinfo@")))); $!authority .= new(:$userinfo); $!authority.userinfo; } } multi method host(URI:D: --> Host ) { return .host with $!authority; ''; } multi method host(URI:D: Str() $host --> Host ) { with $!authority { .host = $host; } elsif $host { self!check-path(:has-authority, :source(self!gister(:authority($host)))); $!authority .= new(:$host); $!authority.host; } } method default-port(URI:D: --> Port ) { URI::DefaultPort.scheme-port($.scheme) } method default_port(URI:D: --> Port ) { $.default-port } # artifact form multi method _port(URI:D: --> Port ) { return .port with $!authority; Nil; } multi method _port(URI:D: Nil --> Port ) { .port = Nil with $!authority; Nil; } multi method _port(URI:D: Int() $port --> Port ) { with $!authority { .port = $port; } else { self!check-path(:has-authority, :source(self!gister(:authority(":$port")))); $!authority .= new(:$port); $!authority.port; } } multi method port(URI:D: --> Port ) { $._port // $.default-port } multi method port(URI:D: |c --> Port ) { self._port(|c) // $.default-port } proto method authority(|c) { * } multi method authority(URI:D: Str() $authority --> Authority:D ) { my $gist; without $!authority { self!check-path(:has-authority, source => $gist //= self!gister(:$authority), ); } $!grammar.parse($authority, rule => 'authority'); if not $!grammar.parse-result { X::URI::Invalid.new( source => $gist // self!gister(:$authority), ).throw; } $!authority = Authority.new($!grammar.parse-result); } multi method authority(URI:D: Nil --> Authority:U ) { with $!authority { self!check-path(:!has-authority, source => self!gister(authority => ''), ); } $!authority = Nil; } multi method authority(URI:D: --> Authority ) is rw { $!authority; } multi method path(URI:D: --> Path:D ) { $!path } method !make-path(Str $path --> Path:D ) { if $path { my $comp = self!check-path( :$path, source => self!gister(:$path), ); Path.new($comp); } else { Path.new; } } multi method path(URI:D: Str() $path --> Path:D ) { $!path = self!make-path($path); } multi method segments(URI:D: --> List:D ) { $!path.segments } multi method segments(URI:D: @segments where *.elems > 0 --> List:D ) { my $path = @segments.join('/'); given self!gister(:$path) -> $source { for @segments -> $bad-segment { X::URI::Path::Invalid.new( :$source, :$path, :$bad-segment, ).throw if $bad-segment.contains("/"); } self!check-path(:$path, :$source); } $!path = Path.new( path => $path, segments => @segments.map(*.Str), ); $!path.segments; } multi method segments(URI:D: $first-segment, *@remaining-segments --> List:D ) { my @segments = $first-segment, |@remaining-segments; self.segments(@segments); } # The absolute and relative methods are artifacts carried over from an old # version of the p6 module. The perl 5 module does not provide such # functionality. The Ruby equivalent just checks for the presence or # absence of a scheme. The URI rfc does identify absolute URIs and # absolute URI paths and these methods somewhat confused the two. Their # functionality at the URI level is no longer seen as needed and is # being removed. method absolute is DEPRECATED { return Bool.new; } method relative is DEPRECATED { return Bool.new; } method is-absolute { so ($!scheme || $.host) } method is-relative { ! $.is-absolute } method directory(URI:D:) { my Str $dir = $!path.Str; $dir .= subst(/<- [/]>*$/, ''); $dir ||= self.is-absolute ?? '/' !! './'; my Path $path = self!make-path($dir); self.clone: :$path; } method rel2abs(URI:D: URI:D $base --> URI:D) { if $.is-absolute { self; } else { my URI::Path $path = $!path.rel2abs($base); $base.clone: :$path; } } multi method query(URI:D: --> URI::Query:D ) { $!query } multi method query(URI:D: Str() $new --> URI::Query:D ) { $!query.query($new); $!query } multi method query(URI:D: *@new, *%bad --> URI::Query:D ) { warn 'The query was passed values as named arguments which are ignored. Did you mean to make sure your pairs were quoted or passed in a list instead?' if %bad; $!query.query-form(@new); $!query } proto method query-form(|c) { * } multi method query-form(URI:D: |c) is DEPRECATED("method query") { $!query.query-form(|c) } method query_form() is DEPRECATED("method query") { $.query } method path-query(URI:D: --> Str:D ) { $.query ?? "$.path?$.query" !! "$.path" } method path_query { $.path-query } #artifact form multi method fragment(URI:D: --> Fragment ) { return-rw $!fragment } multi method fragment(URI:D: Str() $new --> Fragment ) { $!fragment = $new } method frag(URI:D:) { $.fragment } method !gister( :$scheme = $.scheme, :$authority = $.authority, :$path = $.path, :$query = $.query, :$fragment = $.fragment, ) { my Str $s; $s ~= "$scheme:" if $scheme; $s ~= "//$authority" if $authority; $s ~= $path; $s ~= "?$query" if $query; $s ~= "#$fragment" if $fragment; $s; } multi method gist(URI:D: --> Str:D ) { self!gister } multi method Str(URI:D: --> Str:D ) { $.gist } # chunks now strongly deprecated # it's segments in p5 URI and segment is part of rfc so no more chunks soon! method chunks() is DEPRECATED("method segments") { $!path.segments; } method uri() is DEPRECATED("parse-result") { $!uri; } multi method query-form() { $!query } multi method query-form(|c) { $!query.query-form(|c) } =begin pod =head1 NAME URI — Uniform Resource Identifiers (absolute and relative) =head1 SYNOPSIS use URI; my $u = URI.new('http://example.com/foo/bar?tag=woow#bla'); # Look at the parts say $u.scheme; #> http say $u.authority; #> example.com say $u.host; #> example.com say $u.port; #> 80 say $u.path; #> /foo/bar say $u.query; #> tag=woow say $u.fragment; #> bla # Modify the parts $u.scheme('https'); $u.authority('example.com:8443'); $u.path('/bar/foo'); $u.query('x=1&y=2'); # OR $u.fragment('cool'); say "$u"; #> https://example.com:8443/bar/foo?x=1&y=2#cool # Authority is an object, but may be undefined with $u.authority { say .userinfo; # none set, no output say .host; #> example.com say .port; #> 8443 # It is mutable too .userinfo('bob'); say $u.authority; #> bob@example.com:8443 } # Path is an object, always defined, but immutable with $u.path { say .path; #> /bar/foo .say for .segments; #> bar\nfoo\n } # Query is an object, always defined, mutable with $u.query { say .query; #> x=1&y=2 .query('foo=1&foo=2'); say .query-form[0]; #> 1 say .query-form[1]; #> 2 .query-form.push: 'bar' => 'ok'; say .query; #> foo=1&foo=2&bar=ok .query(''); .query-form = 123; say .query; #> abc=123 } =head1 DESCRIPTION A URI object represents a parsed string that abides by the grammar defined in RFC 3986 for Universal Resource Identifiers. The object then works to enforce the rules defined in the RFC for any modifications made to it. As of this writing, The URI class is scheme agnostic. It will verify that URI is correct, in general, but not correct for a given scheme. For example, C would be considered a valid URI even though that is not a valid URI according to the rules specific to the C scheme. This class uses "heavy accessors". From the SYNOPSIS, you may have noted that assignment to accessors is not used. This is because nearly all accessors in this class do some extra work of parsing, validating, and cross-referencing with other fields to guarantee that the URI is correct before making a change. my $u = URI.new; $u.path = '/foo/bar'; # ERROR: «Cannot modify an immutable URI::Path» $u.path('/foo/bar'); # WORKS! This mutator pattern is meant to reflect the internal complexity. =head2 SCHEME, AUTHORITY, AND PATH In RFC 3986 URIs, the scheme, the authority, and the path are related. This should not matter most of the time, but to avoid problems when setting these three, it is safest to set them in this order: my $u = URI.new; $u.path(''); $u.scheme($my-scheme); $u.authority($my-host); $u.path($my-path); This is because an empty path is permitted in any case, but the format of the path is limited whether an authority and scheme are set. With an authority set (i.e., the URI either starts with "//" or with "scheme://"), a non-empty path must start with a "/" and may start with empty path segments, e.g. "scheme://foo//" is valid. When there's no authority set, but a scheme is used (e.g., it starts with "scheme:", but not "scheme://"), a non-empty path may either start with a "/" or not, but must contain one or more other characters in the first segment of the path. Thus, the following code will fail: my $u = URI.new('scheme:'); $u.path('//'); # ERROR: «Could not parse path "//" as part of URI: scheme://» When there's no authority and no scheme used, a non-empty path may start with a "/" or not, but must contain one or more other characters in the first segment. These rules are enforced whenever setting or clearing the scheme, authority, or path. If the resulting URI object would be invalid a C exception will be thrown. =head2 QUERY The C method of this class returns a C object.This is a special object that is C, C, and C. That is, it can be bound to an array variable, a hash variable, and iterated using a loop. If stringified, it will return a URI-encoded query. If used as an array, will act like somewhat like an array of Cs. If used as a hash, will provide a map from query keys to query values. The native internal representation is a list of Cs as this is the best match for how queries are defined. Because of this, there's a problem when using a query as a hash: duplicate pairs are valid. To handle this, the C object always returns a list of values, sorted in the order they appear for each key. For example: my $u = URI.new('?foo=1&bar=2&foo=3'); say $u.query[0]; #> 1 say $u.query[1]; #> 3 say $u.query[0]; #> 2 Older versions of the URI module handled this differently, using a mixed value representation. In order to gain some backwards compatibility, this is still supported by setting the C: # Continues from previous $u.query.hash-format = URI::Query::Mixed; say $u.query[0]; #> 1 say $u.query[1]; #> 3 # The bar value is NOT a list now say $u.query; #> 2 # Return to the default mode $u.query.hash-format = URI::Query::Lists; However, the list version is safer and may likely work with most existing code that worked with mixed before. Another mode is provided to force single values, which is often how applications treat these out of convenience. In that case, only the last value will be kept: # Continues from previous $u.query.hash-format = URI::Query::Singles; # These are never lists now say $u.query; #> 3 say $u.query; #> 2 The C mode is default and recommended mode. =head2 GRAMMAR This class will keep a copy of the result of parsing the URI string for you. If you are interested in precise details of the parse tree, you get them using the C method: my $host-in-grammar = $u.grammar.parse-result; if $host-in-grammar { say 'Host looks like registered domain name - approved!'; } else { say 'Sorry we do not take ip address hosts at this time.'; say 'Please use registered domain name!'; } The C grammar sticks close to the BNF defined in RFC 3986. =head2 PARTIAL MATCHING Many times a URI you are interested in is embedded within another string. This class will allow you to parse URIs out of a larger string, so long as the URI is at the start. This is done by setting the C<:match-prefix> option during construction or when calling C: { # require whole string matches URI and throw exception otherwise .. my $u_v = URI.new('http://?#?#'); CATCH { when X::URI::Invalid { ... } } } my $u_pfx = URI.new('http://example.com } function(var mm){', :match-prefix); =head1 METHODS =head2 method new multi method new(URI:U: Str() $uri, Bool :$match-prefix) returns URI:D multi method new(URI:U: Str() :$uri, Bool :$match-prefix) returns URI:D These construct a new C object and return it. The given C<$uri> value is converted to a string and then parsed using the C method. If C<:match-prefix> is set, then the grammar will be allowed to match a prefix of the given input string rather than requiring a total match. The C<:match-prefix> given also becomes the default value for any figure calls to C. Throws a C exception if the URI cannot be parsed. =head2 method parse method parse(URI:D: Str() $str, Bool :$match-prefix = $.match-prefix) This method allows an existing URI object to be reused to parse another string. This parses the given string and replaces all internal state of the object with values for the new parse. The given C<:match-prefix> flag becomes the new default when set. Throws a C exception if the URI cannot be parsed. =head2 method grammar method grammar(URI:D:) returns IETF::RFC_Grammar:D Returns the object used to parse and store the state of the parse. =head2 method match-prefix method match-prefix(URI:D:) returns Bool:D Returns True if the most recent call to C (or C) allowed a prefix match or False if a total match was required. This is the default value of any future call to C. =head2 method scheme multi method scheme(URI:D:) returns URI::Scheme:D multi method scheme(URI:D: Str() $scheme) returns URI::Scheme:D Returns the scheme part of the URI. This is a string that must match the C subset type. The second form allows the scheme to be replaced with a new scheme. It is legal for the scheme to be set to an empty string, which has the effect of making the URI scheme-less. This will throw an C exception if adding or removing the scheme will make the URI invalid. See SCHEME, AUTHORITY, AND PATH section for additional details. =head2 method authority multi method authority(URI:D:) returns URI::Authority multi method authority(URI:D: Nil) returns URI::Authority:U multi method authority(URI:D: Str() $new) returns URI::Authority:D Returns the C for the current URI object. This may be an undefined type object if no authority has been set or found during parse. When passed a string, the string will have the authority parsed out of it and a new authority object will be used to store the parsed information. An empty authority is valid. When passed C, the authority will be cleared. When passing an argument, this will throw an C exception if setting or clearing the authority on the URI will make the URI invalid. See SCHEME, AUTHORITY, AND PATH section for details. The authority is made up of three components: userinfo, host, and port. Additional methods are provided for accessing each of those parts separately. =head2 method userinfo multi method userinfo(URI:D:) returns URI::Userinfo:D multi method userinfo(URI:D: Str() $new) returns URI::Userinfo:D The userinfo is an optional component of the URI authority. This method returns the current userinfo or an empty string. Setting this method will cause a C to be constructed and C to be set, if it is not already defined. This may result in a C exception being thrown if adding an authority will make the path invalid. =head2 method host multi method host(URI:D:) returns URI::Host:D multi method host(URI:D: Str() $new) returns URI::Host:D The host is a component of the URI authority. This method returns the current host or an empty string. Setting this method will cause a C to be constructed and C to be set, if it is not already defined. This may result in a C exception being thrown if adding an authority will make the path invalid. =head2 method default-port method default-port(URI:D:) returns URI::Port This method applies the C method of C to the scheme set on this object. Basically, a shortcut for: my $u = URI.new("..."); my $port = URI::DefaultPort.scheme-port($u.scheme); It returns the usual port for the named scheme. =head2 method _port multi method _port(URI:D:) returns URI::Port multi method _port(URI:D: Nil) returns URI::Port:U multi method _port(URI:D: Int() $new) returns URI::Port:D When an authority is set on the URI, this gets or sets the authority's port. This differs from C, which returns either the port set or the C. This method returns just the port. If no authority is set or no port is set, this returns an undefined value (i.e., an C type object). Setting this method will cause a C to be constructed and C to be set, if it is not already defined. This may result in a C exception being thrown if adding an authority will make the path invalid. =head2 method port multi method port(URI:D:) returns URI::Port multi method port(URI:D: Nil) returns URI::Port multi method port(URI:D: Int() $new) returns URI::Port When retrieving a value from the object, this method returns either the port set on the authority or the default port for the current URI scheme. It may return an undefined value (i.e., an C type object) if there is no port set and no known default port for the current scheme or no scheme set. When setting a value on the object, this method sets the port on the authority. Setting this method will cause a C to be constructed and C to be set, if it is not already defined. This may result in a C exception being thrown if adding an authority will make the path invalid. =head2 method path multi method path(URI:D:) returns URI::Path:D multi method path(URI:D: Str() $path) returns URI::Path:D Path is the main required element of a URI, but may be an empty string. This method returns the current setting for the path as a C object. It also allows setting a new path, which will construct a new C object. This method will throw a C exception if the path is not valid for the current scheme and authority settings. =head2 method segments multi method segments(URI:D:) returns List:D multi method segments(URI:D: @segments where *.elems > 0) returns List:D multi method segments(URI:D: $first-segment, *@remaining-segments) returns List:D Returns the path segments (i.e., the parts between slashes). This is a shortcut for: my $u = URI.new("..."); my @s = $u.path.segments; The number of segments is equal to the number of slashes in the original path plus one. The segments are constructed so that joining the segments by a slash (/) will give you the original path. You may pass a list in to replace the segments with a new value. This will throw a C exception if any of the segments contain a slash or if the set path will cause the URI to become invalid. Be sure when setting segments to include an initial empty string if you want the path to start with a slash. =head2 method query multi method query(URI:D:) returns URI::Query:D multi method query(URI:D: Str() $new) returns URI::Query:D multi method query(URI:D: *@new) returns URI::Query:D Accesses or updates the query associated with the URI. This is returns as a C object. This will always be defined, but may be empty. When passed a string, the string will be parsed using C and the query object will be updated. When passed a list, the list of Cs given will be used to setup a new query that way. When the list form is used, any named parameters passed will be ignored (they will not be used to fill the query) and a warning will be issued. =head2 method path-query method path-query(URI:D:) returns Str:D Returns the path and query as a string joined by a question mark ("?"). It will return just the path as a string if the query is empty. =head2 method fragment multi method fragment(URI:D:) returns URI::Fragment:D multi method fragment(URI:D: Str() $new) returns URI::Fragment:D Returns the URI fragment, which is always defined, but may be an empty string. If passed a value, it will set the fragment. =head2 method gist multi method gist(URI:D:) returns Str:D Reconstructs the URI from the components and returns it as a string. =head2 method Str multi method Str(URI:D:) returns Str:D Reconstructs the URI from the components and returns it as a string. =head1 SUBROUTINES =head2 sub split-query sub split-query(Str() $query, :$hash-format = URI::Query::None) This routine will slice and dice a query string, which is useful when parsing URIs and may also be useful when parsing POST entities that are encoded as C. This routine is exported with the C<:split-query> tag or can be used with the full namespace, C. With just the required string, this routine will parse that string and return a list of Cs mapping each query form key to its respective value. The order is preserved. This is used by C during construction. For example: my @qf = URI::split-query("foo=1&bar%20=2&foo=3"); dd @qf; #> Array @qf = [:foo("1"), "bar " => ("2"), :foo("3")] Notice that this will perform a C operation of keys and values in the process so the values you receive have had the URI encoded characters decoded. You can retrieve the query string as a hash instead by passing the C<:hash-format> option. This works exactly as it does for C. The options for C<:hash-format> include: =head3 URI::Query::Lists Every key is mapped to a list of one or more values. From the example input, a structure like the following is returned: my %qf = URI::split-query("foo=1&bar%20=2&foo=3", hash-format => URI::Query::Lists, ); dd %qf; #> Hash %qf = {"bar " => (["2"]), :foo(["1", "3"])} This is also the default if you just pass C<:hash-format> as a boolean option. =head3 URI::Query::Mixed Every key is mapped to either a list of two or more values or directly to a single value, like the following: my %qf = URI::split-query("foo=1&bar%20=2&foo=3", hash-format => URI::Query::Mixed, ); dd %qf; #> Hash %qf = {"bar " => ("2"), :foo(["1", "3"])} =head3 URI::Query::Singles Every key is mapped to a single value, which will be the last value encountered in the input, like this: my %qf = URI::split-query("foo=1&bar%20=2&foo=3", hash-format => URI::Query::Mixed, ); dd %qf; #> Hash %qf = {"bar " => ("2"), :foo("3")} =head1 HELPER SUBSETS =head2 URI::Scheme This is a subset of C that only accepts valid schemes. =head2 URI::Userinfo This is a subset of C that only accepts valid userinfo. =head2 URI::Host This is a subset of C that only accepts valid hosts. =head2 URI::Port This is a subset of C. =head2 URI::Query::ValidQuery This is a subset of C that only accepts valid query strings. =head2 URI::Fragment This is a subset of C that only accepts valid URI fragments. =head1 HELPER CLASSES =head2 URI::Authority The C method of a URI constructs or returns this object. It is recommended that you do not costruct a C object directly, but let the methods of C handle construction. =head3 method userinfo method userinfo(URI::Authority:D:) is rw returns URI::Userinfo:D This is a simple setter/getter for the userinfo on the authority. It must be defined, but may be the empty string. If not empty, it must be valid for the userinfo component of a URI. =head3 method host method host(URI::Authority:D:) is rw returns URI::Host:D This is a simple setter/getter for the host part of the URI authority. It must be defined, but may be the empty string. If not empty, must be a valid host value, which may be an IP address or registered name. =head3 method port method port(URI::Authority:D:) is rw returns URI::Port This is a simple setter/getter for the port part of the URI authority. It may be set to an undefined value if no explicit port is set in the authority. If defined, it must an unsigned integer. =head3 method gist multi method gist(URI::Authority:D:) returns Str:D Returns the string representation of the URI authority. For example, my $u = URI.new("http://steve@example.com:8008"); # say calls .gist say $u.authority; #> "steve@example.com:8080"; =head3 method Str multi method gist(URI::Authority:D:) returns Str:D Stringifies identical to C. =head2 URI::Path This class is used to represent URI path components. It is recommended that you do not construct a C object directly, but rely on the C setter in C instead. These objects are immutable. Please use methods on C to make changes. =head3 method path method path(URI::Path:D:) returns Str:D Returns the string representation of the path. =head3 method segments method segments(URI::Path:D:) returns List:D Returns a list representation of the path segments. In a URI, the path segments are the strings between slashes ("/"). =head3 method gist method gist(URI::Path:D:) returns Str:D Returns the C. =head3 method Str method Str(URI::Path:D:) returns Str:D Returns the C. =head2 URI::Query This class is used to represent a URI query component. This class may be safely constructed and used independently of the URI object. It behaves as both a positional and associative object and is iterable. Internally, it is stored as an C of Cs. You must not treat this object purely as you would an C or purely as you would a C as some methods will not work the way you expect. The performance of the associative methods is not guaranteed and is probably going to be relatively slow. This implementation values simplicity and accuracy of representation to CPU performance. If you need something that is better for CPU performance, you should investigate the use of another library, such as C or sub-class to provide a higher performance implementation of C's associative methods. =head3 method new multi method new(Str() $query, URI::Query::HashFormat :$hash-format = URI::Query::Lists) returns URI::Query:D multi method new(:$query, URI::Query::HashFormat :$hash-format = URI::Query::Lists) returns URI::Query:D Constructs a new C from the given string, which may be empty. Unlike C, which permits boolean values for the C option, C requires a C value and will reject a boolean (because the boolean does not make sense in this case). =head3 enum HashFormat This enumeration provides the values that may be set on C on a C. Possible values are as follows. =head4 URI::Query::List This is the default and recommended value. When set in C, each key in the hash representation will point to a list of one or more values. This is recommended as it is the most accurate representation to what is actually possible in a query string. The values within each key will be sorted in the same order as they appear in the query. =head4 URI::Query::Mixed When set in C, each key in the hash representation may be mapped to either a list of two or more values, or to the value itself if there is only one. This is not recommended because it means that setting a key to an iterable value will be treated as multiple key-value pairs when it comes time to render the query as a string. This could have unintended consequences. =head4 URI::Query::Singles When set in C, each key in the hash representation will be mapped directly to a single value. If there are multiple values that have been set in the query, then only the last will be visible through the hash representation. This is not recommended because it may hide certain values, but may be useful for simple applications that treat the query string as having unique values. Just note that the trade-off is that your application may be confused when multiple values are present. =head4 URI::Query::None This value should not be used, but will be treated the same as C if used here. It is provided for use with C only. =head3 method hash-format method hash-format(URI::Query:D) is rw returns URI::Query::HashFormat This is a simple setter/getter for setting the way in which associative lookups are performed. See C for a description of each mode. =head3 method query method query(Query:D:) returns URI::Query::ValidQuery:D method query(Query:D: Str() $new) returns URI::Query::ValidQuery:D This method returns the string representation of the URI query component. When passed a string, it will replace the query with the new value and will use C to parse that query into an array of Cs. The primary representation of the queryo object is the value returned by C. The C is cached to keep the value stored in it when this method is called to set it or when C is constructed from a string. Any modification to the internal array of pairs, though, will clear this cache. It will be generated the next time the C method is called and that string will be cached. =head3 method query-form method query-form(Query:D:) returns Array:D method query-form(Query:D: *@new, *%new) returns Array:D This method returns the array of Cs that store the internal representation of the URI query component. When passed an array of Cs, it will replace the current value with that array. A quick note about the way pairs are passed as parameters to a method, you most likely want to avoid passing values as named parameters. If values are passed using unquoted strings, they will be treated as named parameters, which is most likely what you want: my $q = URI::Query.new; # Prefer this $q.query-form('foo' => 1, 'bar' => 2, 'foo' => 3); # Avoid these $q.query-form(foo => 1, bar => 2, foo => 3); $q.query-form(:foo(1), :bar(2), :foo(3)); The latter two will result in the first "foo" pair being lost. Named parameters assume unique names and the latter "foo" key will effectively override the former. That said, the method will allow hashes to be passed in, if that is your preference. =head3 method of method of() Always returns C. =head3 method iterator method iterator(Query:D:) returns Iterator:D Returns the iterator on the internal array of Cs. =head3 method postcircumflex:<[ ]> method postcircumflex:<[ ]> returns Pair This permits positional access to each C stored internally. You may use this to get a C, set a C, test for existence, or delete. =head3 method postcircumflex:<{ }> method postcircumflex:<{ }> This permits associative access to the values stored internally by key. What is returned here when fetching values depends on the setting in C, a list of one or more values or C, by default. You can use this for getting, setting, existence testing, or deletion. =head3 method keys method keys(Query:D:) returns Seq:D This method returns all the keys of the query in order. =head3 method values method values(Query:D:) returns Seq:D This method returns all the values of the query in order. =head3 method kv method kv(Query:D:) returns Seq:D This method returns a sequence alternating the keys and values of the query in order. =head3 method pairs method kv(Query:D:) returns Seq:D This method returns a copy of the internal representation of the query string array. =head3 method pop method pop(Query:D:) returns Pair This method removes the last Pair from the array of pairs and returns it. =head3 method push method push(Query:D: *@new) This method adds the given pairs to the end of the array of pairs in the query using push semantics. =head3 method append method append(Query:D: *@new) This method adds the given pairs to the end of the array of pairs in the query using append semantics. =head3 method shift method shift(Query:D:) returns Pair This method removes the first Pair from the array of pairs and returns it. =head3 method unshift method unshift(Query:D: *@new) This method adds the given pairs to the front of the array of pairs in the query using unshift semantics. =head3 method prepend method prepend(Query:D: *@new) This method adds the given pairs to the front of the array of pairs in the query using prepend semantics. =head3 method splice method splice(Query:D: $start, $elems?, *@replacement) This method removes a C<$elems> number of pairs from the array of pairs in the query starting at index C<$start>. It then inserts the pairs in C<@replacement> into that part of the array (if any are given). =head3 method elems method elems(Query:D:) returns Int:D Returns the number of pairs stored in the query. =head3 method end method end(Query:D:) returns Int:D Returns the index of the last pair stored in the query. =head3 method Bool method Bool(Query:D:) returns Bool:D Returns C if the at least one pair is stored in the query or C otherwise. =head3 method Int method Int(Query:D:) returns Int:D Returns C. =head3 method Numeric method Numeric(Query:D:) returns Int:D Returns C. =head3 method gist method gist(Query:D:) returns Str:D Returns the C. =head3 method Str method Str(Query:D:) returns Str:D Returns the C. =head1 EXCEPTIONS =head2 X::URI::Invalid This exception is thrown in many places where the URI is being parsed or manipulated. If the string being parsed is not a valid URI or if certain manipulations of the URI object would cause it to become an invalid URI, this exception may be used. It provides a C accessor, which returns the string that was determined to be invalid. =head2 X::URI::Path::Invalid In some cases where an attempt is made to set C to an invalid value, this exception is thrown. The C field will name the invalid URI. Strictly speaking, the URI might be valid, but will not parse the same way as given. To make it clear that this is the case, the C field names the invalid path part. In cases where the segments have been modified in an invalid way, the first invalid segment will be set in C. =head1 AUTHORS Contributors to this module include: =item Ronald Schmidt (ronaldxs) =item Moritz Lentz (moritz) =item Nick Logan (ugexe) =item Tobias Leich (FROGGS) =item Jonathan Stowe (jonathanstowe) =item Justin DeVuyst (jdv) =item Solomon Foster (colomon) =item Roman Baumer (rba) =item Zoffix Znet (zoffixznet) =item Ahmad M. Zawawi (azawawi) =item Gabor Szabo (szabgab) =item Samantha McVey (samcv) =item Pawel Pabian (bbkr) =item Rob Hoelz (hoelzro) =item radiak =item Paul Cochrane (paultcochrane) =item Steve Mynott (stmuk) =item timo =item David Warring (dwarring) =item Sterling Hanenkamp (zostay) =head1 COPYRIGHT & LICENSE Copyright 2017 Ronald Schmidt. This software is licensed under the same license as Perl 6 itself. =end pod # vim:ft=perl6 raku-uri-0.3.5/lib/URI/000077500000000000000000000000001414637465300145005ustar00rootroot00000000000000raku-uri-0.3.5/lib/URI/DefaultPort.rakumod000066400000000000000000000020321414637465300203120ustar00rootroot00000000000000use v6; # This logic seems to belong somewhere related to URI but not in the URI # module itself. unit class URI::DefaultPort; my Int %default_port = ( ftp => 21, sftp => 22, ssh => 22, telnet => 23, tn3270 => 23, smtp => 25, gopher => 70, http => 80, shttp => 80, pop => 110, news => 119, nntp => 119, imap => 143, ldap => 389, https => 443, rlogin => 513, rtsp => 554, rtspu => 554, snews => 563, ldaps => 636, rsync => 873, mms => 1755, sip => 5060, sips => 5061, git => 9418 ); method scheme-port(Str $scheme) { %default_port{$scheme}; } =begin pod =head1 NAME URI::DefaultPort - Stores the default ports for various services =head1 SYNOPSIS use URI::DefaultPort; say scheme-port( "https" ); # 443 =end pod # vim:ft=perl6 raku-uri-0.3.5/lib/URI/Escape.rakumod000066400000000000000000000046721414637465300172750ustar00rootroot00000000000000use v6; unit package URI::Escape; use IETF::RFC_Grammar::URI; my %escapes = (^256).flatmap: { .chr => sprintf '%%%02X', $_ }; # line below may work and be useful when fix RT #126252 # my token escape_unreserved {}; multi sub uri-escape($s, Bool :$no-utf8 = False) is export { $s // return $s; $s.subst(:g, /<- [\-._~A..Za..z0..9]>/, { ( $no-utf8 || .Str.ord < 128 ) ?? %escapes{ .Str } !! .Str.encode.list.fmt('%%%X', "") } ); } multi sub uri-escape(Match $s, Bool :$no-utf8 = False) is export { uri-escape( $s.Str, :$no-utf8 ); } # todo - automatic invalid UTF-8 detection # see http://www.w3.org/International/questions/qa-forms-utf-8 # find first sequence of %[89ABCDEF]<.xdigit> # use algorithm from url to determine if it's valid UTF-8 sub uri-unescape(*@to_unesc, Bool :$no-utf8 = False, Str :$enc = 'UTF-8') is export { my @rc; if $no-utf8 or $enc eq 'UTF-8' { @rc = @to_unesc.flatmap: { .trans('+' => ' ')\ .subst(:g, / [ '%' (<.xdigit> ** 2 ) ]+ /, -> $/ { $no-utf8 ?? $0.flatmap({ :16(~$_).chr }).join !! Buf.new($0.flatmap({ :16(~$_) })).decode('UTF-8') }); } } else { my $Buf = Buf.new; for @to_unesc -> $uri is copy { $uri .= trans('+' => ' '); my $index = 0; while $uri.index('%', $index) { my $new-index = $uri.index('%', $index); $Buf.push($uri.substr($index, $new-index - $index).encode($enc)); $Buf.push(:16(~$0)) if $uri.substr($new-index + 1, 2) ~~ / ( <:AHex> ** 2 ) /; $index = $new-index + 3; } $Buf.push( $uri.substr($index, $uri.chars - $index).encode($enc)) if $index < $uri.chars; @rc.push($Buf.decode($enc)); } } return |@rc; } # preserve snake case interface sub uri_escape($s, Bool :$no_utf8 = False) is export { uri-escape($s, no-utf8 => $no_utf8) } sub uri_unescape(*@to_unesc, Bool :$no_utf8 = False) is export { uri-unescape(@to_unesc, no-utf8 => $no_utf8) } =begin pod =head1 NAME URI::Escape - Escape and unescape unsafe characters =head1 SYNOPSIS use URI::Escape; my $escaped = uri-escape("10% is enough\n"); my $un_escaped = uri-unescape('10%25%20is%20enough%0A'); =end pod # vim:ft=perl6 raku-uri-0.3.5/lib/URI/Path.rakumod000066400000000000000000000030561414637465300167640ustar00rootroot00000000000000# Because Path is limited in form based on factors outside of it, it doesn't # actually provide any validation within itself. As such, this type is immutable # to prevent a problem. This could be made mutable if it is given a reference to # the parent URI class. The URI class is responsible for performing the # necessary validations. unit class URI::Path; use URI::Escape; has Str $.path; has @.segments; my @rules = < path-abempty path-absolute path-noscheme path-rootless path-empty >; multi method new(URI::Path:U: Match:D $comp, :$scheme) { my $path-type = @rules.first({ $comp{ $_ }.defined }); my $path = $comp{ $path-type }; my @segments := $path.list.grep(*.defined).map({.Str}).List || ('', ); if $path || $path -> $first-chunk { @segments := ($first-chunk.Str, |@segments); } if "$path".starts-with('/') { @segments := ('', |@segments); } $path = "$path"; if $scheme and $scheme ~~ /http/ { $path = $path.split("/").map( { uri-escape( uri-unescape($_) ) } ).join("/"); # Reconstruct path } self.new(:$path, :@segments); } submethod BUILD(:$!path = '', :@segments = $!path.split('/')) { @!segments := @segments.List; } multi method gist(URI::Path:D: --> Str ) { $!path } multi method Str(URI::Path:D: --> Str ) { $!path } method rel2abs(URI::Path:D: Str:D() $base) { if $!path.starts-with('/') { self; } else { my $path = $base.subst(rx{"/"*$}, '') ~ '/' ~ $!path; self.new: :$path; } } raku-uri-0.3.5/lib/URI/Query.rakumod000066400000000000000000000133011414637465300171670ustar00rootroot00000000000000unit class URI::Query does Positional does Associative does Iterable; use IETF::RFC_Grammar::URI; use URI::Escape; our subset ValidQuery of Str where /^ $/; our enum HashFormat ; has HashFormat $.hash-format is rw; has ValidQuery $!query; has Pair @!query-form; our sub split-query( Str() $query, :$hash-format is copy = None, ) { $hash-format = None if $hash-format eqv False; $hash-format = Lists if $hash-format eqv True; my @query-form = gather for $query.split(/<[&;]>/) { # when the query component contains abc=123 form when /'='/ { my ($k, $v) = .split('=', 2).map(&uri-unescape); take $k => $v; } # or if the component contains just abc when /./ { take $_ => True; } } given $hash-format { when Mixed { my %query-form; for @query-form -> $qp { if %query-form{ $qp.key }:exists { if %query-form{ $qp.key } ~~ Array { %query-form{ $qp.key }.push: $qp.value; } else { %query-form{ $qp.key } = [ %query-form{ $qp.key }, $qp.value, ]; } } else { %query-form{ $qp.key } = $qp.value; } } return %query-form; } when Singles { return %(@query-form); } when Lists { my %query-form; %query-form{ .key }.push: .value for @query-form; return %query-form; } default { return @query-form; } } } # artifact form our sub split_query(|c) { split-query(|c) } multi method new(Str() $query, :$hash-format = Lists) { self.new(:$query, :$hash-format); } submethod BUILD(:$!query = '', :@!query-form, :$!hash-format = Lists) { die "When calling URI::Query.new set either :query or :query-form, but not both." if @!query-form and $!query; @!query-form = split-query($!query) if $!query; } method !value-for($key) { # TODO Is there a better way to make this list immutable than to use a # Proxy container? my $l = @!query-form.grep({ .key eq $key }).map({ my $v = .value; Proxy.new( FETCH => method () { $v }, STORE => method ($n) { X::Assignment::RO.new(:value($v)).throw }, ); }).List; given $!hash-format { when Mixed { $l.elems == 1 ?? $l[0] !! $l } when Singles { $l.first(:end) } default { $l } } } method of() { Pair } method iterator(URI::Query:D:) { @!query-form.iterator } # positional context method AT-POS(URI::Query:D: $i) { @!query-form[$i] } method EXISTS-POS(URI::Query:D: $i) { @!query-form[$i]:exists } method ASSIGN-POS(URI::Query:D: $i, Pair $p) { $!query = Nil; @!query-form[$i] = $p; } method DELETE-POS(URI::Query:D: $i) { $!query = Nil; @!query-form[$i]:delete } # associative context method AT-KEY(URI::Query:D: $k) { self!value-for($k) } method EXISTS-KEY(URI::Query:D: $k) { @!query-form.first({ .key eq $k }).defined } method ASSIGN-KEY(URI::Query:D: $k, $value) { $!query = Nil; @!query-form .= grep({ .key ne $k }); given $!hash-format { when Singles { @!query-form.push: $k => $value; } default { @!query-form.append: $value.list.map({ $k => $^v }); } } $value } method DELETE-KEY(URI::Query:D: $k) { $!query = Nil; my $r = self!value-for($k); my @ps = @!query-form .= grep({ .key ne $k }); $r; } # Hash-like readers method keys(URI::Query:D:) { @!query-form.map(*.key) } method values(URI::Query:D:) { @!query-form.map(*.value) } method kv(URI::Query:D:) { @!query-form.map(*.kv).flat } method pairs(URI::Query:D:) { @!query-form } # Array-like manipulators method pop(URI::Query:D:) { $!query = Nil; @!query-form.pop } method push(URI::Query:D: |c) { $!query = Nil; @!query-form.push: |c } method append(URI::Query:D: |c) { $!query = Nil; @!query-form.append: |c } method shift(URI::Query:D:) { $!query = Nil; @!query-form.shift } method unshift(URI::Query:D: |c) { $!query = Nil; @!query-form.unshift: |c } method prepend(URI::Query:D: |c) { $!query = Nil; @!query-form.prepend: |c } method splice(URI::Query:D: |c) { $!query = Nil; @!query-form.splice: |c } # Array-like readers method elems(URI::Query:D:) { @!query-form.elems } method end(URI::Query:D:) { @!query-form.end } method Bool(URI::Query:D: |c) { @!query-form.Bool } method Int(URI::Query:D: |c) { @!query-form.Int } method Numeric(URI::Query:D: |c) { @!query-form.Numeric } multi method query(URI::Query:D:) { return $!query with $!query; $!query = @!query-form.grep({ .defined }).map({ if .value eqv True { "{uri-escape(.key)}=" } else { "{uri-escape(.key)}={uri-escape(.value)}" } }).join('&'); # If there's only one pair => True, drop the = # so end with "foo" not "foo=" but keep "foo=&bar=" # If someone is pickier, they should set $!query directly. $!query ~~ s/'=' $// unless $!query ~~ /'&'/; $!query; } multi method query(URI::Query:D: Str() $new) { $!query = $new; @!query-form = split-query($!query); $!query; } multi method query-form(URI::Query:D:) { @!query-form } multi method query-form(URI::Query:D: *@new, *%new) { $!query = Nil; @!query-form = flat %new, @new; } # string context multi method gist(URI::Query:D: --> Str ) { $.query } multi method Str(URI::Query:D: --> Str ) { $.gist } raku-uri-0.3.5/t/000077500000000000000000000000001414637465300135365ustar00rootroot00000000000000raku-uri-0.3.5/t/01.t000066400000000000000000000101771414637465300141510ustar00rootroot00000000000000use v6; use Test; plan 50; use URI; use URI::Escape; ok(1,'We use URI et. al and we are still alive'); my $u = URI.new('http://example.com:80/about/us?foo#bar'); is($u.scheme, 'http', 'scheme'); is($u.host, 'example.com', 'host'); is($u.port, '80', 'port'); is($u.path, '/about/us', 'path'); is($u.query, 'foo', 'query'); is($u.frag, 'bar', 'frag'); is($u.segments.join('/'), '/about/us', 'segments'); is($u.segments[0], '', 'first chunk'); is($u.segments[1], 'about', 'second chunk'); is($u.segments[2], 'us', 'third chunk'); is( ~$u, 'http://example.com:80/about/us?foo#bar', 'Complete path stringification'); # Test with unicode characters $u = URI.new('http://test.de/ö'); is($u.path, '/%C3%B6', 'path with unicode'); say $u; $u = URI.new('http://127.0.0.1:1234/echo2/☃'); is($u, 'http://127.0.0.1:1234/echo2/%E2%98%83', "Punycoding is correct"); # allow uri as named argument too $u = URI.new(uri => 'https://eXAMplE.COM'); is($u.scheme, 'https', 'scheme'); is($u.host, 'example.com', 'host'); is( "$u", 'https://example.com', 'https://eXAMplE.COM stringifies to https://example.com'); is($u.port, 443, 'default https port'); ok(! $u._port.defined, 'no specified port'); $u.parse('/foo/bar/baz'); is($u.segments.join('/'), '/foo/bar/baz', 'segments from absolute path'); $u.parse('foo/bar/baz'); is($u.segments.join('/'), 'foo/bar/baz', 'segments from relative path'); is($u.segments[0], 'foo', 'first segment'); is($u.segments[1], 'bar', 'second segment'); is($u.segments[*-1], 'baz', 'last segment'); # actual uri parameter not required $u = URI.new; $u.parse('http://foo.com'); is-deeply($u.segments, ('',), ".segments return ('',) for empty path"); is $u.segments.join('/'), '', '.segments joined to empty string';; is($u.port, 80, 'default http port'); # test URI parsing with <> or "" and spaces $u.parse(" "); is("$u", 'http://foo.com', '<> removed from str'); $u.parse(' "http://foo.com"'); is("$u", 'http://foo.com', '"" removed from str'); my $host_in_grammar = $u.grammar.parse_result; ok(! $host_in_grammar.defined, 'grammar detected host not ip' ); is($host_in_grammar, 'foo.com', 'grammar detected registered domain style'); $u.parse('http://10.0.0.1'); is($u.host, '10.0.0.1', 'numeric host'); $host_in_grammar = $u.grammar.parse_result; is($host_in_grammar, '10.0.0.1', 'grammar detected ipv4'); ok(! $host_in_grammar.defined, 'grammar detected no registered domain style'); $u.parse('http://example.com:80/about?foo=cod&bell=bob#bar'); is($u.query, 'foo=cod&bell=bob', 'query with form params'); is($u.query, 'cod', 'query param foo'); is($u.query, 'cod', 'snake case query param foo'); is($u.query, 'bob', 'query param bell'); $u.parse('http://example.com:80/about?foo=cod&foo=trout#bar'); is($u.query[0], 'cod', 'query param foo - el 1'); is($u.query[1], 'trout', 'query param foo - el 2'); is($u.frag, 'bar', 'test query and frag capture'); $u.parse('http://example.com:80/about?foo=cod&foo=trout'); is($u.query[1], 'trout', 'query param foo - el 2 without frag'); $u.parse('about/perl6uri?foo=cod&foo=trout#bar'); is($u.query[1], 'trout', 'query param foo - el 2 relative path'); $u.parse('about/perl6uri?foo=cod&foo=trout'); is($u.query[1], 'trout', 'query param foo - el 2 relative path without frag'); throws-like {URI.new('http:://?#?#')}, X::URI::Invalid, 'Bad URI raises exception x:URI::Invalid'; my $uri-w-js = 'http://example.com } function(var mm){ alert(mm) }'; throws-like {URI.new($uri-w-js)}, X::URI::Invalid, 'URI followed by trailing javascript raises exception'; my $uri-pfx = URI.new($uri-w-js, :match-prefix); is(~$uri-pfx, 'http://example.com', 'Pulled of prefix URI'); nok(URI.new('foo://bar.com').port, '.port without default value lives'); lives-ok { URI.new('/foo/bar').port }, '.port on relative URI lives'; my Str $user-info = URI.new( 'http://user%2Cn:deprecatedpwd@obscurity.com:8080/ucantcme' ).userinfo, is( uri-unescape($user-info), 'user,n:deprecatedpwd', 'extracted userinfo correctly'); # vim:ft=perl6 raku-uri-0.3.5/t/authority.t000066400000000000000000000017231414637465300157560ustar00rootroot00000000000000use v6; use Test; use URI; my $u = URI.new('foo://me@localhost:4321'); isa-ok $u.authority, URI::Authority; ok $u.authority.defined; with $u.authority { is .userinfo, 'me'; is .host, 'localhost'; is .port, 4321; is "$_", 'me@localhost:4321'; is .gist, "$_"; .userinfo = 'steve'; is "$_", 'steve@localhost:4321'; .host = 'xyz'; is "$_", 'steve@xyz:4321'; .port = 1234; is "$_", 'steve@xyz:1234'; .userinfo = ''; is "$_", 'xyz:1234'; .userinfo = 'steve'; is "$_", 'steve@xyz:1234'; .userinfo = Nil; is "$_", 'xyz:1234'; .port = Nil; is "$_", 'xyz'; } $u.authority('me@localhost:4321'); is "$u", 'foo://me@localhost:4321'; $u.authority(Nil); is "$u", 'foo:'; $u.userinfo('me'); is "$u", 'foo://me@'; $u.port(4321); is "$u", 'foo://me@:4321'; $u.host('localhost'); is "$u", 'foo://me@localhost:4321'; $u.authority(''); is "$u", 'foo://'; $u.authority(Nil); is "$u", 'foo:'; done-testing; raku-uri-0.3.5/t/directory.t000066400000000000000000000006201414637465300157250ustar00rootroot00000000000000use v6; use Test; use URI; plan 8; sub dir(Str $u --> URI:D) { URI.new($u).directory; } is dir("ccc"), "./"; is dir("/ccc"), "/"; is dir("/ccc/"), "/ccc/"; is dir("ccc/"), "ccc/"; is dir("path/doc.html"), "path/"; is dir("https://mysite.com"), "https://mysite.com/"; is dir("https://mysite.com/"), "https://mysite.com/"; is dir("https://mysite.com/path/doc.html"), "https://mysite.com/path/"; raku-uri-0.3.5/t/escape.t000066400000000000000000000020461414637465300151650ustar00rootroot00000000000000use v6; # Copied, with minor translation to Perl6, from the escape.t file # in the CPAN URI distribution use Test; plan 11; use URI::Escape; ok(1,'We use URI::Escape and we are still alive'); is uri-escape('abcDEF?$%@h&m'), 'abcDEF%3F%24%25%40h%26m', 'basic ascii escape test'; is uri-escape(no-utf8 => True, '|abcå'), '%7Cabc%E5', 'basic latin-1 escape test'; is uri_escape(no_utf8 => True, '|abcå'), '%7Cabc%E5', 'snake case basic latin-1 escape test'; is uri-escape('|abcå'), '%7Cabc%C3%A5', 'basic utf-8 escape test'; is uri-escape('|a b cå'),'%7Ca%20%20b%20c%C3%A5', 'basic utf-8 escape test w spaces'; is uri-unescape(no-utf8 => True, '%7C%25abc%E5'), '|%abcå', 'basic latin-1 unescape test'; is uri_unescape(no_utf8 => True, '%7C%25abc%E5'), '|%abcå', 'snake case basic latin-1 unescape test'; is uri-unescape('%7C%25abc%C3%A5'), '|%abcå', 'basic utf8 unescape test'; is uri-unescape("%40A%42", "CDE", "F%47++H"), ['@AB', 'CDE', 'FG H'], 'unescape list'; ok not uri-escape(Str).defined, 'undef returns undef'; # vim:ft=perl6 raku-uri-0.3.5/t/expected.txt000066400000000000000000000002571414637465300161040ustar00rootroot00000000000000file:///space/pub/music/mp3/Musopen DVD/Brahms - Symphony No 1 in C Major/Symphony No. 1 in C Minor, Op. 68 - IV. Adagio - Più andante - Allegro non troppo, ma con brio.mp3 raku-uri-0.3.5/t/issue-43.t000066400000000000000000000005541414637465300153030ustar00rootroot00000000000000use v6; use Test; use URI; plan 2; # This tests that we aren't double encoding path parts # The test string has an encoded ':' my Str $test-uri = 'http://localhost:5984/_users/org.couchdb.user%3Ayotjfgnr'; my $uri; lives-ok { $uri = URI.new($test-uri) }, "create the URI object"; is $uri.Str, $test-uri, "and the URI stringifies to the same as the source"; raku-uri-0.3.5/t/lib/000077500000000000000000000000001414637465300143045ustar00rootroot00000000000000raku-uri-0.3.5/t/lib/TestURIRequire.pm000066400000000000000000000002001414637465300174660ustar00rootroot00000000000000use v6.c; use URI; class TestURIRequire { method test(Str $uri) { my $u = URI.new($uri); $u.port; } } raku-uri-0.3.5/t/mutate.t000066400000000000000000000134451414637465300152310ustar00rootroot00000000000000use v6; use Test; use URI; use IETF::RFC_Grammar::URI; cmp-ok 'http', '~~', URI::Scheme, 'URI::Scheme matches http'; cmp-ok '', '~~', URI::Scheme, 'URI::Scheme matches ""'; cmp-ok '-asdf', '!~~', URI::Scheme, 'URI::Scheme refused to match -asdf'; my $u = URI.new('http://example.com:80/about/us?foo#bar'); $u.scheme('https'); $u.port(443); is "$u", 'https://example.com:443/about/us?foo#bar', 'modified URI looks good'; $u.port(Nil); is "$u", 'https://example.com/about/us?foo#bar', 'URI with no port looks good'; $u._port(567); is "$u", 'https://example.com:567/about/us?foo#bar', 'setting _port works too'; $u._port(Nil); is "$u", 'https://example.com/about/us?foo#bar', 'clearing with _port works too'; $u.authority("larry@perl6.org:1234"); is "$u", 'https://larry@perl6.org:1234/about/us?foo#bar', 'setting authority works'; is $u.userinfo, 'larry', 'setting authority set userinfo'; is $u.host, 'perl6.org', 'setting authority set host'; is $u.port, 1234, 'setting authority set port'; $u.authority("example.com"); is "$u", 'https://example.com/about/us?foo#bar', 'setting authority with only a host works'; is $u.userinfo, '', 'setting authority clears userinfo'; is $u.host, 'example.com', 'setting authority set host'; ok !$u._port.defined, 'setting authority clears port'; $u.path("/"); is "$u", 'https://example.com/?foo#bar', 'setting path works'; is-deeply $u.segments, ('',''), '/ has empty segments'; $u.path(""); is "$u", 'https://example.com?foo#bar', 'empty path works'; is-deeply $u.segments, ('', ), '"" has one empty segment segments'; $u.path("/careers/are/good"); is "$u", 'https://example.com/careers/are/good?foo#bar', 'empty path works'; is-deeply $u.segments, ('', 'careers', 'are', 'good'), '/careers/are/good has three segments'; $u.segments(«"" foo bar baz»); is $u.path, '/foo/bar/baz'; is "$u", 'https://example.com/foo/bar/baz?foo#bar', 'setting segments via list works'; is-deeply $u.segments, ('', 'foo', 'bar', 'baz'), 'settings segments gets same back'; throws-like { $u.segments("/"); }, X::URI::Path::Invalid; throws-like { $u.segments(); }, X::URI::Path::Invalid; throws-like { $u.segments(); }, X::URI::Path::Invalid; $u.segments('', 'careers', 'are', 'good'); is $u.path, '/careers/are/good'; is "$u", 'https://example.com/careers/are/good?foo#bar', 'setting segments via slurpy works'; is-deeply $u.segments, ('', 'careers', 'are', 'good'), 'settings segments gets same back'; subtest { $u.query.hash-format = URI::Query::Mixed; $u.query('foo=cod&foo=trout'); is "$u", 'https://example.com/careers/are/good?foo=cod&foo=trout#bar', 'setting query works'; is-deeply $u.query, ('cod', 'trout'), 'query from foo is good'; is $u.query[0], 'cod', 'query form foo.0 is good'; is $u.query[1], 'trout', 'query form foo.1 is good'; throws-like { $u.query[0] = 'bad stuff'; }, X::Assignment::RO, 'cannot set query<>[] because it is immutable'; $u.query = True; is "$u", 'https://example.com/careers/are/good?foo#bar', 'setting query<> to True works'; $u.query('bar' => 'lion', 'bar' => 'tiger'); is "$u", 'https://example.com/careers/are/good?bar=lion&bar=tiger#bar', 'setting query works'; is $u.query[0], 'lion', 'query form bar.0 is good'; is $u.query[1], 'tiger', 'query form bar.1 is good'; throws-like { $u.query[0] = 'bad stuff'; }, X::Assignment::RO, 'cannot set query<>[] because it is immutable'; $u.query = 'ok'; is "$u", 'https://example.com/careers/are/good?bar=ok#bar', 'setting query<> works'; $u.query = ('ok', 'andok'); is "$u", 'https://example.com/careers/are/good?bar=ok&bar=andok#bar', 'setting query<> to list works as expected'; }, 'hash-format = Mixed'; subtest { $u.query.hash-format = URI::Query::Singles; $u.query('foo=cod&foo=trout'); is "$u", 'https://example.com/careers/are/good?foo=cod&foo=trout#bar', 'setting query works'; is-deeply $u.query, 'trout', 'query from foo is good'; $u.query = True; is "$u", 'https://example.com/careers/are/good?foo#bar', 'setting query to True works'; $u.query('bar' => 'lion', 'bar' => 'tiger'); is "$u", 'https://example.com/careers/are/good?bar=lion&bar=tiger#bar', 'setting query works'; is $u.query, 'tiger', 'query form bar is good'; $u.query = ('ok', 'andok'); is "$u", 'https://example.com/careers/are/good?bar=ok%20andok#bar', 'setting query<> to list works as expected'; }, 'hash-format = Singles'; subtest { $u.query.hash-format = URI::Query::Lists; $u.query('foo=cod&foo=trout'); is "$u", 'https://example.com/careers/are/good?foo=cod&foo=trout#bar', 'setting query works'; is-deeply $u.query, $('cod', 'trout'), 'query from foo is good'; $u.query = True; is "$u", 'https://example.com/careers/are/good?foo#bar', 'setting query<> to True works'; $u.query('bar' => 'lion', 'bar' => 'tiger'); is "$u", 'https://example.com/careers/are/good?bar=lion&bar=tiger#bar', 'setting query works'; is $u.query[0], 'lion', 'query form bar.0 is good'; is $u.query[1], 'tiger', 'query form bar.1 is good'; throws-like { $u.query[0] = 'bad stuff'; }, X::Assignment::RO, 'cannot set query<>[] because it is immutable'; $u.query = 'ok'; is "$u", 'https://example.com/careers/are/good?bar=ok#bar', 'setting query<> to single works'; $u.query = ('ok', 'andok'); is "$u", 'https://example.com/careers/are/good?bar=ok&bar=andok#bar', 'setting query<> to list works as expected'; }, 'hash-format = Lists'; $u.query('bar'); $u.fragment = "wubba"; is "$u", 'https://example.com/careers/are/good?bar#wubba', 'setting fragment works'; $u.fragment("hello"); is "$u", 'https://example.com/careers/are/good?bar#hello', 'setting fragment works'; done-testing; raku-uri-0.3.5/t/november-urlencoded.t000066400000000000000000000015171414637465300176660ustar00rootroot00000000000000use v6; # lifted more or less completely from the Novemver project # just now uses URI::Escape rather than November::CGI use Test; use URI::Escape; my @t = '%61' => 'a', '%C3%A5' => 'å', '%C4%AC' => 'Ĭ', '%C7%82' => 'ǂ', '%E2%98%BA' => '☺', '%E2%98%BB' => '☻', 'alla+snubbar' => 'alla snubbar', 'text%61+abc' => 'texta abc', 'unicode+%C7%82%C3%A5' => 'unicode ǂå', '%25' => '%', '%25+25' => '% 25', '%25rr' => '%rr', '%2561' => '%61', ; plan +@t; for @t { my $ans = uri-unescape( ~.key ); ok( $ans eq .value, 'Decoding ' ~ .key ) or say "GOT: {$ans.perl}\nEXPECTED: {.value.perl}"; } # vim: ft=perl6 raku-uri-0.3.5/t/path.t000066400000000000000000000013641414637465300146630ustar00rootroot00000000000000use v6; use Test; use URI; my $q = URI.new('foo:/a/b/c'); isa-ok $q.path, URI::Path; ok $q.path.defined; with $q.path { is $_, "/a/b/c"; is .path, "/a/b/c"; is-deeply .segments, $('', 'a', 'b', 'c'); } $q.path('a/b/c'); with $q.path { is $_, "a/b/c"; is .path, "a/b/c"; is-deeply .segments, $('a', 'b', 'c'); } # with auth, path starting with / is required throws-like { $q.host('localhost'); }, X::URI::Invalid; $q.path('/a/b/c'); lives-ok { $q.host('localhost'); }; is "$q", 'foo://localhost/a/b/c'; $q.host(''); is "$q", 'foo:///a/b/c'; $q.authority(Nil); is "$q", 'foo:/a/b/c'; # without auth first segment after / must be non-zero length throws-like { $q.path('//a/b/c'); }, X::URI::Invalid; done-testing; raku-uri-0.3.5/t/query.t000066400000000000000000000132031414637465300150670ustar00rootroot00000000000000use v6; use Test; use URI :split-query; constant $qs = 'foo=1&bar=2&foo=3&baz'; subtest { my @q = split-query(''); is-deeply @q, []; }, 'split-query of empty string'; subtest { my @q = split-query($qs); is-deeply @q, [foo => '1', bar => '2', foo => '3', baz => True]; }, 'split-query to array'; subtest { my @q = split-query($qs, :!hash-format); is-deeply @q, [foo => '1', bar => '2', foo => '3', baz => True]; }, 'split-query to array (boolean :!hash-format)'; subtest { my %q = split-query($qs, :hash-format); is-deeply %q, %(foo => ['1', '3'], bar => ['2'], baz => [ True ]); }, 'split-query to lists hash (boolean :hash-format)'; subtest { my %q = split-query($qs, hash-format => URI::Query::Mixed); is-deeply %q, %(foo => ['1', '3'], bar => '2', baz => True); }, 'split-query to mixed hash'; subtest { my %q = split-query($qs, hash-format => URI::Query::Singles); is-deeply %q, %(foo => '3', bar => '2', baz => True); }, 'split-query to singles hash'; subtest { my %q = split-query($qs, hash-format => URI::Query::Lists); is-deeply %q, %(foo => ['1', '3'], bar => ['2'], baz => [ True ]); }, 'split-query to lists hash'; subtest { dies-ok { URI::Query.new(query => $qs, query-form => [ a => 1 ]); }; }, 'bad .new'; subtest { my $q = URI::Query.new($qs); is "$q", 'foo=1&bar=2&foo=3&baz', 'query is cached'; is $q, True, 'query baz is True'; $q = True; is "$q", 'foo=1&bar=2&foo=3&baz=', 'query is recomputed'; }, 'query caching'; subtest { my $q = URI::Query.new($qs); is $q[0].key, 'foo'; is $q[0].value, '1'; is $q[1].key, 'bar'; is $q[1].value, '2'; is $q[2].key, 'foo'; is $q[2].value, '3'; is $q[3].key, 'baz'; is $q[3].value, True; is $q[$_]:exists, True for ^3; is $q[4]:exists, False; $q[0] = 'qux' => 4; is "$q", "qux=4&bar=2&foo=3&baz="; my $bar = $q[1]:delete; is $bar.key, 'bar'; is $bar.value, '2'; is "$q", "qux=4&foo=3&baz="; }, 'array-ish bits'; subtest { my $q = URI::Query.new($qs); is-deeply $q, $('1', '3'); is-deeply $q, $('2',); is-deeply $q, $(True,); throws-like { $q[0] = '0'; }, X::Assignment::RO; is $q{$_}:exists, True for ; is $q:exists, False; my $foo = $q:delete; is $foo, $('1', '3'); is "$q", 'bar=2&baz='; $q = '4'; is "$q", 'bar=2&baz=&foo=4'; $q = '5', '6'; is "$q", 'bar=2&baz=&foo=5&foo=6'; }, 'hash-ish lists bits'; subtest { my $q = URI::Query.new($qs, :hash-format(URI::Query::Mixed)); is-deeply $q, $('1', '3'); is-deeply $q, '2'; is-deeply $q, True; throws-like { $q[0] = '0'; }, X::Assignment::RO; is $q{$_}:exists, True for ; is $q:exists, False; my $foo = $q:delete; is $foo, $('1', '3'); is "$q", 'bar=2&baz='; $q = '4'; is "$q", 'bar=2&baz=&foo=4'; $q = '5', '6'; is "$q", 'bar=2&baz=&foo=5&foo=6'; }, 'hash-ish mixed bits'; subtest { my $q = URI::Query.new($qs, :hash-format(URI::Query::Singles)); is-deeply $q, '3'; is-deeply $q, '2'; is-deeply $q, True; is $q{$_}:exists, True for ; is $q:exists, False; my $foo = $q:delete; is $foo, '3'; is "$q", 'bar=2&baz='; $q = '4'; is "$q", 'bar=2&baz=&foo=4'; $q = '5', '6'; is "$q", 'bar=2&baz=&foo=5%206'; }, 'hash-ish singles bits'; subtest { my $q = URI::Query.new($qs); is-deeply $q.keys, ; is-deeply $q.values, $('1', '2', '3', True); is-deeply $q.kv, $('foo', '1', 'bar', '2', 'foo', '3', 'baz', True); is-deeply [|$q.pairs], [foo => '1', bar => '2', foo => '3', baz => True]; is $q.elems, 4; is $q.end, 3; is +$q, 4; is "$q", "foo=1&bar=2&foo=3&baz"; is $q.gist, "$q"; is $q.Str, $q.gist; }, 'iterator methods'; subtest { my $q = URI::Query.new($qs); my $baz = $q.pop; is $baz.key, 'baz'; is $baz.value, True; is "$q", "foo=1&bar=2&foo=3"; }, 'pop'; subtest { my $q = URI::Query.new($qs); $q.push: 'foo' => 4, 'foo' => 5; is "$q", 'foo=1&bar=2&foo=3&baz=&foo=4&foo=5'; }, 'push'; subtest { my $q = URI::Query.new($qs); my @more-foo = 'foo' => 4, 'foo' => 5; $q.append: @more-foo; is "$q", 'foo=1&bar=2&foo=3&baz=&foo=4&foo=5'; }, 'append'; subtest { my $q = URI::Query.new($qs); my $foo = $q.shift; is $foo.key, 'foo'; is $foo.value, '1'; is "$q", "bar=2&foo=3&baz="; }, 'shift'; subtest { my $q = URI::Query.new($qs); $q.unshift: 'foo' => 4, 'foo' => 5; is "$q", 'foo=4&foo=5&foo=1&bar=2&foo=3&baz='; }, 'unshift'; subtest { my $q = URI::Query.new($qs); my @more-foo = 'foo' => 4, 'foo' => 5; $q.prepend: @more-foo; is "$q", 'foo=4&foo=5&foo=1&bar=2&foo=3&baz='; }, 'prepend'; subtest { my $q = URI::Query.new($qs); my @more-foo = 'foo' => 4, 'foo' => 5; $q.splice: 1, 2, @more-foo; is "$q", 'foo=1&foo=4&foo=5&baz='; }, 'splice'; subtest { my $q = URI::Query.new($qs); is ?$q, True; $q.query(''); is ?$q, False; }, 'Bool'; subtest { my $q = URI::Query.new; is "$q", ''; is $q.query , ''; $q.query($qs); is "$q", $qs; is $q.query, $qs; $q = True; is "$q", "foo=1&bar=2&foo=3&baz="; is $q.query, "foo=1&bar=2&foo=3&baz="; }, 'query'; subtest { my $q = URI::Query.new; is-deeply [|$q.query-form], []; $q.query-form('foo' => 1, 'bar' => 2, 'foo' => 3, 'baz' => True); is "$q", "foo=1&bar=2&foo=3&baz="; }, 'query-form'; done-testing; raku-uri-0.3.5/t/rel2abs.t000066400000000000000000000011261414637465300152550ustar00rootroot00000000000000use v6; use Test; use URI; plan 10; sub rel2abs($r, $b) { my $rel = URI.new($r); my $base = URI.new($b); $rel.rel2abs($base).Str; } nok URI.new("foo:ccc").is-relative; ok URI.new("/ccc").is-relative; is rel2abs("ccc", "/aaa/bbb"), "/aaa/bbb/ccc"; is rel2abs("ccc", "/aaa/bbb/"), "/aaa/bbb/ccc"; is rel2abs("ccc", "/aaa/bbb//"), "/aaa/bbb/ccc"; is rel2abs("/ccc", "/aaa/bbb"), "/ccc"; is rel2abs("/ccc", "foo:/aaa/bbb"), "foo:/ccc"; is rel2abs("/ccc", "foo://aaa/bbb"), "foo://aaa/ccc"; is rel2abs("foo:ccc", "/aaa/bbb"), "foo:ccc"; is rel2abs("foo:ccc", "foo:/aaa/bbb"), "foo:ccc"; raku-uri-0.3.5/t/require.t000066400000000000000000000005151414637465300154000ustar00rootroot00000000000000#!/usr/bin/env perl6 use v6.c; use Test; plan 2; use lib $*PROGRAM.parent.child('lib').Str; require TestURIRequire; my $port; lives-ok { $port = ::("TestURIRequire").test('http://example.com'); }, "can use URI in a module that is itself required rather than used"; is $port, 80, "and got the right thing back"; done-testing; raku-uri-0.3.5/t/rfc-3986-examples.t000066400000000000000000000030541414637465300167220ustar00rootroot00000000000000use v6; use Test; plan 25; use URI; my $u = URI.new('ftp://ftp.is.co.za/rfc/rfc1808.txt', :validating<1>); is($u.scheme, 'ftp', 'ftp scheme'); is($u.host, 'ftp.is.co.za', 'ftp host'); is($u.path, '/rfc/rfc1808.txt', 'ftp path'); $u.parse('http://www.ietf.org/rfc/rfc2396.txt'); is($u.scheme, 'http', 'http scheme'); is($u.host, 'www.ietf.org', 'http host'); is($u.path, '/rfc/rfc2396.txt', 'http path'); $u.parse('ldap://[2001:db8::7]/c=GB?objectClass?one'); is($u.scheme, 'ldap', 'ldap scheme'); is($u.host, '[2001:db8::7]', 'ldap host'); is($u.path, '/c=GB', 'ldap path'); is($u.query, 'objectClass?one', 'ldap query'); $u.parse('mailto:John.Doe@example.com'); is($u.scheme, 'mailto', 'mailto scheme'); is($u.path, 'John.Doe@example.com', 'news path'); $u.parse('news:comp.infosystems.www.servers.unix'); is($u.scheme, 'news', 'news scheme'); is($u.path, 'comp.infosystems.www.servers.unix', 'news path'); $u.parse('tel:+1-816-555-1212'); is($u.scheme, 'tel', 'telephone scheme'); is($u.path, '+1-816-555-1212', 'telephone path'); $u.parse('telnet://192.0.2.16:80/'); is($u.scheme, 'telnet', 'telnet scheme'); is($u.authority, '192.0.2.16:80', 'telnet authority'); is($u.host, '192.0.2.16', 'telnet host'); is($u.port, '80', 'telnet port'); $u.parse("file:///etc/hosts"); is $u.scheme, 'file', 'file scheme'; ok $u.authority.defined, 'no authority'; is $u.path, '/etc/hosts'; $u.parse('urn:oasis:names:specification:docbook:dtd:xml:4.1.2'); is($u.scheme, 'urn', 'urn scheme'); is($u.path, 'oasis:names:specification:docbook:dtd:xml:4.1.2', 'urn path'); # vim:ft=perl6 raku-uri-0.3.5/t/utf8-c8.t000066400000000000000000000006721414637465300151260ustar00rootroot00000000000000use Test; plan 1; use URI::Escape; my $uri = "file:///space/pub/music/mp3/Musopen%20DVD/Brahms%20-%20Symphony%20No%201%20in%20C%20Major/Symphony%20No.%201%20in%20C%20Minor,%20Op.%2068%20-%20IV.%20Adagio%20-%20Piu%CC%80%20andante%20-%20Allegro%20non%20troppo,%20ma%20con%20brio.mp3"; my $expected = "t/expected.txt".IO.slurp(:enc('utf8-c8')).chomp; is uri-unescape($uri, :enc('utf8-c8')), $expected, "uri-unescape works with encoding utf8-c8";