re-vendor dependencies
This commit is contained in:
parent
7c8dde1bbf
commit
a1dd0b21f4
6
.gitmodules
vendored
6
.gitmodules
vendored
|
@ -1,10 +1,10 @@
|
||||||
[submodule "backend/vendor/hare-http"]
|
[submodule "vendor/hare-http"]
|
||||||
path = vendor/hare-http
|
path = vendor/hare-http
|
||||||
url = https://git.fnordig.de/jer/hare-http.git
|
url = https://git.fnordig.de/jer/hare-http.git
|
||||||
branch = host-in-uri
|
branch = host-in-uri
|
||||||
[submodule "backend/vendor/hare-logfmt"]
|
[submodule "vendor/hare-logfmt"]
|
||||||
path = vendor/hare-logfmt
|
path = vendor/hare-logfmt
|
||||||
url = https://git.sr.ht/~blainsmith/hare-logfmt
|
url = https://git.sr.ht/~blainsmith/hare-logfmt
|
||||||
[submodule "backend/vendor/hare-json"]
|
[submodule "vendor/hare-json"]
|
||||||
path = vendor/hare-json
|
path = vendor/hare-json
|
||||||
url = https://git.sr.ht/~sircmpwn/hare-json
|
url = https://git.sr.ht/~sircmpwn/hare-json
|
||||||
|
|
1
vendor/hare-http
vendored
Submodule
1
vendor/hare-http
vendored
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit f3a96257c7fc3594a1ec2cde37c248730a174e6f
|
367
vendor/hare-http/COPYING
vendored
367
vendor/hare-http/COPYING
vendored
|
@ -1,367 +0,0 @@
|
||||||
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.
|
|
74
vendor/hare-http/cmd/http/main.ha
vendored
74
vendor/hare-http/cmd/http/main.ha
vendored
|
@ -1,74 +0,0 @@
|
||||||
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"),
|
|
||||||
"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
92
vendor/hare-http/cmd/httpd/main.ha
vendored
|
@ -1,92 +0,0 @@
|
||||||
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
17
vendor/hare-http/net/http/README
vendored
|
@ -1,17 +0,0 @@
|
||||||
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
95
vendor/hare-http/net/http/client.ha
vendored
|
@ -1,95 +0,0 @@
|
||||||
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
112
vendor/hare-http/net/http/constants.ha
vendored
|
@ -1,112 +0,0 @@
|
||||||
// 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
144
vendor/hare-http/net/http/do.ha
vendored
|
@ -1,144 +0,0 @@
|
||||||
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
26
vendor/hare-http/net/http/error.ha
vendored
|
@ -1,26 +0,0 @@
|
||||||
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";
|
|
||||||
};
|
|
||||||
};
|
|
107
vendor/hare-http/net/http/header.ha
vendored
107
vendor/hare-http/net/http/header.ha
vendored
|
@ -1,107 +0,0 @@
|
||||||
use ascii;
|
|
||||||
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);
|
|
||||||
let name = ascii::strlower(name);
|
|
||||||
append(head, (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 (ascii::strcasecmp(head[i].0, name) == 0) {
|
|
||||||
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 (ascii::strcasecmp(key, name) == 0) {
|
|
||||||
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 == "") {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
// TODO: validate field-name
|
|
||||||
|
|
||||||
header_add(head, name, val);
|
|
||||||
};
|
|
||||||
};
|
|
278
vendor/hare-http/net/http/request.ha
vendored
278
vendor/hare-http/net/http/request.ha
vendored
|
@ -1,278 +0,0 @@
|
||||||
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
63
vendor/hare-http/net/http/response.ha
vendored
|
@ -1,63 +0,0 @@
|
||||||
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
44
vendor/hare-http/net/http/server.ha
vendored
|
@ -1,44 +0,0 @@
|
||||||
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
111
vendor/hare-http/net/http/status.ha
vendored
|
@ -1,111 +0,0 @@
|
||||||
// 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
296
vendor/hare-http/net/http/transport.ha
vendored
|
@ -1,296 +0,0 @@
|
||||||
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;
|
|
||||||
};
|
|
||||||
};
|
|
1
vendor/hare-json
vendored
Submodule
1
vendor/hare-json
vendored
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit b6aeae96199607a1f3b4d437d5c99f821bd6a6b6
|
367
vendor/hare-json/COPYING
vendored
367
vendor/hare-json/COPYING
vendored
|
@ -1,367 +0,0 @@
|
||||||
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
36
vendor/hare-json/Makefile
vendored
|
@ -1,36 +0,0 @@
|
||||||
.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
23
vendor/hare-json/README.md
vendored
|
@ -1,23 +0,0 @@
|
||||||
# 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
62
vendor/hare-json/encoding/json/+test/lexer.ha
vendored
|
@ -1,62 +0,0 @@
|
||||||
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
164
vendor/hare-json/encoding/json/+test/test_load.ha
vendored
|
@ -1,164 +0,0 @@
|
||||||
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);
|
|
||||||
};
|
|
|
@ -1,49 +0,0 @@
|
||||||
// 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
15
vendor/hare-json/encoding/json/README
vendored
|
@ -1,15 +0,0 @@
|
||||||
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
81
vendor/hare-json/encoding/json/dump.ha
vendored
|
@ -1,81 +0,0 @@
|
||||||
// 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
377
vendor/hare-json/encoding/json/lex.ha
vendored
|
@ -1,377 +0,0 @@
|
||||||
// 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
148
vendor/hare-json/encoding/json/load.ha
vendored
|
@ -1,148 +0,0 @@
|
||||||
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
26
vendor/hare-json/encoding/json/path/path.ha
vendored
|
@ -1,26 +0,0 @@
|
||||||
// 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
50
vendor/hare-json/encoding/json/types.ha
vendored
|
@ -1,50 +0,0 @@
|
||||||
// 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
219
vendor/hare-json/encoding/json/value.ha
vendored
|
@ -1,219 +0,0 @@
|
||||||
// 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;
|
|
||||||
};
|
|
||||||
};
|
|
1
vendor/hare-logfmt
vendored
Submodule
1
vendor/hare-logfmt
vendored
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 2b4a37459be54c83ac6ac6354cddec3e9fa796bf
|
21
vendor/hare-logfmt/LICENSE
vendored
21
vendor/hare-logfmt/LICENSE
vendored
|
@ -1,21 +0,0 @@
|
||||||
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
28
vendor/hare-logfmt/Makefile
vendored
|
@ -1,28 +0,0 @@
|
||||||
.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
45
vendor/hare-logfmt/README.md
vendored
|
@ -1,45 +0,0 @@
|
||||||
# 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
48
vendor/hare-logfmt/log/logfmt/+test.ha
vendored
|
@ -1,48 +0,0 @@
|
||||||
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
64
vendor/hare-logfmt/log/logfmt/logfmt.ha
vendored
|
@ -1,64 +0,0 @@
|
||||||
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