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)!);
|
||||
};
|
||||
|
||||
const length: (void | size) = void;
|
||||
const head_length = header_get(&header, "Content-Length");
|
||||
if ("" != head_length) {
|
||||
match (strconv::stoz(head_length)) {
|
||||
case let s: size => length = s;
|
||||
case strconv::invalid => return protoerr;
|
||||
case strconv::overflow => return protoerr;
|
||||
};
|
||||
};
|
||||
|
||||
let body: (io::handle | void) = void;
|
||||
if (length is size) {
|
||||
const limit = io::limitreader(&scan, length as size);
|
||||
let _body = alloc(memio::dynamic());
|
||||
io::copy(_body, &limit)!;
|
||||
io::seek(_body, 0, io::whence::SET)!;
|
||||
body = _body;
|
||||
};
|
||||
|
||||
return request {
|
||||
method = req_line.method,
|
||||
target = target,
|
||||
header = header,
|
||||
body = body,
|
||||
...
|
||||
};
|
||||
};
|
||||
|
||||
export fn parsed_request_finish(request: *request) void = {
|
||||
uri::finish(request.target);
|
||||
free(request.target);
|
||||
|
||||
match (request.body) {
|
||||
case void => yield;
|
||||
case let body: io::handle =>
|
||||
io::close(body)!;
|
||||
free(body: *memio::stream);
|
||||
};
|
||||
|
||||
header_free(&request.header);
|
||||
};
|
||||
|
||||
export fn request_line_parse(scan: *bufio::scanner) (request_line | protoerr | io::error) = {
|
||||
const line = 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(line, " ");
|
||||
|
||||
const method = match (strings::next_token(&tok)) {
|
||||
case let method: str =>
|
||||
yield strings::dup(method);
|
||||
case done =>
|
||||
return protoerr;
|
||||
};
|
||||
|
||||
const uri: request_uri = match (strings::next_token(&tok)) {
|
||||
case let req_uri: str =>
|
||||
if ("*" == req_uri) {
|
||||
yield request_server;
|
||||
};
|
||||
|
||||
yield match (uri::parse(req_uri)) {
|
||||
case let uri: uri::uri => yield alloc(uri);
|
||||
case => yield strings::dup(req_uri); // as path
|
||||
};
|
||||
case done =>
|
||||
return protoerr;
|
||||
};
|
||||
|
||||
const version = match (strings::next_token(&tok)) {
|
||||
case let ver: str =>
|
||||
yield ver;
|
||||
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;
|
||||
};
|
||||
|
||||
if (major > 1) {
|
||||
return errors::unsupported;
|
||||
};
|
||||
|
||||
if (uri is request_server && method != OPTIONS) {
|
||||
return protoerr;
|
||||
};
|
||||
|
||||
return request_line {
|
||||
method = method,
|
||||
uri = uri,
|
||||
version = (major, minor),
|
||||
};
|
||||
};
|
||||
|
||||
export fn request_line_finish(line: *request_line) void = {
|
||||
match (line.uri) {
|
||||
case let path: str => free(path);
|
||||
case => yield;
|
||||
};
|
||||
};
|
63
vendor/hare-http/net/http/response.ha
vendored
Normal file
63
vendor/hare-http/net/http/response.ha
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
use io;
|
||||
use os;
|
||||
use fmt;
|
||||
use strconv;
|
||||
|
||||
export type version = (uint, uint);
|
||||
|
||||
// Stores state related to an HTTP response.
|
||||
export type response = struct {
|
||||
// HTTP protocol version (major, minor)
|
||||
version: version,
|
||||
// The HTTP status for this request as an integer.
|
||||
status: uint,
|
||||
// The HTTP status reason phrase.
|
||||
reason: str,
|
||||
// The HTTP headers provided by the server.
|
||||
header: header,
|
||||
// The response body, if any.
|
||||
body: nullable *io::stream,
|
||||
};
|
||||
|
||||
// Frees state associated with an HTTP [[response]]. If the response has a
|
||||
// non-null body, the user must call [[io::close]] prior to calling this
|
||||
// function.
|
||||
export fn response_finish(resp: *response) void = {
|
||||
header_free(&resp.header);
|
||||
free(resp.reason);
|
||||
free(resp.body);
|
||||
};
|
||||
|
||||
export fn response_write(
|
||||
rw: io::handle,
|
||||
status: uint,
|
||||
body: (void | io::handle),
|
||||
header: (str, str)...
|
||||
) (void | io::error) = {
|
||||
fmt::fprintfln(rw, "HTTP/1.1 {} {}", status, status_reason(status))?;
|
||||
|
||||
let header = header_dup(&header);
|
||||
defer header_free(&header);
|
||||
|
||||
match (body) {
|
||||
case void => void;
|
||||
case let body: io::handle =>
|
||||
match (io::tell(body)) {
|
||||
case io::error => void;
|
||||
case let off: io::off =>
|
||||
header_add(&header, "Content-Length", strconv::i64tos(off));
|
||||
io::seek(body, 0, io::whence::SET)!;
|
||||
body = &io::limitreader(body, off: size);
|
||||
};
|
||||
};
|
||||
|
||||
write_header(rw, &header)?;
|
||||
|
||||
fmt::fprintln(rw)!;
|
||||
|
||||
match (body) {
|
||||
case void => void;
|
||||
case let body: io::handle =>
|
||||
io::copy(rw, body)!;
|
||||
};
|
||||
};
|
44
vendor/hare-http/net/http/server.ha
vendored
Normal file
44
vendor/hare-http/net/http/server.ha
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
use net;
|
||||
use net::ip;
|
||||
use net::tcp;
|
||||
use net::tcp::{listen_option};
|
||||
|
||||
export type server = struct {
|
||||
socket: net::socket,
|
||||
};
|
||||
|
||||
export type server_request = struct {
|
||||
socket: net::socket,
|
||||
request: request,
|
||||
};
|
||||
|
||||
export fn listen(ip: ip::addr, port: u16, options: listen_option...) (*server | net::error) = {
|
||||
return alloc(server {
|
||||
socket = tcp::listen(ip, port, options...)?,
|
||||
});
|
||||
};
|
||||
|
||||
export fn server_finish(server: *server) void = {
|
||||
net::close(server.socket)!;
|
||||
free(server);
|
||||
};
|
||||
|
||||
export fn accept(server: *server) (net::socket | net::error) = {
|
||||
return net::accept(server.socket)?;
|
||||
};
|
||||
|
||||
export fn serve(server: *server) (*server_request | net::error) = {
|
||||
const socket = accept(server)?;
|
||||
const request = request_parse(socket)!;
|
||||
|
||||
return alloc(server_request {
|
||||
request = request,
|
||||
socket = socket,
|
||||
});
|
||||
};
|
||||
|
||||
export fn serve_finish(serv_req: *server_request) void = {
|
||||
parsed_request_finish(&serv_req.request);
|
||||
net::close(serv_req.socket)!;
|
||||
free(serv_req);
|
||||
};
|
111
vendor/hare-http/net/http/status.ha
vendored
Normal file
111
vendor/hare-http/net/http/status.ha
vendored
Normal file
|
@ -0,0 +1,111 @@
|
|||
// A semantic HTTP error and its status code.
|
||||
export type httperror = !uint;
|
||||
|
||||
// Checks if an HTTP status code is semantically considered an error, returning
|
||||
// [[httperror]] if so, or otherwise returning the original status code.
|
||||
export fn check(status: uint) (uint | httperror) = {
|
||||
if (status >= 400 && status < 600) {
|
||||
return status: httperror;
|
||||
};
|
||||
return status;
|
||||
};
|
||||
|
||||
// Converts a standard HTTP status code into the reason text typically
|
||||
// associated with this status code (or "Unknown Status" if the status code is
|
||||
// not known to net::http).
|
||||
export fn status_reason(status: uint) const str = {
|
||||
switch (status) {
|
||||
case STATUS_CONTINUE =>
|
||||
return "Continue";
|
||||
case STATUS_SWITCHING_PROTOCOLS =>
|
||||
return "Switching Protocols";
|
||||
case STATUS_OK =>
|
||||
return "Continue";
|
||||
case STATUS_CREATED =>
|
||||
return "Continue";
|
||||
case STATUS_ACCEPTED =>
|
||||
return "Accepted";
|
||||
case STATUS_NONAUTHORITATIVE_INFO =>
|
||||
return "Non-Authoritative Information";
|
||||
case STATUS_NO_CONTENT =>
|
||||
return "No Content";
|
||||
case STATUS_RESET_CONTENT =>
|
||||
return "Reset Content";
|
||||
case STATUS_PARTIAL_CONTENT =>
|
||||
return "Partial Content";
|
||||
case STATUS_MULTIPLE_CHOICES =>
|
||||
return "Multiple Choices";
|
||||
case STATUS_MOVED_PERMANENTLY =>
|
||||
return "Moved Permanently";
|
||||
case STATUS_FOUND =>
|
||||
return "Found";
|
||||
case STATUS_SEE_OTHER =>
|
||||
return "See Other";
|
||||
case STATUS_NOT_MODIFIED =>
|
||||
return "Not Modified";
|
||||
case STATUS_USE_PROXY =>
|
||||
return "Use Proxy";
|
||||
case STATUS_TEMPORARY_REDIRECT =>
|
||||
return "Temporary Redirect";
|
||||
case STATUS_PERMANENT_REDIRECT =>
|
||||
return "Permanent Redirect";
|
||||
case STATUS_BAD_REQUEST =>
|
||||
return "Bad Request";
|
||||
case STATUS_UNAUTHORIZED =>
|
||||
return "Unauthorized";
|
||||
case STATUS_PAYMENT_REQUIRED =>
|
||||
return "Payment Required";
|
||||
case STATUS_FORBIDDEN =>
|
||||
return "Forbidden";
|
||||
case STATUS_NOT_FOUND =>
|
||||
return "Not Found";
|
||||
case STATUS_METHOD_NOT_ALLOWED =>
|
||||
return "Method Not Allowed";
|
||||
case STATUS_NOT_ACCEPTABLE =>
|
||||
return "Not Acceptable";
|
||||
case STATUS_PROXY_AUTH_REQUIRED =>
|
||||
return "Proxy Authentication Required";
|
||||
case STATUS_REQUEST_TIMEOUT =>
|
||||
return "Request Timeout";
|
||||
case STATUS_CONFLICT =>
|
||||
return "Conflict";
|
||||
case STATUS_GONE =>
|
||||
return "Gone";
|
||||
case STATUS_LENGTH_REQUIRED =>
|
||||
return "Length Required";
|
||||
case STATUS_PRECONDITION_FAILED =>
|
||||
return "Precondition Failed";
|
||||
case STATUS_REQUEST_ENTITY_TOO_LARGE =>
|
||||
return "Request Entity Too Large";
|
||||
case STATUS_REQUEST_URI_TOO_LONG =>
|
||||
return "Request URI Too Long";
|
||||
case STATUS_UNSUPPORTED_MEDIA_TYPE =>
|
||||
return "Unsupported Media Type";
|
||||
case STATUS_REQUESTED_RANGE_NOT_SATISFIABLE =>
|
||||
return "Requested Range Not Satisfiable";
|
||||
case STATUS_EXPECTATION_FAILED =>
|
||||
return "Expectation Failed";
|
||||
case STATUS_TEAPOT =>
|
||||
return "I'm A Teapot";
|
||||
case STATUS_MISDIRECTED_REQUEST =>
|
||||
return "Misdirected Request";
|
||||
case STATUS_UNPROCESSABLE_ENTITY =>
|
||||
return "Unprocessable Entity";
|
||||
case STATUS_UPGRADE_REQUIRED =>
|
||||
return "Upgrade Required";
|
||||
case STATUS_INTERNAL_SERVER_ERROR =>
|
||||
return "Internal Server Error";
|
||||
case STATUS_NOT_IMPLEMENTED =>
|
||||
return "Not Implemented";
|
||||
case STATUS_BAD_GATEWAY =>
|
||||
return "Bad Gateway";
|
||||
case STATUS_SERVICE_UNAVAILABLE =>
|
||||
return "Service Unavailable";
|
||||
case STATUS_GATEWAY_TIMEOUT =>
|
||||
return "Gateway Timeout";
|
||||
case STATUS_HTTP_VERSION_NOT_SUPPORTED =>
|
||||
return "HTTP Version Not Supported";
|
||||
case =>
|
||||
return "Unknown status";
|
||||
};
|
||||
};
|
296
vendor/hare-http/net/http/transport.ha
vendored
Normal file
296
vendor/hare-http/net/http/transport.ha
vendored
Normal file
|
@ -0,0 +1,296 @@
|
|||
use errors;
|
||||
use bufio;
|
||||
use bytes;
|
||||
use io;
|
||||
use os;
|
||||
use strconv;
|
||||
use strings;
|
||||
use types;
|
||||
|
||||
// Configures the Transport-Encoding behavior.
|
||||
//
|
||||
// If set to NONE, no transport decoding or encoding is performed on the message
|
||||
// body, irrespective of the value of the Transport-Encoding header. The user
|
||||
// must perform any required encoding or decoding themselves in this mode. If
|
||||
// set to AUTO, the implementation will examine the Transport-Encoding header
|
||||
// and encode the message body appropriately.
|
||||
//
|
||||
// Most users will want this to be set to auto.
|
||||
export type transport_mode = enum {
|
||||
AUTO = 0,
|
||||
NONE,
|
||||
};
|
||||
|
||||
// Configures the Content-Encoding behavior.
|
||||
//
|
||||
// If set to NONE, no transport decoding or encoding is performed on the message
|
||||
// body, irrespective of the value of the Content-Encoding header. The user must
|
||||
// perform any required encoding or decoding themselves in this mode. If set to
|
||||
// AUTO, the implementation will examine the Content-Encoding header and encode
|
||||
// the message body appropriately.
|
||||
//
|
||||
// Most users will want this to be set to AUTO.
|
||||
export type content_mode = enum {
|
||||
AUTO = 0,
|
||||
NONE,
|
||||
};
|
||||
|
||||
// Describes an HTTP [[client]]'s transport configuration for a given request.
|
||||
//
|
||||
// The default value of this type sets all parameters to "auto".
|
||||
export type transport = struct {
|
||||
// Desired Transport-Encoding configuration, see [[transport_mode]] for
|
||||
// details.
|
||||
request_transport: transport_mode,
|
||||
response_transport: transport_mode,
|
||||
// Desired Content-Encoding configuration, see [[content_mode]] for
|
||||
// details.
|
||||
request_content: content_mode,
|
||||
response_content: content_mode,
|
||||
};
|
||||
|
||||
fn new_reader(
|
||||
conn: io::file,
|
||||
resp: *response,
|
||||
scan: *bufio::scanner,
|
||||
) (*io::stream | errors::unsupported | protoerr) = {
|
||||
// TODO: Content-Encoding support
|
||||
const cl = header_get(&resp.header, "Content-Length");
|
||||
const te = header_get(&resp.header, "Transfer-Encoding");
|
||||
|
||||
if (cl != "" || te == "") {
|
||||
let length = types::SIZE_MAX;
|
||||
if (cl != "") {
|
||||
length = match (strconv::stoz(cl)) {
|
||||
case let z: size =>
|
||||
yield z;
|
||||
case =>
|
||||
return protoerr;
|
||||
};
|
||||
};
|
||||
return new_identity_reader(conn, scan, length);
|
||||
};
|
||||
|
||||
// TODO: Figure out the semantics for closing the stream
|
||||
// The caller should probably be required to close it
|
||||
// It should close/free any intermediate transport/content decoders
|
||||
// And it should not close the actual connection if it's still in the
|
||||
// connection pool
|
||||
// Unless it isn't in the pool, then it should!
|
||||
let stream: io::handle = conn;
|
||||
let buffer: []u8 = bufio::scan_buffer(scan);
|
||||
const iter = strings::tokenize(te, ",");
|
||||
for (const tok => strings::next_token(&iter)) {
|
||||
const te = strings::trim(tok);
|
||||
|
||||
// XXX: We could add lzw support if someone added it to
|
||||
// hare-compress
|
||||
const next = switch (te) {
|
||||
case "chunked" =>
|
||||
yield new_chunked_reader(stream, buffer);
|
||||
case "deflate" =>
|
||||
abort(); // TODO
|
||||
case "gzip" =>
|
||||
abort(); // TODO
|
||||
case =>
|
||||
return errors::unsupported;
|
||||
};
|
||||
stream = next;
|
||||
|
||||
buffer = [];
|
||||
};
|
||||
|
||||
if (!(stream is *io::stream)) {
|
||||
// Empty Transfer-Encoding header
|
||||
return protoerr;
|
||||
};
|
||||
return stream as *io::stream;
|
||||
};
|
||||
|
||||
type identity_reader = struct {
|
||||
vtable: io::stream,
|
||||
conn: io::file,
|
||||
scan: *bufio::scanner,
|
||||
src: io::limitstream,
|
||||
};
|
||||
|
||||
const identity_reader_vtable = io::vtable {
|
||||
reader = &identity_read,
|
||||
closer = &identity_close,
|
||||
...
|
||||
};
|
||||
|
||||
// Creates a new reader that reads data until the response's Content-Length is
|
||||
// reached; i.e. the null Transport-Encoding.
|
||||
fn new_identity_reader(
|
||||
conn: io::file,
|
||||
scan: *bufio::scanner,
|
||||
content_length: size,
|
||||
) *io::stream = {
|
||||
const scan = alloc(*scan);
|
||||
return alloc(identity_reader {
|
||||
vtable = &identity_reader_vtable,
|
||||
conn = conn,
|
||||
scan = scan,
|
||||
src = io::limitreader(scan, content_length),
|
||||
...
|
||||
});
|
||||
};
|
||||
|
||||
fn identity_read(
|
||||
s: *io::stream,
|
||||
buf: []u8,
|
||||
) (size | io::EOF | io::error) = {
|
||||
let rd = s: *identity_reader;
|
||||
assert(rd.vtable == &identity_reader_vtable);
|
||||
return io::read(&rd.src, buf)?;
|
||||
};
|
||||
|
||||
fn identity_close(s: *io::stream) (void | io::error) = {
|
||||
let rd = s: *identity_reader;
|
||||
assert(rd.vtable == &identity_reader_vtable);
|
||||
|
||||
// Flush the remainder of the response in case the caller did not read
|
||||
// it out entirely
|
||||
io::copy(io::empty, &rd.src)?;
|
||||
|
||||
bufio::finish(rd.scan);
|
||||
free(rd.scan);
|
||||
io::close(rd.conn)?;
|
||||
};
|
||||
|
||||
type chunk_state = enum {
|
||||
HEADER,
|
||||
DATA,
|
||||
FOOTER,
|
||||
};
|
||||
|
||||
type chunked_reader = struct {
|
||||
vtable: io::stream,
|
||||
conn: io::handle,
|
||||
buffer: [os::BUFSZ]u8,
|
||||
state: chunk_state,
|
||||
// Amount of read-ahead data in buffer
|
||||
pending: size,
|
||||
// Length of current chunk
|
||||
length: size,
|
||||
};
|
||||
|
||||
fn new_chunked_reader(
|
||||
conn: io::handle,
|
||||
buffer: []u8,
|
||||
) *io::stream = {
|
||||
let rd = alloc(chunked_reader {
|
||||
vtable = &chunked_reader_vtable,
|
||||
conn = conn,
|
||||
...
|
||||
});
|
||||
rd.buffer[..len(buffer)] = buffer[..];
|
||||
rd.pending = len(buffer);
|
||||
return rd;
|
||||
};
|
||||
|
||||
const chunked_reader_vtable = io::vtable {
|
||||
reader = &chunked_read,
|
||||
...
|
||||
};
|
||||
|
||||
fn chunked_read(
|
||||
s: *io::stream,
|
||||
buf: []u8,
|
||||
) (size | io::EOF | io::error) = {
|
||||
// XXX: I am not satisfied with this code
|
||||
let rd = s: *chunked_reader;
|
||||
assert(rd.vtable == &chunked_reader_vtable);
|
||||
|
||||
for (true) switch (rd.state) {
|
||||
case chunk_state::HEADER =>
|
||||
let crlf = 0z;
|
||||
for (true) {
|
||||
const n = rd.pending;
|
||||
match (bytes::index(rd.buffer[..n], ['\r', '\n'])) {
|
||||
case let z: size =>
|
||||
crlf = z;
|
||||
break;
|
||||
case void =>
|
||||
yield;
|
||||
};
|
||||
if (rd.pending >= len(rd.buffer)) {
|
||||
// Chunk header exceeds buffer size
|
||||
return errors::overflow;
|
||||
};
|
||||
|
||||
match (io::read(rd.conn, rd.buffer[rd.pending..])?) {
|
||||
case let n: size =>
|
||||
rd.pending += n;
|
||||
case io::EOF =>
|
||||
if (rd.pending > 0) {
|
||||
return errors::invalid;
|
||||
};
|
||||
return io::EOF;
|
||||
};
|
||||
};
|
||||
|
||||
// XXX: Should we do anything with chunk-ext?
|
||||
const header = rd.buffer[..crlf];
|
||||
const (ln, _) = bytes::cut(header, ';');
|
||||
const ln = match (strings::fromutf8(ln)) {
|
||||
case let s: str =>
|
||||
yield s;
|
||||
case =>
|
||||
return errors::invalid;
|
||||
};
|
||||
|
||||
match (strconv::stoz(ln, strconv::base::HEX)) {
|
||||
case let z: size =>
|
||||
rd.length = z;
|
||||
case =>
|
||||
return errors::invalid;
|
||||
};
|
||||
if (rd.length == 0) {
|
||||
return io::EOF;
|
||||
};
|
||||
|
||||
const n = crlf + 2;
|
||||
rd.buffer[..rd.pending - n] = rd.buffer[n..rd.pending];
|
||||
rd.pending -= n;
|
||||
rd.state = chunk_state::DATA;
|
||||
case chunk_state::DATA =>
|
||||
if (rd.pending == 0) {
|
||||
match (io::read(rd.conn, rd.buffer)?) {
|
||||
case let n: size =>
|
||||
rd.pending += n;
|
||||
case io::EOF =>
|
||||
return io::EOF;
|
||||
};
|
||||
};
|
||||
let n = len(buf);
|
||||
if (n > rd.pending) {
|
||||
n = rd.pending;
|
||||
};
|
||||
if (n > rd.length) {
|
||||
n = rd.length;
|
||||
};
|
||||
buf[..n] = rd.buffer[..n];
|
||||
rd.buffer[..rd.pending - n] = rd.buffer[n..rd.pending];
|
||||
rd.pending -= n;
|
||||
rd.length -= n;
|
||||
rd.state = chunk_state::FOOTER;
|
||||
return n;
|
||||
case chunk_state::FOOTER =>
|
||||
for (rd.pending < 2) {
|
||||
match (io::read(rd.conn, rd.buffer[rd.pending..])?) {
|
||||
case let n: size =>
|
||||
rd.pending += n;
|
||||
case io::EOF =>
|
||||
return io::EOF;
|
||||
};
|
||||
};
|
||||
if (!bytes::equal(rd.buffer[..2], ['\r', '\n'])) {
|
||||
return errors::invalid;
|
||||
};
|
||||
rd.buffer[..rd.pending - 2] = rd.buffer[2..rd.pending];
|
||||
rd.pending -= 2;
|
||||
rd.state = chunk_state::HEADER;
|
||||
};
|
||||
};
|
367
vendor/hare-json/COPYING
vendored
Normal file
367
vendor/hare-json/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.
|
36
vendor/hare-json/Makefile
vendored
Normal file
36
vendor/hare-json/Makefile
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
.POSIX:
|
||||
.SUFFIXES:
|
||||
HARE=hare
|
||||
HAREFLAGS=
|
||||
HAREDOC=haredoc
|
||||
|
||||
DESTDIR=
|
||||
PREFIX=/usr/local
|
||||
SRCDIR=$(PREFIX)/src
|
||||
HARESRCDIR=$(SRCDIR)/hare
|
||||
THIRDPARTYDIR=$(HARESRCDIR)/third-party
|
||||
|
||||
all:
|
||||
@true # no-op
|
||||
|
||||
check:
|
||||
$(HARE) test
|
||||
|
||||
clean:
|
||||
rm -rf docs
|
||||
|
||||
docs:
|
||||
mkdir -p docs/encoding/json
|
||||
$(HAREDOC) -Fhtml encoding > docs/encoding/index.html
|
||||
$(HAREDOC) -Fhtml encoding::json > docs/encoding/json/index.html
|
||||
|
||||
install:
|
||||
mkdir -p "$(DESTDIR)$(THIRDPARTYDIR)"/encoding
|
||||
mkdir -p "$(DESTDIR)$(THIRDPARTYDIR)"/encoding/json
|
||||
install -m644 encoding/json/README "$(DESTDIR)$(THIRDPARTYDIR)"/encoding/json/README
|
||||
install -m644 encoding/json/*.ha "$(DESTDIR)$(THIRDPARTYDIR)"/encoding/json
|
||||
|
||||
uninstall:
|
||||
rm -rf $(DESTDIR)$(THIRDPARTYDIR)/encoding/json
|
||||
|
||||
.PHONY: all docs clean check install uninstall
|
23
vendor/hare-json/README.md
vendored
Normal file
23
vendor/hare-json/README.md
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
# hare-json
|
||||
|
||||
This package provides JSON support for Hare.
|
||||
|
||||
## Installation
|
||||
|
||||
### From your distribution
|
||||
|
||||
The recommended name for this package is "hare-json". Look for this, or
|
||||
something similar, in your local package manager. This is the preferred way to
|
||||
install this package.
|
||||
|
||||
### System-wide installation
|
||||
|
||||
```
|
||||
make install
|
||||
```
|
||||
|
||||
### Vendoring
|
||||
|
||||
```
|
||||
git subtree -P vendor/hare-json/ add https://git.sr.ht/~sircmpwn/hare-json master
|
||||
```
|
62
vendor/hare-json/encoding/json/+test/lexer.ha
vendored
Normal file
62
vendor/hare-json/encoding/json/+test/lexer.ha
vendored
Normal file
|
@ -0,0 +1,62 @@
|
|||
use io;
|
||||
use memio;
|
||||
use strings;
|
||||
|
||||
@test fn lex() void = {
|
||||
const cases: [_](str, []token) = [
|
||||
("true", [true]),
|
||||
("false", [false]),
|
||||
("null", [_null]),
|
||||
("1234", [1234.0]),
|
||||
("12.34", [12.34]),
|
||||
("12.34e5", [12.34e5]),
|
||||
("12.34E5", [12.34e5]),
|
||||
("12.34e+5", [12.34e5]),
|
||||
("12.34e-5", [12.34e-5]),
|
||||
("12e5", [12.0e5]),
|
||||
("-1234", [-1234.0]),
|
||||
(`"hello world"`, ["hello world"]),
|
||||
(`"\"\\\/\b\f\n\r\t\u0020"`, ["\"\\/\b\f\n\r\t\u0020"]),
|
||||
("[ null, null ]", [arraystart, _null, comma, _null, arrayend]),
|
||||
];
|
||||
|
||||
for (let i = 0z; i < len(cases); i += 1) {
|
||||
const src = strings::toutf8(cases[i].0);
|
||||
const src = memio::fixed(src);
|
||||
const lexer = newlexer(&src);
|
||||
defer close(&lexer);
|
||||
|
||||
for (let j = 0z; j < len(cases[i].1); j += 1) {
|
||||
const want = cases[i].1[j];
|
||||
const have = lex(&lexer)! as token;
|
||||
assert(tokeq(want, have));
|
||||
};
|
||||
|
||||
assert(lex(&lexer) is io::EOF);
|
||||
};
|
||||
};
|
||||
|
||||
fn tokeq(want: token, have: token) bool = {
|
||||
match (want) {
|
||||
case _null =>
|
||||
return have is _null;
|
||||
case comma =>
|
||||
return have is comma;
|
||||
case colon =>
|
||||
return have is colon;
|
||||
case arraystart =>
|
||||
return have is arraystart;
|
||||
case arrayend =>
|
||||
return have is arrayend;
|
||||
case objstart =>
|
||||
return have is objstart;
|
||||
case objend =>
|
||||
return have is objend;
|
||||
case let b: bool =>
|
||||
return have as bool == b;
|
||||
case let f: f64 =>
|
||||
return have as f64 == f;
|
||||
case let s: str =>
|
||||
return have as str == s;
|
||||
};
|
||||
};
|
164
vendor/hare-json/encoding/json/+test/test_load.ha
vendored
Normal file
164
vendor/hare-json/encoding/json/+test/test_load.ha
vendored
Normal file
|
@ -0,0 +1,164 @@
|
|||
use fmt;
|
||||
|
||||
fn roundtrip(input: str, expected: value) void = {
|
||||
const val = loadstr(input)!;
|
||||
defer finish(val);
|
||||
assert(equal(val, expected));
|
||||
const s = dumpstr(val);
|
||||
defer free(s);
|
||||
const val = loadstr(s)!;
|
||||
defer finish(val);
|
||||
assert(equal(val, expected));
|
||||
};
|
||||
|
||||
fn errassert(input: str, expected_loc: (uint, uint)) void = {
|
||||
const loc = loadstr(input) as invalid;
|
||||
if (loc.0 != expected_loc.0 || loc.1 != expected_loc.1) {
|
||||
fmt::errorfln("=== JSON:\n{}", input)!;
|
||||
fmt::errorfln("=== expected error location:\n({}, {})",
|
||||
expected_loc.0, expected_loc.1)!;
|
||||
fmt::errorfln("=== actual error location:\n({}, {})",
|
||||
loc.0, loc.1)!;
|
||||
abort();
|
||||
};
|
||||
};
|
||||
|
||||
@test fn load() void = {
|
||||
let obj = newobject();
|
||||
defer finish(obj);
|
||||
let obj2 = newobject();
|
||||
defer finish(obj2);
|
||||
|
||||
roundtrip(`1234`, 1234.0);
|
||||
roundtrip(`[]`, []);
|
||||
roundtrip(`[1, 2, 3, null]`, [1.0, 2.0, 3.0, _null]);
|
||||
roundtrip(`{}`, obj);
|
||||
set(&obj, "hello", "world");
|
||||
set(&obj, "answer", 42.0);
|
||||
roundtrip(`{ "hello": "world", "answer": 42 }`, obj);
|
||||
reset(&obj);
|
||||
roundtrip(`[[] ]`, [[]]);
|
||||
roundtrip(`[""]`, [""]);
|
||||
roundtrip(`["a"]`, ["a"]);
|
||||
roundtrip(`[false]`, [false]);
|
||||
roundtrip(`[null, 1, "1", {}]`, [_null, 1.0, "1", obj]);
|
||||
roundtrip(`[null]`, [_null]);
|
||||
roundtrip("[1\n]", [1.0]);
|
||||
roundtrip(`[1,null,null,null,2]`, [1.0, _null, _null, _null, 2.0]);
|
||||
set(&obj, "", 0.0);
|
||||
roundtrip(`{"":0}`, obj);
|
||||
reset(&obj);
|
||||
set(&obj, "foo\0bar", 42.0);
|
||||
roundtrip(`{"foo\u0000bar": 42}`, obj);
|
||||
reset(&obj);
|
||||
set(&obj, "min", -1.0e+28);
|
||||
set(&obj, "max", 1.0e+28);
|
||||
roundtrip(`{"min": -1.0e+28, "max": 1.0e+28}`, obj);
|
||||
reset(&obj);
|
||||
set(&obj, "id", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
|
||||
set(&obj2, "id", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
|
||||
set(&obj, "x", [obj2]);
|
||||
roundtrip(`{"x":[{"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}], "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}`, obj);
|
||||
reset(&obj);
|
||||
reset(&obj2);
|
||||
set(&obj, "a", []);
|
||||
roundtrip(`{"a":[]}`, obj);
|
||||
roundtrip("{\n" `"a": []` "\n}", obj);
|
||||
reset(&obj);
|
||||
roundtrip(`"\u0060\u012a\u12AB"`, "\u0060\u012a\u12AB");
|
||||
roundtrip(`"\"\\\/\b\f\n\r\t"`, "\"\\/\b\f\n\r\t");
|
||||
roundtrip(`"\\u0000"`, `\u0000`);
|
||||
roundtrip(`"\""`, `"`);
|
||||
roundtrip(`"a/*b*/c/*d//e"`, "a/*b*/c/*d//e");
|
||||
roundtrip(`"\\a"`, `\a`);
|
||||
roundtrip(`"\\n"`, `\n`);
|
||||
roundtrip(`"\u0012"`, "\u0012");
|
||||
roundtrip(`[ "asd"]`, ["asd"]);
|
||||
roundtrip(`"new\u000Aline"`, "new\nline");
|
||||
roundtrip(`"\u0000"`, "\0");
|
||||
roundtrip(`"\u002c"`, "\u002c");
|
||||
roundtrip(`"asd "`, "asd ");
|
||||
roundtrip(`" "`, " ");
|
||||
roundtrip(`"\u0821"`, "\u0821");
|
||||
roundtrip(`"\u0123"`, "\u0123");
|
||||
roundtrip(`"\u0061\u30af\u30EA\u30b9"`, "\u0061\u30af\u30EA\u30b9");
|
||||
roundtrip(`"\uA66D"`, "\uA66D");
|
||||
roundtrip(`"\u005C"`, `\`);
|
||||
roundtrip(`"\u0022"`, `"`);
|
||||
roundtrip(`""`, "");
|
||||
roundtrip(` [] `, []);
|
||||
|
||||
errassert(`[1,,]`, (1, 4));
|
||||
errassert(`[1 true]`, (1, 7));
|
||||
errassert(`["": 1]`, (1, 4));
|
||||
errassert(`[,1]`, (1, 2));
|
||||
errassert(`[1,,2]`, (1, 4));
|
||||
errassert(`["",]`, (1, 5));
|
||||
errassert(`["x"`, (1, 5));
|
||||
errassert(`[x`, (1, 2));
|
||||
errassert(`[3[4]]`, (1, 3));
|
||||
errassert(`[1:2]`, (1, 3));
|
||||
errassert(`[,]`, (1, 2));
|
||||
errassert(`[-]`, (1, 3));
|
||||
errassert(`[ , ""]`, (1, 5));
|
||||
errassert("[\"a\",\n4\n,1,", (3, 4));
|
||||
errassert(`[1,]`, (1, 4));
|
||||
errassert("[\"\va\"\\f", (1, 3));
|
||||
errassert(`[*]`, (1, 2));
|
||||
errassert(`[1,`, (1, 4));
|
||||
errassert("[1,\n1\n,1", (3, 3));
|
||||
errassert(`[{}`, (1, 4));
|
||||
errassert(`["x", truth]`, (1, 11));
|
||||
errassert(`{[: "x"}`, (1, 2));
|
||||
errassert(`{"x", null}`, (1, 5));
|
||||
errassert(`{"x"::"b"}`, (1, 6));
|
||||
errassert(`{"a":"a" 123}`, (1, 12));
|
||||
errassert(`{"a" b}`, (1, 6));
|
||||
errassert(`{:"b"}`, (1, 2));
|
||||
errassert(`{"a" "b"}`, (1, 8));
|
||||
errassert(`{"a":`, (1, 6));
|
||||
errassert(`{"a"`, (1, 5));
|
||||
errassert(`{1:1}`, (1, 2));
|
||||
errassert(`{9999E9999:1}`, (1, 10));
|
||||
errassert(`{null:null,null:null}`, (1, 5));
|
||||
errassert(`{"id":0,,,,,}`, (1, 9));
|
||||
errassert(`{'a':0}`, (1, 2));
|
||||
errassert(`{"id":0,}`, (1, 9));
|
||||
errassert(`{"a":"b",,"c":"d"}`, (1, 10));
|
||||
errassert(`{true: false}`, (1, 5));
|
||||
errassert(`{"a":"a`, (1, 8));
|
||||
errassert(`{ "foo" : "bar", "a" }`, (1, 22));
|
||||
errassert(` `, (1, 2));
|
||||
errassert(`<null>`, (1, 1));
|
||||
errassert(`["asd]`, (1, 7));
|
||||
errassert(`True`, (1, 4));
|
||||
errassert(`]`, (1, 1));
|
||||
errassert(`}`, (1, 1));
|
||||
errassert(`{"x": true,`, (1, 12));
|
||||
errassert(`[`, (1, 2));
|
||||
errassert(`{`, (1, 2));
|
||||
errassert(``, (1, 1));
|
||||
errassert("\0", (1, 1));
|
||||
errassert(`{"":`, (1, 5));
|
||||
errassert(`['`, (1, 2));
|
||||
errassert(`["`, (1, 3));
|
||||
errassert(`[,`, (1, 2));
|
||||
errassert(`[{`, (1, 3));
|
||||
errassert(`{[`, (1, 2));
|
||||
errassert(`{]`, (1, 2));
|
||||
errassert(`[}`, (1, 2));
|
||||
errassert(`{'`, (1, 2));
|
||||
errassert(`{"`, (1, 3));
|
||||
errassert(`{,`, (1, 2));
|
||||
errassert(`["\{["\{["\{["\{`, (1, 4));
|
||||
errassert(`*`, (1, 1));
|
||||
errassert(`\u000A""`, (1, 1));
|
||||
errassert("\f", (1, 1));
|
||||
};
|
||||
|
||||
@test fn nestlimit() void = {
|
||||
const s = `{ "foo": [[[{"bar": ["baz"]}]]] }`;
|
||||
const val = loadstr(s, 6: nestlimit)!;
|
||||
finish(val);
|
||||
assert(loadstr(s, 5: nestlimit) is limitreached);
|
||||
};
|
49
vendor/hare-json/encoding/json/+test/test_value.ha
vendored
Normal file
49
vendor/hare-json/encoding/json/+test/test_value.ha
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
// License: MPL-2.0
|
||||
// (c) 2022 Drew DeVault <sir@cmpwn.com>
|
||||
|
||||
@test fn object() void = {
|
||||
let obj = newobject();
|
||||
defer finish(obj);
|
||||
|
||||
set(&obj, "hello", "world");
|
||||
set(&obj, "foo", "bar");
|
||||
set(&obj, "the answer", 42.0);
|
||||
|
||||
// XXX: Match overhaul?
|
||||
assert(*(get(&obj, "hello") as *value) as str == "world");
|
||||
assert(*(get(&obj, "foo") as *value) as str == "bar");
|
||||
assert(*(get(&obj, "the answer") as *value) as f64 == 42.0);
|
||||
assert(get(&obj, "nonexistent") is void);
|
||||
|
||||
del(&obj, "hello");
|
||||
assert(get(&obj, "hello") is void);
|
||||
};
|
||||
|
||||
@test fn iterator() void = {
|
||||
let obj = newobject();
|
||||
defer finish(obj);
|
||||
|
||||
set(&obj, "hello", "world");
|
||||
set(&obj, "foo", "bar");
|
||||
set(&obj, "the answer", 42.0);
|
||||
|
||||
let it = iter(&obj);
|
||||
assert(next(&it) is (const str, const *value));
|
||||
assert(next(&it) is (const str, const *value));
|
||||
assert(next(&it) is (const str, const *value));
|
||||
assert(next(&it) is void);
|
||||
};
|
||||
|
||||
@test fn equal() void = {
|
||||
let a = newobject();
|
||||
defer finish(a);
|
||||
set(&a, "a", 42.0);
|
||||
set(&a, "A", "hello");
|
||||
|
||||
let b = newobject();
|
||||
defer finish(b);
|
||||
set(&b, "A", "hello");
|
||||
set(&b, "a", 42.0);
|
||||
|
||||
assert(equal(a, b));
|
||||
};
|
15
vendor/hare-json/encoding/json/README
vendored
Normal file
15
vendor/hare-json/encoding/json/README
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
This module provides an implementation of the JavaScript Object Notation (JSON)
|
||||
format, as defined by RFC 8259. Note that several other, incompatible
|
||||
specifications exist. This implementation does not include any extensions; only
|
||||
features which are strictly required by the spec are implemented.
|
||||
|
||||
A lexer for JSON values is provided, which may be initialized with [[lex]] and
|
||||
provides tokens via [[next]], and which uses a relatively small amount of memory
|
||||
and provides relatively few guarantees regarding the compliance of the input with
|
||||
the JSON grammar.
|
||||
|
||||
Additionally, the [[value]] type is provided to store any value JSON value, as
|
||||
well as helpers like [[newobject]], [[get]], and [[set]]. One can load a JSON
|
||||
value from an input stream into a heap-allocated [[value]] via [[load]], which
|
||||
enforces all of JSON's grammar constraints and returns an object which must be
|
||||
freed with [[finish]].
|
81
vendor/hare-json/encoding/json/dump.ha
vendored
Normal file
81
vendor/hare-json/encoding/json/dump.ha
vendored
Normal file
|
@ -0,0 +1,81 @@
|
|||
// License: MPL-2.0
|
||||
// (c) 2022 Sebastian <sebastian@sebsite.pw>
|
||||
use fmt;
|
||||
use io;
|
||||
use strings;
|
||||
use memio;
|
||||
|
||||
// Dumps a [[value]] into an [[io::handle]] as a string without any additional
|
||||
// formatting.
|
||||
export fn dump(out: io::handle, val: value) (size | io::error) = {
|
||||
let z = 0z;
|
||||
match (val) {
|
||||
case let v: (f64 | bool) =>
|
||||
z += fmt::fprint(out, v)?;
|
||||
case let s: str =>
|
||||
z += fmt::fprint(out, `"`)?;
|
||||
let it = strings::iter(s);
|
||||
for (const r => strings::next(&it)) {
|
||||
switch (r) {
|
||||
case '\b' =>
|
||||
z += fmt::fprint(out, `\b`)?;
|
||||
case '\f' =>
|
||||
z += fmt::fprint(out, `\f`)?;
|
||||
case '\n' =>
|
||||
z += fmt::fprint(out, `\n`)?;
|
||||
case '\r' =>
|
||||
z += fmt::fprint(out, `\r`)?;
|
||||
case '\t' =>
|
||||
z += fmt::fprint(out, `\t`)?;
|
||||
case '\"' =>
|
||||
z += fmt::fprint(out, `\"`)?;
|
||||
case '\\' =>
|
||||
z += fmt::fprint(out, `\\`)?;
|
||||
case =>
|
||||
if (iscntrl(r)) {
|
||||
z += fmt::fprintf(out, `\u{:.4x}`,
|
||||
r: u32)?;
|
||||
} else {
|
||||
z += fmt::fprint(out, r)?;
|
||||
};
|
||||
};
|
||||
};
|
||||
z += fmt::fprint(out, `"`)?;
|
||||
case _null =>
|
||||
z += fmt::fprint(out, "null")?;
|
||||
case let a: []value =>
|
||||
z += fmt::fprint(out, "[")?;
|
||||
for (let i = 0z; i < len(a); i += 1) {
|
||||
z += dump(out, a[i])?;
|
||||
if (i < len(a) - 1) {
|
||||
z += fmt::fprint(out, ",")?;
|
||||
};
|
||||
};
|
||||
z += fmt::fprint(out, "]")?;
|
||||
case let o: object =>
|
||||
z += fmt::fprint(out, "{")?;
|
||||
let comma = false;
|
||||
let it = iter(&o);
|
||||
for (true) match (next(&it)) {
|
||||
case void => break;
|
||||
case let pair: (const str, const *value) =>
|
||||
if (comma) {
|
||||
z += fmt::fprint(out, ",")?;
|
||||
};
|
||||
comma = true;
|
||||
z += dump(out, pair.0)?;
|
||||
z += fmt::fprint(out, ":")?;
|
||||
z += dump(out, *pair.1)?;
|
||||
};
|
||||
z += fmt::fprint(out, "}")?;
|
||||
};
|
||||
return z;
|
||||
};
|
||||
|
||||
// Dumps a [[value]] into a string without any additional formatting. The caller
|
||||
// must free the return value.
|
||||
export fn dumpstr(val: value) str = {
|
||||
let s = memio::dynamic();
|
||||
dump(&s, val)!;
|
||||
return memio::string(&s)!;
|
||||
};
|
377
vendor/hare-json/encoding/json/lex.ha
vendored
Normal file
377
vendor/hare-json/encoding/json/lex.ha
vendored
Normal file
|
@ -0,0 +1,377 @@
|
|||
// License: MPL-2.0
|
||||
// (c) 2022 Drew DeVault <sir@cmpwn.com>
|
||||
use ascii;
|
||||
use bufio;
|
||||
use encoding::utf8;
|
||||
use io;
|
||||
use os;
|
||||
use strconv;
|
||||
use strings;
|
||||
use memio;
|
||||
|
||||
export type lexer = struct {
|
||||
src: io::handle,
|
||||
strbuf: memio::stream,
|
||||
un: (token | void),
|
||||
rb: (rune | void),
|
||||
loc: (uint, uint),
|
||||
prevloc: (uint, uint),
|
||||
nextloc: (uint, uint),
|
||||
prevrloc: (uint, uint),
|
||||
};
|
||||
|
||||
// Creates a new JSON lexer. The caller may obtain tokens with [[lex]] and
|
||||
// should pass the result to [[close]] when they're done with it.
|
||||
export fn newlexer(src: io::handle) lexer = lexer {
|
||||
src = src,
|
||||
strbuf = memio::dynamic(),
|
||||
un = void,
|
||||
rb = void,
|
||||
loc = (1, 0),
|
||||
...
|
||||
};
|
||||
|
||||
// Frees state associated with a JSON lexer.
|
||||
export fn close(lex: *lexer) void = {
|
||||
io::close(&lex.strbuf)!;
|
||||
};
|
||||
|
||||
// Returns the next token from a JSON lexer. The return value is borrowed from
|
||||
// the lexer and will be overwritten on subsequent calls.
|
||||
export fn lex(lex: *lexer) (token | io::EOF | error) = {
|
||||
match (lex.un) {
|
||||
case void =>
|
||||
lex.prevloc = lex.loc;
|
||||
case let tok: token =>
|
||||
lex.un = void;
|
||||
lex.prevloc = lex.loc;
|
||||
lex.loc = lex.nextloc;
|
||||
return tok;
|
||||
};
|
||||
|
||||
const rn = match (nextrunews(lex)?) {
|
||||
case io::EOF =>
|
||||
return io::EOF;
|
||||
case let rn: rune =>
|
||||
yield rn;
|
||||
};
|
||||
|
||||
switch (rn) {
|
||||
case '[' =>
|
||||
return arraystart;
|
||||
case ']' =>
|
||||
return arrayend;
|
||||
case '{' =>
|
||||
return objstart;
|
||||
case '}' =>
|
||||
return objend;
|
||||
case ',' =>
|
||||
return comma;
|
||||
case ':' =>
|
||||
return colon;
|
||||
case '"' =>
|
||||
return scan_str(lex)?;
|
||||
case =>
|
||||
yield;
|
||||
};
|
||||
|
||||
if (ascii::isdigit(rn) || rn == '-') {
|
||||
unget(lex, rn);
|
||||
return scan_number(lex)?;
|
||||
};
|
||||
|
||||
if (!ascii::isalpha(rn)) {
|
||||
return lex.loc: invalid;
|
||||
};
|
||||
|
||||
unget(lex, rn);
|
||||
const word = scan_word(lex)?;
|
||||
switch (word) {
|
||||
case "true" =>
|
||||
return true;
|
||||
case "false" =>
|
||||
return false;
|
||||
case "null" =>
|
||||
return _null;
|
||||
case =>
|
||||
return lex.loc: invalid;
|
||||
};
|
||||
};
|
||||
|
||||
// "Unlexes" a token from the lexer, such that the next call to [[lex]] will
|
||||
// return that token again. Only one token can be unlexed at a time, otherwise
|
||||
// the program will abort.
|
||||
export fn unlex(lex: *lexer, tok: token) void = {
|
||||
assert(lex.un is void, "encoding::json::unlex called twice in a row");
|
||||
lex.un = tok;
|
||||
lex.nextloc = lex.loc;
|
||||
lex.loc = lex.prevloc;
|
||||
};
|
||||
|
||||
// Scans until encountering a non-alphabetical character, returning the
|
||||
// resulting word.
|
||||
fn scan_word(lex: *lexer) (str | error) = {
|
||||
memio::reset(&lex.strbuf);
|
||||
|
||||
for (true) {
|
||||
const rn = match (nextrune(lex)?) {
|
||||
case let rn: rune =>
|
||||
yield rn;
|
||||
case io::EOF =>
|
||||
break;
|
||||
};
|
||||
if (!ascii::isalpha(rn)) {
|
||||
unget(lex, rn);
|
||||
break;
|
||||
};
|
||||
memio::appendrune(&lex.strbuf, rn)!;
|
||||
};
|
||||
|
||||
return memio::string(&lex.strbuf)!;
|
||||
};
|
||||
|
||||
type numstate = enum {
|
||||
SIGN,
|
||||
START,
|
||||
ZERO,
|
||||
INTEGER,
|
||||
FRACSTART,
|
||||
FRACTION,
|
||||
EXPSIGN,
|
||||
EXPSTART,
|
||||
EXPONENT,
|
||||
};
|
||||
|
||||
fn scan_number(lex: *lexer) (token | error) = {
|
||||
memio::reset(&lex.strbuf);
|
||||
|
||||
let state = numstate::SIGN;
|
||||
for (true) {
|
||||
const rn = match (nextrune(lex)?) {
|
||||
case let rn: rune =>
|
||||
yield rn;
|
||||
case io::EOF =>
|
||||
break;
|
||||
};
|
||||
|
||||
switch (state) {
|
||||
case numstate::SIGN =>
|
||||
state = numstate::START;
|
||||
if (rn != '-') {
|
||||
unget(lex, rn);
|
||||
continue;
|
||||
};
|
||||
case numstate::START =>
|
||||
switch (rn) {
|
||||
case '0' =>
|
||||
state = numstate::ZERO;
|
||||
case =>
|
||||
if (!ascii::isdigit(rn)) {
|
||||
return lex.loc: invalid;
|
||||
};
|
||||
state = numstate::INTEGER;
|
||||
};
|
||||
case numstate::ZERO =>
|
||||
switch (rn) {
|
||||
case '.' =>
|
||||
state = numstate::FRACSTART;
|
||||
case 'e', 'E' =>
|
||||
state = numstate::EXPSIGN;
|
||||
case =>
|
||||
if (ascii::isdigit(rn)) {
|
||||
return lex.loc: invalid;
|
||||
};
|
||||
unget(lex, rn);
|
||||
break;
|
||||
};
|
||||
case numstate::INTEGER =>
|
||||
switch (rn) {
|
||||
case '.' =>
|
||||
state = numstate::FRACSTART;
|
||||
case 'e', 'E' =>
|
||||
state = numstate::EXPSIGN;
|
||||
case =>
|
||||
if (!ascii::isdigit(rn)) {
|
||||
unget(lex, rn);
|
||||
break;
|
||||
};
|
||||
};
|
||||
case numstate::FRACSTART =>
|
||||
if (!ascii::isdigit(rn)) {
|
||||
return lex.loc: invalid;
|
||||
};
|
||||
state = numstate::FRACTION;
|
||||
case numstate::FRACTION =>
|
||||
switch (rn) {
|
||||
case 'e', 'E' =>
|
||||
state = numstate::EXPSIGN;
|
||||
case =>
|
||||
if (!ascii::isdigit(rn)) {
|
||||
unget(lex, rn);
|
||||
break;
|
||||
};
|
||||
};
|
||||
case numstate::EXPSIGN =>
|
||||
state = numstate::EXPSTART;
|
||||
if (rn != '+' && rn != '-') {
|
||||
unget(lex, rn);
|
||||
continue;
|
||||
};
|
||||
case numstate::EXPSTART =>
|
||||
if (!ascii::isdigit(rn)) {
|
||||
return lex.loc: invalid;
|
||||
};
|
||||
state = numstate::EXPONENT;
|
||||
case numstate::EXPONENT =>
|
||||
if (!ascii::isdigit(rn)) {
|
||||
unget(lex, rn);
|
||||
break;
|
||||
};
|
||||
};
|
||||
|
||||
memio::appendrune(&lex.strbuf, rn)!;
|
||||
};
|
||||
|
||||
match (strconv::stof64(memio::string(&lex.strbuf)!)) {
|
||||
case let f: f64 =>
|
||||
return f;
|
||||
case =>
|
||||
return lex.loc: invalid;
|
||||
};
|
||||
};
|
||||
|
||||
fn scan_str(lex: *lexer) (token | error) = {
|
||||
memio::reset(&lex.strbuf);
|
||||
|
||||
for (true) {
|
||||
const rn = match (nextrune(lex)?) {
|
||||
case let rn: rune =>
|
||||
yield rn;
|
||||
case io::EOF =>
|
||||
lex.loc.1 += 1;
|
||||
return lex.loc: invalid;
|
||||
};
|
||||
|
||||
switch (rn) {
|
||||
case '"' =>
|
||||
break;
|
||||
case '\\' =>
|
||||
const rn = scan_escape(lex)?;
|
||||
memio::appendrune(&lex.strbuf, rn)!;
|
||||
case =>
|
||||
if (iscntrl(rn)) {
|
||||
return lex.loc: invalid;
|
||||
};
|
||||
memio::appendrune(&lex.strbuf, rn)!;
|
||||
};
|
||||
};
|
||||
|
||||
return memio::string(&lex.strbuf)!;
|
||||
};
|
||||
|
||||
fn scan_escape(lex: *lexer) (rune | error) = {
|
||||
const rn = match (nextrune(lex)?) {
|
||||
case let rn: rune =>
|
||||
yield rn;
|
||||
case io::EOF =>
|
||||
return lex.loc: invalid;
|
||||
};
|
||||
|
||||
switch (rn) {
|
||||
case '\"' =>
|
||||
return '\"';
|
||||
case '\\' =>
|
||||
return '\\';
|
||||
case '/' =>
|
||||
return '/';
|
||||
case 'b' =>
|
||||
return '\b';
|
||||
case 'f' =>
|
||||
return '\f';
|
||||
case 'n' =>
|
||||
return '\n';
|
||||
case 'r' =>
|
||||
return '\r';
|
||||
case 't' =>
|
||||
return '\t';
|
||||
case 'u' =>
|
||||
let buf: [4]u8 = [0...];
|
||||
match (io::readall(lex.src, buf)?) {
|
||||
case io::EOF =>
|
||||
return lex.loc: invalid;
|
||||
case size =>
|
||||
yield;
|
||||
};
|
||||
const s = match (strings::fromutf8(buf)) {
|
||||
case let s: str =>
|
||||
yield s;
|
||||
case =>
|
||||
return lex.loc: invalid;
|
||||
};
|
||||
match (strconv::stou32(s, strconv::base::HEX)) {
|
||||
case let u: u32 =>
|
||||
lex.loc.1 += 4;
|
||||
return u: rune;
|
||||
case =>
|
||||
return lex.loc: invalid;
|
||||
};
|
||||
case =>
|
||||
return lex.loc: invalid;
|
||||
};
|
||||
};
|
||||
|
||||
// Gets the next rune from the lexer.
|
||||
fn nextrune(lex: *lexer) (rune | io::EOF | error) = {
|
||||
if (lex.rb is rune) {
|
||||
lex.prevrloc = lex.loc;
|
||||
const r = lex.rb as rune;
|
||||
lex.rb = void;
|
||||
if (r == '\n') {
|
||||
lex.loc = (lex.loc.0 + 1, 0);
|
||||
} else {
|
||||
lex.loc.1 += 1;
|
||||
};
|
||||
return r;
|
||||
};
|
||||
match (bufio::read_rune(lex.src)) {
|
||||
case let err: io::error =>
|
||||
return err;
|
||||
case utf8::invalid =>
|
||||
return lex.loc: invalid;
|
||||
case io::EOF =>
|
||||
return io::EOF;
|
||||
case let rn: rune =>
|
||||
lex.prevrloc = lex.loc;
|
||||
if (rn == '\n') {
|
||||
lex.loc = (lex.loc.0 + 1, 0);
|
||||
} else {
|
||||
lex.loc.1 += 1;
|
||||
};
|
||||
return rn;
|
||||
};
|
||||
};
|
||||
|
||||
// Like nextrune but skips whitespace.
|
||||
fn nextrunews(lex: *lexer) (rune | io::EOF | error) = {
|
||||
for (true) {
|
||||
match (nextrune(lex)?) {
|
||||
case let rn: rune =>
|
||||
if (isspace(rn)) {
|
||||
continue;
|
||||
};
|
||||
return rn;
|
||||
case io::EOF =>
|
||||
return io::EOF;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
fn unget(lex: *lexer, r: rune) void = {
|
||||
assert(lex.rb is void);
|
||||
lex.rb = r;
|
||||
lex.loc = lex.prevrloc;
|
||||
};
|
||||
|
||||
fn iscntrl(r: rune) bool = r: u32 < 0x20;
|
||||
|
||||
fn isspace(r: rune) bool = ascii::isspace(r) && r != '\f';
|
148
vendor/hare-json/encoding/json/load.ha
vendored
Normal file
148
vendor/hare-json/encoding/json/load.ha
vendored
Normal file
|
@ -0,0 +1,148 @@
|
|||
use memio;
|
||||
use io;
|
||||
use strings;
|
||||
use types;
|
||||
|
||||
// Options for [[load]].
|
||||
export type load_option = nestlimit;
|
||||
|
||||
// The maximum number of nested objects or arrays that can be entered before
|
||||
// erroring out.
|
||||
export type nestlimit = uint;
|
||||
|
||||
// Parses a JSON value from the given [[io::handle]], returning the value or an
|
||||
// error. The return value is allocated on the heap; use [[finish]] to free it
|
||||
// up when you're done using it.
|
||||
//
|
||||
// By default, this function assumes non-antagonistic inputs, and does not limit
|
||||
// recursion depth or memory usage. You may want to set a custom [[nestlimit]],
|
||||
// or incorporate an [[io::limitreader]] or similar. Alternatively, you can use
|
||||
// the JSON lexer ([[lex]]) directly if dealing with potentially malicious
|
||||
// inputs.
|
||||
export fn load(src: io::handle, opts: load_option...) (value | error) = {
|
||||
let limit = types::UINT_MAX;
|
||||
for (let i = 0z; i < len(opts); i += 1) {
|
||||
limit = opts[i]: nestlimit: uint;
|
||||
};
|
||||
const lex = newlexer(src);
|
||||
defer close(&lex);
|
||||
return _load(&lex, 0, limit);
|
||||
};
|
||||
|
||||
// Parses a JSON value from the given string, returning the value or an error.
|
||||
// The return value is allocated on the heap; use [[finish]] to free it up when
|
||||
// you're done using it.
|
||||
//
|
||||
// See the documentation for [[load]] for information on dealing with
|
||||
// potentially malicious inputs.
|
||||
export fn loadstr(input: str, opts: load_option...) (value | error) = {
|
||||
let src = memio::fixed(strings::toutf8(input));
|
||||
return load(&src, opts...);
|
||||
};
|
||||
|
||||
fn _load(lexer: *lexer, level: uint, limit: uint) (value | error) = {
|
||||
const tok = mustscan(lexer)?;
|
||||
match (tok) {
|
||||
case _null =>
|
||||
return _null;
|
||||
case let b: bool =>
|
||||
return b;
|
||||
case let f: f64 =>
|
||||
return f;
|
||||
case let s: str =>
|
||||
return strings::dup(s);
|
||||
case arraystart =>
|
||||
if (level == limit) {
|
||||
return limitreached;
|
||||
};
|
||||
return _load_array(lexer, level + 1, limit);
|
||||
case objstart =>
|
||||
if (level == limit) {
|
||||
return limitreached;
|
||||
};
|
||||
return _load_obj(lexer, level + 1, limit);
|
||||
case (arrayend | objend | colon | comma) =>
|
||||
return lexer.loc: invalid;
|
||||
};
|
||||
};
|
||||
|
||||
fn _load_array(lexer: *lexer, level: uint, limit: uint) (value | error) = {
|
||||
let success = false;
|
||||
let array: []value = [];
|
||||
defer if (!success) finish(array);
|
||||
let tok = mustscan(lexer)?;
|
||||
match (tok) {
|
||||
case arrayend =>
|
||||
success = true;
|
||||
return array;
|
||||
case =>
|
||||
unlex(lexer, tok);
|
||||
};
|
||||
|
||||
for (true) {
|
||||
append(array, _load(lexer, level, limit)?);
|
||||
|
||||
tok = mustscan(lexer)?;
|
||||
match (tok) {
|
||||
case comma => void;
|
||||
case arrayend => break;
|
||||
case =>
|
||||
return lexer.loc: invalid;
|
||||
};
|
||||
};
|
||||
success = true;
|
||||
return array;
|
||||
};
|
||||
|
||||
fn _load_obj(lexer: *lexer, level: uint, limit: uint) (value | error) = {
|
||||
let success = false;
|
||||
let obj = newobject();
|
||||
defer if (!success) finish(obj);
|
||||
let tok = mustscan(lexer)?;
|
||||
match (tok) {
|
||||
case objend =>
|
||||
success = true;
|
||||
return obj;
|
||||
case =>
|
||||
unlex(lexer, tok);
|
||||
};
|
||||
|
||||
for (true) {
|
||||
let tok = mustscan(lexer)?;
|
||||
const key = match (tok) {
|
||||
case let s: str =>
|
||||
yield strings::dup(s);
|
||||
case =>
|
||||
return lexer.loc: invalid;
|
||||
};
|
||||
defer free(key);
|
||||
|
||||
tok = mustscan(lexer)?;
|
||||
if (!(tok is colon)) {
|
||||
return lexer.loc: invalid;
|
||||
};
|
||||
|
||||
put(&obj, key, _load(lexer, level, limit)?);
|
||||
|
||||
tok = mustscan(lexer)?;
|
||||
match (tok) {
|
||||
case comma => void;
|
||||
case objend => break;
|
||||
case =>
|
||||
return lexer.loc: invalid;
|
||||
};
|
||||
};
|
||||
|
||||
success = true;
|
||||
return obj;
|
||||
};
|
||||
|
||||
fn mustscan(lexer: *lexer) (token | error) = {
|
||||
match (lex(lexer)?) {
|
||||
case io::EOF =>
|
||||
lexer.loc.1 += 1;
|
||||
return lexer.loc: invalid;
|
||||
case let tok: token =>
|
||||
return tok;
|
||||
};
|
||||
};
|
26
vendor/hare-json/encoding/json/path/path.ha
vendored
Normal file
26
vendor/hare-json/encoding/json/path/path.ha
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
// A compiled JSONPath query.
|
||||
export type query = []segment;
|
||||
|
||||
export type segment_type = enum {
|
||||
CHILD,
|
||||
DESCENDANT,
|
||||
};
|
||||
|
||||
export type segment = struct {
|
||||
stype: segment_type,
|
||||
selector: selector,
|
||||
};
|
||||
|
||||
export type selector = (str | wild | index | slice | filter);
|
||||
|
||||
export type wild = void;
|
||||
|
||||
export type index = int;
|
||||
|
||||
export type slice = struct {
|
||||
start: (int | void),
|
||||
end: (int | void),
|
||||
step: (int | void),
|
||||
};
|
||||
|
||||
export type filter = void; // TODO
|
50
vendor/hare-json/encoding/json/types.ha
vendored
Normal file
50
vendor/hare-json/encoding/json/types.ha
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
// License: MPL-2.0
|
||||
// (c) 2022 Drew DeVault <sir@cmpwn.com>
|
||||
use fmt;
|
||||
use io;
|
||||
|
||||
// An invalid JSON token was encountered at this location (line, column).
|
||||
export type invalid = !(uint, uint);
|
||||
|
||||
// The maximum nesting limit was reached.
|
||||
export type limitreached = !void;
|
||||
|
||||
// A tagged union of all possible errors returned from this module.
|
||||
export type error = !(invalid | limitreached | io::error);
|
||||
|
||||
// The JSON null value.
|
||||
export type _null = void;
|
||||
|
||||
// The '[' token, signaling the start of a JSON array.
|
||||
export type arraystart = void;
|
||||
|
||||
// The ']' token, signaling the end of a JSON array.
|
||||
export type arrayend = void;
|
||||
|
||||
// The '{' token, signaling the start of a JSON object.
|
||||
export type objstart = void;
|
||||
|
||||
// The '}' token, signaling the end of a JSON object.
|
||||
export type objend = void;
|
||||
|
||||
// The ':' token.
|
||||
export type colon = void;
|
||||
|
||||
// The ',' token.
|
||||
export type comma = void;
|
||||
|
||||
// All tokens which can be returned from the JSON tokenizer.
|
||||
export type token = (arraystart | arrayend | objstart |
|
||||
objend | colon | comma | str | f64 | bool | _null);
|
||||
|
||||
// Converts an [[error]] into a human-friendly string.
|
||||
export fn strerror(err: error) const str = {
|
||||
static let buf: [53]u8 = [0...];
|
||||
match (err) {
|
||||
case let err: invalid =>
|
||||
return fmt::bsprintf(buf,
|
||||
"{}:{}: Invalid JSON token encountered", err.0, err.1);
|
||||
case let err: io::error =>
|
||||
return io::strerror(err);
|
||||
};
|
||||
};
|
219
vendor/hare-json/encoding/json/value.ha
vendored
Normal file
219
vendor/hare-json/encoding/json/value.ha
vendored
Normal file
|
@ -0,0 +1,219 @@
|
|||
// License: MPL-2.0
|
||||
// (c) 2022 Drew DeVault <sir@cmpwn.com>
|
||||
use hash::fnv;
|
||||
use strings;
|
||||
|
||||
// TODO: Resize table as appropriate
|
||||
export def OBJECT_BUCKETS: size = 32;
|
||||
|
||||
export type object = struct {
|
||||
buckets: [OBJECT_BUCKETS][](str, value),
|
||||
count: size,
|
||||
};
|
||||
|
||||
// A JSON value.
|
||||
export type value = (f64 | str | bool | _null | []value | object);
|
||||
|
||||
// Initializes a new (empty) JSON object. Call [[finish]] to free associated
|
||||
// resources when you're done using it.
|
||||
export fn newobject() object = {
|
||||
return object { ... };
|
||||
};
|
||||
|
||||
// Gets a value from a JSON object. The return value is borrowed from the
|
||||
// object.
|
||||
export fn get(obj: *object, key: str) (*value | void) = {
|
||||
const hash = fnv::string(key);
|
||||
const bucket = &obj.buckets[hash % len(obj.buckets)];
|
||||
for (let i = 0z; i < len(bucket); i += 1) {
|
||||
if (bucket[i].0 == key) {
|
||||
return &bucket[i].1;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// Sets a value in a JSON object. The key and value will be duplicated.
|
||||
export fn set(obj: *object, key: const str, val: const value) void = {
|
||||
put(obj, key, dup(val));
|
||||
};
|
||||
|
||||
// Sets a value in a JSON object. The key will be duplicated. The object will
|
||||
// assume ownership over the value, without duplicating it.
|
||||
export fn put(obj: *object, key: const str, val: const value) void = {
|
||||
const hash = fnv::string(key);
|
||||
const bucket = &obj.buckets[hash % len(obj.buckets)];
|
||||
for (let i = 0z; i < len(bucket); i += 1) {
|
||||
if (bucket[i].0 == key) {
|
||||
finish(bucket[i].1);
|
||||
bucket[i].1 = val;
|
||||
return;
|
||||
};
|
||||
};
|
||||
obj.count += 1;
|
||||
append(bucket, (strings::dup(key), val));
|
||||
};
|
||||
|
||||
// Deletes values from a JSON object, if they are present.
|
||||
export fn del(obj: *object, keys: const str...) void = {
|
||||
for (let i = 0z; i < len(keys); i += 1) {
|
||||
match (take(obj, keys[i])) {
|
||||
case let val: value =>
|
||||
finish(val);
|
||||
case void => void;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// Deletes a key from a JSON object, returning its previous value, if any.
|
||||
// The caller is responsible for freeing the value.
|
||||
export fn take(obj: *object, key: const str) (value | void) = {
|
||||
const hash = fnv::string(key);
|
||||
const bucket = &obj.buckets[hash % len(obj.buckets)];
|
||||
for (let i = 0z; i < len(bucket); i += 1) {
|
||||
if (bucket[i].0 == key) {
|
||||
obj.count -= 1;
|
||||
free(bucket[i].0);
|
||||
const val = bucket[i].1;
|
||||
delete(bucket[i]);
|
||||
return val;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// Clears all values from a JSON object, leaving it empty.
|
||||
export fn reset(obj: *object) void = {
|
||||
let it = iter(obj);
|
||||
for (true) match (next(&it)) {
|
||||
case void =>
|
||||
break;
|
||||
case let v: (const str, const *value) =>
|
||||
del(obj, v.0);
|
||||
};
|
||||
};
|
||||
|
||||
// Returns the number of key/value pairs in a JSON object.
|
||||
export fn count(obj: *object) size = {
|
||||
return obj.count;
|
||||
};
|
||||
|
||||
export type iterator = struct {
|
||||
obj: *object,
|
||||
i: size,
|
||||
j: size,
|
||||
};
|
||||
|
||||
// Creates an iterator that enumerates over the key/value pairs in an
|
||||
// [[object]].
|
||||
export fn iter(obj: *object) iterator = {
|
||||
return iterator { obj = obj, ... };
|
||||
};
|
||||
|
||||
// Returns the next key/value pair from this iterator, or void if none remain.
|
||||
export fn next(iter: *iterator) ((const str, const *value) | void) = {
|
||||
for (iter.i < len(iter.obj.buckets); iter.i += 1) {
|
||||
const bucket = &iter.obj.buckets[iter.i];
|
||||
for (iter.j < len(bucket)) {
|
||||
const key = bucket[iter.j].0;
|
||||
const val = &bucket[iter.j].1;
|
||||
iter.j += 1;
|
||||
return (key, val);
|
||||
};
|
||||
iter.j = 0;
|
||||
};
|
||||
};
|
||||
|
||||
// Duplicates a JSON value. The caller must pass the return value to [[finish]]
|
||||
// to free associated resources when they're done using it.
|
||||
export fn dup(val: value) value = {
|
||||
match (val) {
|
||||
case let s: str =>
|
||||
return strings::dup(s);
|
||||
case let v: []value =>
|
||||
let new: []value = alloc([], len(v));
|
||||
for (let i = 0z; i < len(v); i += 1) {
|
||||
append(new, dup(v[i]));
|
||||
};
|
||||
return new;
|
||||
case let o: object =>
|
||||
let new = newobject();
|
||||
const i = iter(&o);
|
||||
for (true) {
|
||||
const pair = match (next(&i)) {
|
||||
case void =>
|
||||
break;
|
||||
case let pair: (const str, const *value) =>
|
||||
yield pair;
|
||||
};
|
||||
set(&new, pair.0, *pair.1);
|
||||
};
|
||||
return new;
|
||||
case =>
|
||||
return val;
|
||||
};
|
||||
};
|
||||
|
||||
// Checks two JSON values for equality.
|
||||
export fn equal(a: value, b: value) bool = {
|
||||
match (a) {
|
||||
case _null =>
|
||||
return b is _null;
|
||||
case let a: bool =>
|
||||
return b is bool && a == b as bool;
|
||||
case let a: f64 =>
|
||||
return b is f64 && a == b as f64;
|
||||
case let a: str =>
|
||||
return b is str && a == b as str;
|
||||
case let a: []value =>
|
||||
if (!(b is []value)) return false;
|
||||
const b = b as []value;
|
||||
if (len(a) != len(b)) return false;
|
||||
for (let i = 0z; i < len(a); i += 1) {
|
||||
if (!equal(a[i], b[i])) {
|
||||
return false;
|
||||
};
|
||||
};
|
||||
return true;
|
||||
case let a: object =>
|
||||
if (!(b is object)) return false;
|
||||
let b = b as object;
|
||||
if (count(&a) != count(&b)) {
|
||||
return false;
|
||||
};
|
||||
let a = iter(&a);
|
||||
for (true) match (next(&a)) {
|
||||
case let a: (const str, const *value) =>
|
||||
match (get(&b, a.0)) {
|
||||
case let b: *value =>
|
||||
if (!equal(*a.1, *b)) {
|
||||
return false;
|
||||
};
|
||||
case void => return false;
|
||||
};
|
||||
case void => break;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
||||
// Frees state associated with a JSON value.
|
||||
export fn finish(val: value) void = {
|
||||
match (val) {
|
||||
case let s: str =>
|
||||
free(s);
|
||||
case let v: []value =>
|
||||
for (let i = 0z; i < len(v); i += 1) {
|
||||
finish(v[i]);
|
||||
};
|
||||
free(v);
|
||||
case let o: object =>
|
||||
for (let i = 0z; i < len(o.buckets); i += 1) {
|
||||
const bucket = &o.buckets[i];
|
||||
for (let j = 0z; j < len(bucket); j += 1) {
|
||||
free(bucket[j].0);
|
||||
finish(bucket[j].1);
|
||||
};
|
||||
free(*bucket);
|
||||
};
|
||||
case => void;
|
||||
};
|
||||
};
|
21
vendor/hare-logfmt/LICENSE
vendored
Normal file
21
vendor/hare-logfmt/LICENSE
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Blain Smith
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
28
vendor/hare-logfmt/Makefile
vendored
Normal file
28
vendor/hare-logfmt/Makefile
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
.POSIX:
|
||||
.SUFFIXES:
|
||||
HARE=hare
|
||||
HAREFLAGS=
|
||||
|
||||
DESTDIR=
|
||||
PREFIX=/usr/local
|
||||
SRCDIR=$(PREFIX)/src
|
||||
HARESRCDIR=$(SRCDIR)/hare
|
||||
THIRDPARTYDIR=$(HARESRCDIR)/third-party
|
||||
|
||||
all:
|
||||
# no-op
|
||||
|
||||
clean:
|
||||
# no-op
|
||||
|
||||
check:
|
||||
$(HARE) test
|
||||
|
||||
install:
|
||||
mkdir -p $(DESTDIR)$(THIRDPARTYDIR)/log/logfmt
|
||||
install -m644 * $(DESTDIR)$(THIRDPARTYDIR)
|
||||
|
||||
uninstall:
|
||||
rm -rf $(DESTDIR)$(THIRDPARTYDIR)/log/logfmt
|
||||
|
||||
.PHONY: all clean check install uninstall
|
45
vendor/hare-logfmt/README.md
vendored
Normal file
45
vendor/hare-logfmt/README.md
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
# hare-logfmt
|
||||
|
||||
A logfmt logger that can be used in [`log::setlogger(*logger) void`](https://docs.harelang.org/log#setlogger) in Hare.
|
||||
|
||||
## Usage
|
||||
|
||||
```hare
|
||||
use logfmt;
|
||||
use log;
|
||||
|
||||
export fn main() void = {
|
||||
// create an instance of the logger
|
||||
let l = logfmt::new(os::stderr);
|
||||
|
||||
// set the global logger to the logfmt logger
|
||||
log::setlogger(&l);
|
||||
|
||||
// use the normal log::println function
|
||||
log::println("request_uri", "/", "method", "POST", "user_id", 123);
|
||||
log::println("request_uri", "/sign-in", "method", "GET");
|
||||
log::println("request_uri", "/dashboard", "method", "GET", "user_id", 123);
|
||||
};
|
||||
```
|
||||
|
||||
**Output**
|
||||
|
||||
```console
|
||||
ts=2022-05-12T09:36:27-0400 request_uri=/ method=POST user_id=123
|
||||
ts=2022-05-12T09:42:27-0400 request_uri=/sign-in method=GET
|
||||
ts=2022-05-12T09:52:10-0400 request_uri=/dashboard method=GET user_id=123
|
||||
```
|
||||
|
||||
You can also run `haredoc` to read the module documentation.
|
||||
|
||||
```console
|
||||
> haredoc
|
||||
// Implements the log::logger for outputting logs in Logfmt format.
|
||||
type logfmtlogger = struct {
|
||||
log::logger,
|
||||
handle: io::handle,
|
||||
};
|
||||
|
||||
// creates a new instace of logfmtlogger to be use with [[log::setlogger]].
|
||||
fn new(handle: io::handle) logfmtlogger;
|
||||
```
|
48
vendor/hare-logfmt/log/logfmt/+test.ha
vendored
Normal file
48
vendor/hare-logfmt/log/logfmt/+test.ha
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
use log;
|
||||
use io;
|
||||
use os;
|
||||
use strings;
|
||||
use fmt;
|
||||
|
||||
@test fn logfmt() void = {
|
||||
let s = teststream_open();
|
||||
|
||||
let l = new(&s);
|
||||
|
||||
log::setlogger(&l);
|
||||
log::println("request_uri", "/", "method", "POST", "user_id", 123);
|
||||
|
||||
let sbuf = strings::fromutf8(s.buf)!;
|
||||
|
||||
assert(strings::contains(sbuf, "request_uri=/ method=POST user_id=123"));
|
||||
|
||||
free(sbuf);
|
||||
};
|
||||
|
||||
const teststream_vtable: io::vtable = io::vtable {
|
||||
reader = &teststream_read,
|
||||
writer = &teststream_write,
|
||||
...
|
||||
};
|
||||
|
||||
type teststream = struct {
|
||||
stream: io::stream,
|
||||
buf: []u8,
|
||||
};
|
||||
|
||||
fn teststream_open() teststream = teststream {
|
||||
stream = &teststream_vtable,
|
||||
...
|
||||
};
|
||||
|
||||
fn teststream_read(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = {
|
||||
let stream = s: *teststream;
|
||||
buf = stream.buf;
|
||||
return len(buf);
|
||||
};
|
||||
|
||||
fn teststream_write(s: *io::stream, buf: const []u8) (size | io::error) = {
|
||||
let stream = s: *teststream;
|
||||
append(stream.buf, buf...);
|
||||
return len(buf);
|
||||
};
|
64
vendor/hare-logfmt/log/logfmt/logfmt.ha
vendored
Normal file
64
vendor/hare-logfmt/log/logfmt/logfmt.ha
vendored
Normal file
|
@ -0,0 +1,64 @@
|
|||
use io;
|
||||
use log;
|
||||
use fmt;
|
||||
use time::date;
|
||||
use os;
|
||||
use encoding::utf8;
|
||||
use strings;
|
||||
|
||||
// Implements the log::logger for outputting logs in Logfmt format.
|
||||
export type logfmtlogger = struct {
|
||||
log::logger,
|
||||
handle: io::handle,
|
||||
};
|
||||
|
||||
// creates a new instace of logfmtlogger to be use with [[log::setlogger]].
|
||||
export fn new(handle: io::handle) logfmtlogger = {
|
||||
return logfmtlogger {
|
||||
println = &log_println,
|
||||
printfln = &log_printfln,
|
||||
handle = handle,
|
||||
};
|
||||
};
|
||||
|
||||
fn log_println(logger: *log::logger, fields: fmt::formattable...) void = {
|
||||
const logger = logger: *logfmtlogger;
|
||||
assert(logger.println == &log_println);
|
||||
|
||||
const now = date::now();
|
||||
fmt::fprint(logger.handle, "ts="): void;
|
||||
date::format(logger.handle, date::RFC3339, &now): void;
|
||||
fmt::fprint(logger.handle, " "): void;
|
||||
|
||||
for (let i = 0z; i < len(fields); i+= 1) {
|
||||
if (i % 2 == 0) {
|
||||
fmt::fprint(logger.handle, fields[i]): void;
|
||||
fmt::fprint(logger.handle, "="): void;
|
||||
} else {
|
||||
fmt::fprint(logger.handle, fields[i]): void;
|
||||
fmt::fprint(logger.handle, " "): void;
|
||||
};
|
||||
};
|
||||
fmt::fprintln(logger.handle, ""): void;
|
||||
};
|
||||
|
||||
fn log_printfln(logger: *log::logger, fmt: str, fields: fmt::field...) void = {
|
||||
const logger = logger: *logfmtlogger;
|
||||
assert(logger.printfln == &log_printfln);
|
||||
|
||||
const now = date::now();
|
||||
fmt::fprint(logger.handle, "ts="): void;
|
||||
date::format(logger.handle, date::RFC3339, &now): void;
|
||||
fmt::fprint(logger.handle, " "): void;
|
||||
|
||||
for (let i = 0z; i < len(fields); i+= 1) {
|
||||
if (i % 2 == 0) {
|
||||
fmt::fprintf(logger.handle, "{}", fields[i]): void;
|
||||
fmt::fprint(logger.handle, "="): void;
|
||||
} else {
|
||||
fmt::fprintf(logger.handle, "{}", fields[i]): void;
|
||||
fmt::fprint(logger.handle, " "): void;
|
||||
};
|
||||
};
|
||||
fmt::fprintln(logger.handle, ""): void;
|
||||
};
|
Loading…
Reference in a new issue