moved
This commit is contained in:
parent
addeecebdb
commit
a474d3e408
6
.gitmodules
vendored
6
.gitmodules
vendored
|
@ -1,10 +1,10 @@
|
|||
[submodule "backend/vendor/hare-http"]
|
||||
path = backend/vendor/hare-http
|
||||
path = vendor/hare-http
|
||||
url = https://git.fnordig.de/jer/hare-http.git
|
||||
branch = host-in-uri
|
||||
[submodule "backend/vendor/hare-logfmt"]
|
||||
path = backend/vendor/hare-logfmt
|
||||
path = vendor/hare-logfmt
|
||||
url = https://git.sr.ht/~blainsmith/hare-logfmt
|
||||
[submodule "backend/vendor/hare-json"]
|
||||
path = backend/vendor/hare-json
|
||||
path = vendor/hare-json
|
||||
url = https://git.sr.ht/~sircmpwn/hare-json
|
||||
|
|
0
backend/Cargo.lock → Cargo.lock
generated
0
backend/Cargo.lock → Cargo.lock
generated
1
backend/vendor/hare-http
vendored
1
backend/vendor/hare-http
vendored
|
@ -1 +0,0 @@
|
|||
Subproject commit f3a96257c7fc3594a1ec2cde37c248730a174e6f
|
1
backend/vendor/hare-json
vendored
1
backend/vendor/hare-json
vendored
|
@ -1 +0,0 @@
|
|||
Subproject commit b6aeae96199607a1f3b4d437d5c99f821bd6a6b6
|
1
backend/vendor/hare-logfmt
vendored
1
backend/vendor/hare-logfmt
vendored
|
@ -1 +0,0 @@
|
|||
Subproject commit 2b4a37459be54c83ac6ac6354cddec3e9fa796bf
|
367
vendor/hare-http/COPYING
vendored
Normal file
367
vendor/hare-http/COPYING
vendored
Normal file
|
@ -0,0 +1,367 @@
|
|||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
75
vendor/hare-http/cmd/http/main.ha
vendored
Normal file
75
vendor/hare-http/cmd/http/main.ha
vendored
Normal file
|
@ -0,0 +1,75 @@
|
|||
use getopt;
|
||||
use io;
|
||||
use log;
|
||||
use net::dial;
|
||||
use net::http;
|
||||
use net::uri;
|
||||
use os;
|
||||
use strings;
|
||||
|
||||
const usage: [_]getopt::help = [
|
||||
"HTTP client",
|
||||
('H', "Name:value", "Sets an HTTP header"),
|
||||
('X', "method", "Sets the HTTP method verb"),
|
||||
"url"
|
||||
];
|
||||
|
||||
export fn main() void = {
|
||||
const client = http::newclient("Hare net::http test client");
|
||||
defer http::client_finish(&client);
|
||||
|
||||
const cmd = getopt::parse(os::args, usage...);
|
||||
defer getopt::finish(&cmd);
|
||||
|
||||
if (len(cmd.args) != 1) {
|
||||
getopt::printusage(os::stderr, "http", usage)!;
|
||||
os::exit(os::status::FAILURE);
|
||||
};
|
||||
|
||||
const targ = match (uri::parse(cmd.args[0])) {
|
||||
case let u: uri::uri =>
|
||||
yield u;
|
||||
case uri::invalid =>
|
||||
log::fatal("Invalid URI");
|
||||
};
|
||||
defer uri::finish(&targ);
|
||||
|
||||
let req = http::new_request(&client, "GET", &targ)!;
|
||||
for (let i = 0z; i < len(cmd.opts); i += 1) {
|
||||
const (opt, val) = cmd.opts[i];
|
||||
switch (opt) {
|
||||
case 'H' =>
|
||||
const (name, val) = strings::cut(val, ":");
|
||||
http::header_add(&req.header, name, val);
|
||||
case 'X' =>
|
||||
req.method = val;
|
||||
case => abort();
|
||||
};
|
||||
};
|
||||
|
||||
const resp = match (http::do(&client, &req)) {
|
||||
case let err: http::error =>
|
||||
log::fatal("HTTP error:", http::strerror(err));
|
||||
case let resp: http::response =>
|
||||
yield resp;
|
||||
};
|
||||
defer http::response_finish(&resp);
|
||||
|
||||
log::printfln("HTTP/{}.{}: {} {}",
|
||||
resp.version.0, resp.version.1,
|
||||
resp.status, resp.reason);
|
||||
|
||||
for (let i = 0z; i < len(resp.header); i += 1) {
|
||||
const (name, val) = resp.header[i];
|
||||
log::printfln("{}: {}", name, val);
|
||||
};
|
||||
|
||||
const body = match (resp.body) {
|
||||
case let st: *io::stream =>
|
||||
yield st;
|
||||
case null =>
|
||||
return;
|
||||
};
|
||||
io::copy(os::stdout, body)!;
|
||||
io::close(body)!;
|
||||
};
|
92
vendor/hare-http/cmd/httpd/main.ha
vendored
Normal file
92
vendor/hare-http/cmd/httpd/main.ha
vendored
Normal file
|
@ -0,0 +1,92 @@
|
|||
use getopt;
|
||||
use net;
|
||||
use net::ip;
|
||||
use net::http;
|
||||
use net::dial;
|
||||
use os;
|
||||
use memio;
|
||||
use io;
|
||||
use fmt;
|
||||
use bufio;
|
||||
use strings;
|
||||
|
||||
const usage: [_]getopt::help = [
|
||||
"HTTP server",
|
||||
('a', "address", "listened address (ex: 127.0.0.1:8080)")
|
||||
];
|
||||
|
||||
export fn main() void = {
|
||||
const cmd = getopt::parse(os::args, usage...);
|
||||
defer getopt::finish(&cmd);
|
||||
|
||||
let port: u16 = 8080;
|
||||
let ip_addr: ip::addr4 = [127, 0, 0, 1];
|
||||
|
||||
for (let i = 0z; i < len(cmd.opts); i += 1) {
|
||||
const opt = cmd.opts[i];
|
||||
switch (opt.0) {
|
||||
case 'a' =>
|
||||
match (dial::splitaddr(opt.1, "")) {
|
||||
case let value: (str, u16) =>
|
||||
ip_addr = ip::parsev4(value.0)!;
|
||||
port = value.1;
|
||||
case dial::invalid_address =>
|
||||
abort("Invalid address");
|
||||
};
|
||||
case => abort(); // unreachable
|
||||
};
|
||||
};
|
||||
|
||||
const server = match (http::listen(ip_addr, port)) {
|
||||
case let this: *http::server =>
|
||||
yield this;
|
||||
case net::error => abort("failure while listening");
|
||||
};
|
||||
defer http::server_finish(server);
|
||||
|
||||
for (true) {
|
||||
const serv_req = match (http::serve(server)) {
|
||||
case let this: *http::server_request =>
|
||||
yield this;
|
||||
case net::error => abort("failure while serving");
|
||||
};
|
||||
defer http::serve_finish(serv_req);
|
||||
|
||||
let buf = memio::dynamic();
|
||||
defer io::close(&buf)!;
|
||||
handlereq(&buf, &serv_req.request);
|
||||
|
||||
http::response_write(
|
||||
serv_req.socket,
|
||||
http::STATUS_OK,
|
||||
&buf,
|
||||
("Content-Type", "text/plain")
|
||||
)!;
|
||||
};
|
||||
};
|
||||
|
||||
export fn handlereq(buf: *io::stream, request: *http::request) void = {
|
||||
fmt::fprintfln(buf, "Method: {}", request.method)!;
|
||||
fmt::fprintfln(buf, "Path: {}", request.target.path)!;
|
||||
fmt::fprintfln(buf, "Fragment: {}", request.target.fragment)!;
|
||||
fmt::fprintfln(buf, "Query: {}", request.target.query)!;
|
||||
fmt::fprintfln(buf, "Headers: <<EOF")!;
|
||||
http::write_header(buf, &request.header)!;
|
||||
fmt::fprintfln(buf, "EOF")!;
|
||||
|
||||
match (request.body) {
|
||||
case void => void;
|
||||
case let body: io::handle =>
|
||||
fmt::fprintfln(buf, "Body: <<EOF")!;
|
||||
for (true) {
|
||||
match (bufio::read_line(body)!) {
|
||||
case let line: []u8 =>
|
||||
fmt::fprintln(buf, strings::fromutf8(line)!)!;
|
||||
break;
|
||||
case io::EOF =>
|
||||
break;
|
||||
};
|
||||
};
|
||||
fmt::fprintfln(buf, "EOF")!;
|
||||
};
|
||||
};
|
17
vendor/hare-http/net/http/README
vendored
Normal file
17
vendor/hare-http/net/http/README
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
net::http provides an implementation of an HTTP 1.1 client and server as defined
|
||||
by RFC 9110 et al.
|
||||
|
||||
TODO: Flesh me out
|
||||
|
||||
Caveats:
|
||||
|
||||
- No attempt is made to validate that the input for client requests or responses
|
||||
are valid according to the HTTP grammar; such cases will fail when rejected by
|
||||
the other party.
|
||||
- Details indicated by RFC 7230 et al as "obsolete" are not implemented
|
||||
- Max header length including "name: value" is 4KiB
|
||||
|
||||
TODO:
|
||||
|
||||
- Server stuff
|
||||
- TLS
|
95
vendor/hare-http/net/http/client.ha
vendored
Normal file
95
vendor/hare-http/net/http/client.ha
vendored
Normal file
|
@ -0,0 +1,95 @@
|
|||
use io;
|
||||
use net::uri;
|
||||
|
||||
export type client = struct {
|
||||
default_header: header,
|
||||
default_transport: transport,
|
||||
};
|
||||
|
||||
// Creates a new HTTP [[client]] with the provided User-Agent string.
|
||||
//
|
||||
// The HTTP client implements a number of sane defaults, which may be tuned. The
|
||||
// set of default headers is configured with [[client_default_header]], and the
|
||||
// default transport behavior with [[client_default_transport]].
|
||||
//
|
||||
// TODO: Implement and document the connection pool
|
||||
//
|
||||
// The caller must pass the client object to [[client_finish]] to free resources
|
||||
// associated with this client after use.
|
||||
export fn newclient(ua: str) client = {
|
||||
let client = client { ... };
|
||||
header_add(&client, "User-Agent", ua);
|
||||
return client;
|
||||
};
|
||||
|
||||
// Frees resources associated with an HTTP [[client]].
|
||||
export fn client_finish(client: *client) void = {
|
||||
header_free(&client.default_header);
|
||||
};
|
||||
|
||||
// Returns the default headers used by this HTTP client, so that the user can
|
||||
// examine or modify the net::http defaults (such as User-Agent or
|
||||
// Accept-Encoding), or add their own.
|
||||
export fn client_default_header(client: *client) *header = {
|
||||
return &client.default_header;
|
||||
};
|
||||
|
||||
// Returns the default [[transport]] configuration used by this HTTP client.
|
||||
export fn client_default_transport(client: *client) *transport = {
|
||||
return &client.default_transport;
|
||||
};
|
||||
|
||||
fn uri_origin_form(target: *uri::uri) uri::uri = {
|
||||
let target = *target;
|
||||
target.scheme = "";
|
||||
target.host = "";
|
||||
target.fragment = "";
|
||||
target.userinfo = "";
|
||||
target.port = 0;
|
||||
if (target.path == "") {
|
||||
target.path = "/";
|
||||
};
|
||||
return target;
|
||||
};
|
||||
|
||||
// Performs a synchronous HTTP GET request with the given client.
|
||||
export fn get(client: *client, target: *uri::uri) (response | error) = {
|
||||
const req = new_request(client, GET, target)?;
|
||||
defer request_finish(&req);
|
||||
return do(client, &req);
|
||||
};
|
||||
|
||||
// Performs a synchronous HTTP HEAD request with the given client.
|
||||
export fn head(client: *client, target: *uri::uri) (response | error) = {
|
||||
const req = new_request(client, HEAD, target)?;
|
||||
defer request_finish(&req);
|
||||
return do(client, &req);
|
||||
};
|
||||
|
||||
// Performs a synchronous HTTP POST request with the given client.
|
||||
//
|
||||
// If the provided I/O handle is seekable, the Content-Length header is added
|
||||
// automatically. Otherwise, Transfer-Encoding: chunked will be used.
|
||||
export fn post(
|
||||
client: *client,
|
||||
target: *uri::uri,
|
||||
body: io::handle,
|
||||
) (response | error) = {
|
||||
const req = new_request_body(client, POST, target, body)?;
|
||||
defer request_finish(&req);
|
||||
return do(client, &req);
|
||||
};
|
||||
|
||||
// Performs a synchronous HTTP PUT request with the given client.
|
||||
//
|
||||
// If the provided I/O handle is seekable, the Content-Length header is added
|
||||
// automatically. Otherwise, Transfer-Encoding: chunked will be used.
|
||||
export fn put(
|
||||
client: *client,
|
||||
target: *uri::uri,
|
||||
body: io::handle,
|
||||
) (response | error) = {
|
||||
const req = new_request_body(client, PUT, target, body)?;
|
||||
defer request_finish(&req);
|
||||
return do(client, &req);
|
||||
};
|
112
vendor/hare-http/net/http/constants.ha
vendored
Normal file
112
vendor/hare-http/net/http/constants.ha
vendored
Normal file
|
@ -0,0 +1,112 @@
|
|||
// HTTP "GET" method.
|
||||
export def GET: str = "GET";
|
||||
// HTTP "HEAD" method.
|
||||
export def HEAD: str = "HEAD";
|
||||
// HTTP "POST" method.
|
||||
export def POST: str = "POST";
|
||||
// HTTP "PUT" method.
|
||||
export def PUT: str = "PUT";
|
||||
// HTTP "DELETE" method.
|
||||
export def DELETE: str = "DELETE";
|
||||
// HTTP "OPTIONS" method.
|
||||
export def OPTIONS: str = "OPTIONS";
|
||||
// HTTP "PATCH" method.
|
||||
export def PATCH: str = "PATCH";
|
||||
// HTTP "CONNECT" method.
|
||||
export def CONNECT: str = "CONNECT";
|
||||
|
||||
// HTTP "Continue" response status (100).
|
||||
export def STATUS_CONTINUE: uint = 100;
|
||||
// HTTP "Switching Protocols" response status (101).
|
||||
export def STATUS_SWITCHING_PROTOCOLS: uint = 101;
|
||||
|
||||
// HTTP "OK" response status (200).
|
||||
export def STATUS_OK: uint = 200;
|
||||
// HTTP "Created" response status (201).
|
||||
export def STATUS_CREATED: uint = 201;
|
||||
// HTTP "Accepted" response status (202).
|
||||
export def STATUS_ACCEPTED: uint = 202;
|
||||
// HTTP "Non-authoritative Info" response status (203).
|
||||
export def STATUS_NONAUTHORITATIVE_INFO: uint = 203;
|
||||
// HTTP "No Content" response status (204).
|
||||
export def STATUS_NO_CONTENT: uint = 204;
|
||||
// HTTP "Reset Content" response status (205).
|
||||
export def STATUS_RESET_CONTENT: uint = 205;
|
||||
// HTTP "Partial Content" response status (206).
|
||||
export def STATUS_PARTIAL_CONTENT: uint = 206;
|
||||
|
||||
// HTTP "Multiple Choices" response status (300).
|
||||
export def STATUS_MULTIPLE_CHOICES: uint = 300;
|
||||
// HTTP "Moved Permanently" response status (301).
|
||||
export def STATUS_MOVED_PERMANENTLY: uint = 301;
|
||||
// HTTP "Found" response status (302).
|
||||
export def STATUS_FOUND: uint = 302;
|
||||
// HTTP "See Other" response status (303).
|
||||
export def STATUS_SEE_OTHER: uint = 303;
|
||||
// HTTP "Not Modified" response status (304).
|
||||
export def STATUS_NOT_MODIFIED: uint = 304;
|
||||
// HTTP "Use Proxy" response status (305).
|
||||
export def STATUS_USE_PROXY: uint = 305;
|
||||
|
||||
// HTTP "Temporary Redirect" response status (307).
|
||||
export def STATUS_TEMPORARY_REDIRECT: uint = 307;
|
||||
// HTTP "Permanent Redirect" response status (308).
|
||||
export def STATUS_PERMANENT_REDIRECT: uint = 308;
|
||||
|
||||
// HTTP "Bad Request" response status (400).
|
||||
export def STATUS_BAD_REQUEST: uint = 400;
|
||||
// HTTP "Unauthorized" response status (401).
|
||||
export def STATUS_UNAUTHORIZED: uint = 401;
|
||||
// HTTP "Payment Required" response status (402).
|
||||
export def STATUS_PAYMENT_REQUIRED: uint = 402;
|
||||
// HTTP "Forbidden" response status (403).
|
||||
export def STATUS_FORBIDDEN: uint = 403;
|
||||
// HTTP "Not Found" response status (404).
|
||||
export def STATUS_NOT_FOUND: uint = 404;
|
||||
// HTTP "Method Not Allowed" response status (405).
|
||||
export def STATUS_METHOD_NOT_ALLOWED: uint = 405;
|
||||
// HTTP "Not Acceptable" response status (406).
|
||||
export def STATUS_NOT_ACCEPTABLE: uint = 406;
|
||||
// HTTP "Proxy Authentication Required" response status (407).
|
||||
export def STATUS_PROXY_AUTH_REQUIRED: uint = 407;
|
||||
// HTTP "Request Timeout" response status (408).
|
||||
export def STATUS_REQUEST_TIMEOUT: uint = 408;
|
||||
// HTTP "Conflict" response status (409).
|
||||
export def STATUS_CONFLICT: uint = 409;
|
||||
// HTTP "Gone" response status (410).
|
||||
export def STATUS_GONE: uint = 410;
|
||||
// HTTP "Length Required" response status (411).
|
||||
export def STATUS_LENGTH_REQUIRED: uint = 411;
|
||||
// HTTP "Precondition Failed" response status (412).
|
||||
export def STATUS_PRECONDITION_FAILED: uint = 412;
|
||||
// HTTP "Request Entity Too Large" response status (413).
|
||||
export def STATUS_REQUEST_ENTITY_TOO_LARGE: uint = 413;
|
||||
// HTTP "Request URI Too Long" response status (414).
|
||||
export def STATUS_REQUEST_URI_TOO_LONG: uint = 414;
|
||||
// HTTP "Unsupported Media Type" response status (415).
|
||||
export def STATUS_UNSUPPORTED_MEDIA_TYPE: uint = 415;
|
||||
// HTTP "Requested Range Not Satisfiable" response status (416).
|
||||
export def STATUS_REQUESTED_RANGE_NOT_SATISFIABLE: uint = 416;
|
||||
// HTTP "Expectation Failed" response status (417).
|
||||
export def STATUS_EXPECTATION_FAILED: uint = 417;
|
||||
// HTTP "I'm a Teapot" response status (418).
|
||||
export def STATUS_TEAPOT: uint = 418;
|
||||
// HTTP "Misdirected Request" response status (421).
|
||||
export def STATUS_MISDIRECTED_REQUEST: uint = 421;
|
||||
// HTTP "Unprocessable Entity" response status (422).
|
||||
export def STATUS_UNPROCESSABLE_ENTITY: uint = 422;
|
||||
// HTTP "Upgrade Required" response status (426).
|
||||
export def STATUS_UPGRADE_REQUIRED: uint = 426;
|
||||
|
||||
// HTTP "Internal Server Error" response status (500).
|
||||
export def STATUS_INTERNAL_SERVER_ERROR: uint = 500;
|
||||
// HTTP "Not Implemented" response status (501).
|
||||
export def STATUS_NOT_IMPLEMENTED: uint = 501;
|
||||
// HTTP "Bad Gateway" response status (502).
|
||||
export def STATUS_BAD_GATEWAY: uint = 502;
|
||||
// HTTP "Service Unavailable" response status (503).
|
||||
export def STATUS_SERVICE_UNAVAILABLE: uint = 503;
|
||||
// HTTP "Gateway Timeout" response status (504).
|
||||
export def STATUS_GATEWAY_TIMEOUT: uint = 504;
|
||||
// HTTP "HTTP Version Not Supported" response status (505).
|
||||
export def STATUS_HTTP_VERSION_NOT_SUPPORTED: uint = 505;
|
144
vendor/hare-http/net/http/do.ha
vendored
Normal file
144
vendor/hare-http/net/http/do.ha
vendored
Normal file
|
@ -0,0 +1,144 @@
|
|||
use bufio;
|
||||
use encoding::utf8;
|
||||
use errors;
|
||||
use fmt;
|
||||
use io;
|
||||
use net::dial;
|
||||
use net::uri;
|
||||
use net;
|
||||
use os;
|
||||
use strconv;
|
||||
use strings;
|
||||
use types;
|
||||
|
||||
// Performs an HTTP [[request]] with the given [[client]]. The request is
|
||||
// performed synchronously; this function blocks until the server has returned
|
||||
// the response status and all HTTP headers associated with the response.
|
||||
//
|
||||
// If the provided [[response]] has a non-null body, the user must pass it to
|
||||
// [[io::close]] before calling [[response_finish]].
|
||||
export fn do(client: *client, req: *request) (response | error) = {
|
||||
assert(req.target.scheme == "http"); // TODO: https
|
||||
const conn = dial::dial_uri("tcp", req.target)?;
|
||||
|
||||
let buf: [os::BUFSZ]u8 = [0...];
|
||||
let file = bufio::init(conn, [], buf);
|
||||
bufio::setflush(&file, []);
|
||||
|
||||
fmt::fprintf(&file, "{} ", req.method)?;
|
||||
|
||||
// TODO: Support other request-targets than origin-form
|
||||
const target = uri_origin_form(req.target);
|
||||
uri::fmt(&file, &target)?;
|
||||
fmt::fprintf(&file, " HTTP/1.1\r\n")?;
|
||||
|
||||
write_header(&file, &req.header)?;
|
||||
fmt::fprintf(&file, "\r\n")?;
|
||||
bufio::flush(&file)?;
|
||||
|
||||
const trans = match (req.transport) {
|
||||
case let t: *transport =>
|
||||
yield t;
|
||||
case =>
|
||||
yield &client.default_transport;
|
||||
};
|
||||
// TODO: Implement None
|
||||
assert(trans.request_transport == transport_mode::AUTO);
|
||||
assert(trans.response_transport == transport_mode::AUTO);
|
||||
assert(trans.request_content == content_mode::AUTO);
|
||||
assert(trans.response_content == content_mode::AUTO);
|
||||
|
||||
match (req.body) {
|
||||
case let body: io::handle =>
|
||||
io::copy(conn, body)?;
|
||||
case void =>
|
||||
yield;
|
||||
};
|
||||
|
||||
let resp = response { ... };
|
||||
const scan = bufio::newscanner(conn, 512);
|
||||
read_statusline(&resp, &scan)?;
|
||||
read_header(&resp.header, &scan)?;
|
||||
|
||||
const response_complete =
|
||||
req.method == "HEAD" ||
|
||||
resp.status == STATUS_NO_CONTENT ||
|
||||
resp.status == STATUS_NOT_MODIFIED ||
|
||||
(resp.status >= 100 && resp.status < 200) ||
|
||||
(req.method == "CONNECT" && resp.status >= 200 && resp.status < 300);
|
||||
if (!response_complete) {
|
||||
resp.body = new_reader(conn, &resp, &scan)?;
|
||||
} else if (req.method != "CONNECT") {
|
||||
io::close(conn)!;
|
||||
};
|
||||
return resp;
|
||||
};
|
||||
|
||||
fn read_statusline(
|
||||
resp: *response,
|
||||
scan: *bufio::scanner,
|
||||
) (void | error) = {
|
||||
const status = match (bufio::scan_string(scan, "\r\n")) {
|
||||
case let line: const str =>
|
||||
yield line;
|
||||
case let err: io::error =>
|
||||
return err;
|
||||
case utf8::invalid =>
|
||||
return protoerr;
|
||||
case io::EOF =>
|
||||
return protoerr;
|
||||
};
|
||||
|
||||
const tok = strings::tokenize(status, " ");
|
||||
|
||||
const version = match (strings::next_token(&tok)) {
|
||||
case let ver: str =>
|
||||
yield ver;
|
||||
case done =>
|
||||
return protoerr;
|
||||
};
|
||||
|
||||
const status = match (strings::next_token(&tok)) {
|
||||
case let status: str =>
|
||||
yield status;
|
||||
case done =>
|
||||
return protoerr;
|
||||
};
|
||||
|
||||
const reason = match (strings::next_token(&tok)) {
|
||||
case let reason: str =>
|
||||
yield reason;
|
||||
case done =>
|
||||
return protoerr;
|
||||
};
|
||||
|
||||
const (_, version) = strings::cut(version, "/");
|
||||
const (major, minor) = strings::cut(version, ".");
|
||||
|
||||
const major = match (strconv::stou(major)) {
|
||||
case let u: uint =>
|
||||
yield u;
|
||||
case =>
|
||||
return protoerr;
|
||||
};
|
||||
const minor = match (strconv::stou(minor)) {
|
||||
case let u: uint =>
|
||||
yield u;
|
||||
case =>
|
||||
return protoerr;
|
||||
};
|
||||
resp.version = (major, minor);
|
||||
|
||||
if (resp.version.0 > 1) {
|
||||
return errors::unsupported;
|
||||
};
|
||||
|
||||
resp.status = match (strconv::stou(status)) {
|
||||
case let u: uint =>
|
||||
yield u;
|
||||
case =>
|
||||
return protoerr;
|
||||
};
|
||||
|
||||
resp.reason = strings::dup(reason);
|
||||
};
|
26
vendor/hare-http/net/http/error.ha
vendored
Normal file
26
vendor/hare-http/net/http/error.ha
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
use errors;
|
||||
use io;
|
||||
use net::dial;
|
||||
|
||||
// Errors possible while servicing HTTP requests. Note that these errors are for
|
||||
// errors related to the processing of the HTTP connection; semantic HTTP errors
|
||||
// such as [[STATUS_NOT_FOUND]] are not handled by this type.
|
||||
export type error = !(dial::error | io::error | errors::unsupported | protoerr);
|
||||
|
||||
// An HTTP protocol error occurred, indicating that the remote party is not
|
||||
// conformant with HTTP semantics.
|
||||
export type protoerr = !void;
|
||||
|
||||
// Converts an [[error]] to a string.
|
||||
export fn strerror(err: error) const str = {
|
||||
match (err) {
|
||||
case let err: dial::error =>
|
||||
return dial::strerror(err);
|
||||
case let err: io::error =>
|
||||
return io::strerror(err);
|
||||
case errors::unsupported =>
|
||||
return "Unsupported HTTP feature";
|
||||
case protoerr =>
|
||||
return "HTTP protocol error";
|
||||
};
|
||||
};
|
105
vendor/hare-http/net/http/header.ha
vendored
Normal file
105
vendor/hare-http/net/http/header.ha
vendored
Normal file
|
@ -0,0 +1,105 @@
|
|||
use bufio;
|
||||
use encoding::utf8;
|
||||
use fmt;
|
||||
use io;
|
||||
use strings;
|
||||
|
||||
// List of HTTP headers.
|
||||
// TODO: [](str, []str)
|
||||
export type header = [](str, str);
|
||||
|
||||
// Adds a given HTTP header, which may be added more than once. The name should
|
||||
// be canonicalized by the caller.
|
||||
export fn header_add(head: *header, name: str, val: str) void = {
|
||||
assert(len(name) >= 1 && len(val) >= 1);
|
||||
append(head, (strings::dup(name), strings::dup(val)));
|
||||
};
|
||||
|
||||
// Sets the value of a given HTTP header, removing any previous values. The name
|
||||
// should be canonicalized by the caller.
|
||||
export fn header_set(head: *header, name: str, val: str) void = {
|
||||
header_del(head, name);
|
||||
header_add(head, name, val);
|
||||
};
|
||||
|
||||
// Removes an HTTP header from a list of [[header]]. If multiple headers match
|
||||
// the given name, all matching headers are removed.
|
||||
export fn header_del(head: *header, name: str) void = {
|
||||
for (let i = 0z; i < len(head); i += 1) {
|
||||
if (head[i].0 == name) {
|
||||
free(head[i].0);
|
||||
free(head[i].1);
|
||||
delete(head[i]);
|
||||
i -= 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// Retrieves a value, or values, from a header. An empty string indicates the
|
||||
// absence of a header.
|
||||
export fn header_get(head: *header, name: str) str = {
|
||||
for (let i = 0z; i < len(head); i += 1) {
|
||||
const (key, val) = head[i];
|
||||
if (key == name) {
|
||||
return val;
|
||||
};
|
||||
};
|
||||
return "";
|
||||
};
|
||||
|
||||
// Frees state associated with an HTTP [[header]].
|
||||
export fn header_free(head: *header) void = {
|
||||
for (let i = 0z; i < len(head); i += 1) {
|
||||
free(head[i].0);
|
||||
free(head[i].1);
|
||||
};
|
||||
free(*head);
|
||||
};
|
||||
|
||||
// Duplicates a set of HTTP headers.
|
||||
export fn header_dup(head: *header) header = {
|
||||
let new: header = [];
|
||||
for (let i = 0z; i < len(head); i += 1) {
|
||||
const (key, val) = head[i];
|
||||
header_add(&new, key, val);
|
||||
};
|
||||
return new;
|
||||
};
|
||||
|
||||
// Writes a list of HTTP headers to the provided I/O handle in the HTTP wire
|
||||
// format.
|
||||
export fn write_header(sink: io::handle, head: *header) (size | io::error) = {
|
||||
let z = 0z;
|
||||
for (let i = 0z; i < len(head); i += 1) {
|
||||
const (name, val) = head[i];
|
||||
z += fmt::fprintf(sink, "{}: {}\r\n", name, val)?;
|
||||
};
|
||||
return z;
|
||||
};
|
||||
|
||||
fn read_header(head: *header, scan: *bufio::scanner) (void | io::error | protoerr) = {
|
||||
for (true) {
|
||||
const item = match (bufio::scan_string(scan, "\r\n")) {
|
||||
case let line: const str =>
|
||||
yield line;
|
||||
case io::EOF =>
|
||||
break;
|
||||
case let err: io::error =>
|
||||
return err;
|
||||
case utf8::invalid =>
|
||||
return protoerr;
|
||||
};
|
||||
if (item == "") {
|
||||
break;
|
||||
};
|
||||
|
||||
let (name, val) = strings::cut(item, ":");
|
||||
val = strings::trim(val);
|
||||
if (val == "") {
|
||||
return protoerr;
|
||||
};
|
||||
// TODO: validate field-name
|
||||
|
||||
header_add(head, name, val);
|
||||
};
|
||||
};
|
278
vendor/hare-http/net/http/request.ha
vendored
Normal file
278
vendor/hare-http/net/http/request.ha
vendored
Normal file
|
@ -0,0 +1,278 @@
|
|||
use errors;
|
||||
use fmt;
|
||||
use io;
|
||||
use net::ip;
|
||||
use net::uri;
|
||||
use strconv;
|
||||
use strings;
|
||||
use bufio;
|
||||
use memio;
|
||||
use encoding::utf8;
|
||||
use types;
|
||||
|
||||
// Stores state related to an HTTP request.
|
||||
//
|
||||
// For a request to be processable by an HTTP [[client]], i.e. via [[do]], the
|
||||
// method and target must be filled in appropriately. The target must include at
|
||||
// least a host, port, and scheme. The default values for other fields are
|
||||
// suitable if appropriate for the request you wish to perform.
|
||||
export type request = struct {
|
||||
// HTTP request method, e.g. GET
|
||||
method: str,
|
||||
// Request target URI.
|
||||
//
|
||||
// Note that the normal constraints for [[uri::parse]] are not upheld in
|
||||
// the case of a request using the origin-form (e.g. /index.html), i.e.
|
||||
// the scheme field may be empty.
|
||||
target: *uri::uri,
|
||||
|
||||
// List of HTTP request headers.
|
||||
header: header,
|
||||
|
||||
// Transport configuration, or null to use the [[client]] default.
|
||||
transport: nullable *transport,
|
||||
|
||||
// I/O reader for the request body, or void if there is no body.
|
||||
body: (io::handle | void),
|
||||
};
|
||||
|
||||
// Frees state associated with an HTTP [[request]].
|
||||
export fn request_finish(req: *request) void = {
|
||||
header_free(&req.header);
|
||||
uri::finish(req.target);
|
||||
free(req.target);
|
||||
};
|
||||
|
||||
// Creates a new HTTP [[request]] using the given HTTP [[client]] defaults.
|
||||
export fn new_request(
|
||||
client: *client,
|
||||
method: str,
|
||||
target: *uri::uri,
|
||||
) (request | errors::unsupported) = {
|
||||
let req = request {
|
||||
method = method,
|
||||
target = alloc(uri::dup(target)),
|
||||
header = header_dup(&client.default_header),
|
||||
transport = null,
|
||||
body = void,
|
||||
};
|
||||
switch (req.target.scheme) {
|
||||
case "http" =>
|
||||
if (req.target.port == 0) {
|
||||
req.target.port = 80;
|
||||
};
|
||||
case "https" =>
|
||||
if (req.target.port == 0) {
|
||||
req.target.port = 443;
|
||||
};
|
||||
case =>
|
||||
return errors::unsupported;
|
||||
};
|
||||
|
||||
let host = match (req.target.host) {
|
||||
case let host: str =>
|
||||
yield host;
|
||||
case let ip: ip::addr4 =>
|
||||
yield ip::string(ip);
|
||||
case let ip: ip::addr6 =>
|
||||
static let buf: [64 + 2]u8 = [0...];
|
||||
yield fmt::bsprintf(buf, "[{}]", ip::string(ip));
|
||||
};
|
||||
|
||||
if (req.target.scheme == "http" && req.target.port != 80) {
|
||||
host = fmt::asprintf("{}:{}", host, req.target.port);
|
||||
} else if (target.scheme == "https" && target.port != 443) {
|
||||
host = fmt::asprintf("{}:{}", host, req.target.port);
|
||||
} else {
|
||||
host = strings::dup(host);
|
||||
};
|
||||
defer free(host);
|
||||
header_add(&req.header, "Host", host);
|
||||
return req;
|
||||
};
|
||||
|
||||
// Creates a new HTTP [[request]] using the given HTTP [[client]] defaults and
|
||||
// the provided request body.
|
||||
//
|
||||
// If the provided I/O handle is seekable, the Content-Length header is added
|
||||
// automatically. Otherwise, Transfer-Encoding: chunked will be used.
|
||||
export fn new_request_body(
|
||||
client: *client,
|
||||
method: str,
|
||||
target: *uri::uri,
|
||||
body: io::handle,
|
||||
) (request | errors::unsupported) = {
|
||||
let req = new_request(client, method, target)?;
|
||||
req.body = body;
|
||||
|
||||
const offs = match (io::seek(body, 0, io::whence::CUR)) {
|
||||
case let off: io::off =>
|
||||
yield off;
|
||||
case io::error =>
|
||||
header_add(&req.header, "Transfer-Encoding", "chunked");
|
||||
return req;
|
||||
};
|
||||
const ln = io::seek(body, 0, io::whence::END)!;
|
||||
io::seek(body, offs, io::whence::SET)!;
|
||||
header_add(&req.header, "Content-Length", strconv::ztos(ln: size));
|
||||
return req;
|
||||
};
|
||||
|
||||
export type request_server = void;
|
||||
export type authority = void;
|
||||
|
||||
export type request_uri = (
|
||||
request_server |
|
||||
authority |
|
||||
*uri::uri |
|
||||
str |
|
||||
);
|
||||
|
||||
export type request_line = struct {
|
||||
method: str,
|
||||
uri: request_uri,
|
||||
version: version,
|
||||
};
|
||||
|
||||
export fn request_parse(file: io::handle) (request | protoerr | io::error) = {
|
||||
const scan = bufio::newscanner(file, types::SIZE_MAX);
|
||||
defer bufio::finish(&scan);
|
||||
|
||||
const req_line = request_line_parse(&scan)?;
|
||||
defer request_line_finish(&req_line);
|
||||
|
||||
let header: header = [];
|
||||
read_header(&header, &scan)?;
|
||||
|
||||
const target = match (req_line.uri) {
|
||||
case let uri: request_server => return errors::unsupported;
|
||||
case let uri: authority => return errors::unsupported;
|
||||
case let uri: *uri::uri => yield uri;
|
||||
case let path: str =>
|
||||
const host = header_get(&header, "Host");
|
||||
const uri = fmt::asprintf("http://{}{}", host, path);
|
||||
defer free(uri);
|
||||
yield alloc(uri::parse(uri)!);
|
||||