mirror of
https://gitea.invidious.io/iv-org/lsquic.cr
synced 2024-08-15 00:43:31 +00:00
Compare commits
26 commits
Author | SHA1 | Date | |
---|---|---|---|
|
de2c945a2e | ||
|
60c4020fd9 | ||
|
53c90d4b1c | ||
|
d8b1b5077f | ||
|
6cbead5185 | ||
|
037cd3a47d | ||
|
9049f7ec29 | ||
|
fea2b19a63 | ||
|
a88e21b222 | ||
|
fa1c9d8814 | ||
|
714461074a | ||
|
15a67d48a0 | ||
|
8825f4741d | ||
|
0219ab13d3 | ||
|
2ebf1fc9ac | ||
|
c0ba00560f | ||
|
125a547a4c | ||
|
73903fa7e1 | ||
|
3a19eac5bd | ||
|
0421bbed1c | ||
|
5b9a26d5ec | ||
|
7fe65a03ea | ||
|
75937aee70 | ||
|
96d761aa1e | ||
|
1dc92cd18e | ||
|
21fb20c02b |
14 changed files with 874 additions and 415 deletions
251
LICENSE.boringssl
Normal file
251
LICENSE.boringssl
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
BoringSSL is a fork of OpenSSL. As such, large parts of it fall under OpenSSL
|
||||||
|
licensing. Files that are completely new have a Google copyright and an ISC
|
||||||
|
license. This license is reproduced at the bottom of this file.
|
||||||
|
|
||||||
|
Contributors to BoringSSL are required to follow the CLA rules for Chromium:
|
||||||
|
https://cla.developers.google.com/clas
|
||||||
|
|
||||||
|
Files in third_party/ have their own licenses, as described therein. The MIT
|
||||||
|
license, for third_party/fiat, which, unlike other third_party directories, is
|
||||||
|
compiled into non-test libraries, is included below.
|
||||||
|
|
||||||
|
The OpenSSL toolkit stays under a dual license, i.e. both the conditions of the
|
||||||
|
OpenSSL License and the original SSLeay license apply to the toolkit. See below
|
||||||
|
for the actual license texts. Actually both licenses are BSD-style Open Source
|
||||||
|
licenses. In case of any license issues related to OpenSSL please contact
|
||||||
|
openssl-core@openssl.org.
|
||||||
|
|
||||||
|
The following are Google-internal bug numbers where explicit permission from
|
||||||
|
some authors is recorded for use of their work. (This is purely for our own
|
||||||
|
record keeping.)
|
||||||
|
27287199
|
||||||
|
27287880
|
||||||
|
27287883
|
||||||
|
|
||||||
|
OpenSSL License
|
||||||
|
---------------
|
||||||
|
|
||||||
|
/* ====================================================================
|
||||||
|
* Copyright (c) 1998-2011 The OpenSSL Project. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in
|
||||||
|
* the documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
*
|
||||||
|
* 3. All advertising materials mentioning features or use of this
|
||||||
|
* software must display the following acknowledgment:
|
||||||
|
* "This product includes software developed by the OpenSSL Project
|
||||||
|
* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
|
||||||
|
*
|
||||||
|
* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
|
||||||
|
* endorse or promote products derived from this software without
|
||||||
|
* prior written permission. For written permission, please contact
|
||||||
|
* openssl-core@openssl.org.
|
||||||
|
*
|
||||||
|
* 5. Products derived from this software may not be called "OpenSSL"
|
||||||
|
* nor may "OpenSSL" appear in their names without prior written
|
||||||
|
* permission of the OpenSSL Project.
|
||||||
|
*
|
||||||
|
* 6. Redistributions of any form whatsoever must retain the following
|
||||||
|
* acknowledgment:
|
||||||
|
* "This product includes software developed by the OpenSSL Project
|
||||||
|
* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
|
||||||
|
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
|
||||||
|
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
* ====================================================================
|
||||||
|
*
|
||||||
|
* This product includes cryptographic software written by Eric Young
|
||||||
|
* (eay@cryptsoft.com). This product includes software written by Tim
|
||||||
|
* Hudson (tjh@cryptsoft.com).
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
Original SSLeay License
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This package is an SSL implementation written
|
||||||
|
* by Eric Young (eay@cryptsoft.com).
|
||||||
|
* The implementation was written so as to conform with Netscapes SSL.
|
||||||
|
*
|
||||||
|
* This library is free for commercial and non-commercial use as long as
|
||||||
|
* the following conditions are aheared to. The following conditions
|
||||||
|
* apply to all code found in this distribution, be it the RC4, RSA,
|
||||||
|
* lhash, DES, etc., code; not just the SSL code. The SSL documentation
|
||||||
|
* included with this distribution is covered by the same copyright terms
|
||||||
|
* except that the holder is Tim Hudson (tjh@cryptsoft.com).
|
||||||
|
*
|
||||||
|
* Copyright remains Eric Young's, and as such any Copyright notices in
|
||||||
|
* the code are not to be removed.
|
||||||
|
* If this package is used in a product, Eric Young should be given attribution
|
||||||
|
* as the author of the parts of the library used.
|
||||||
|
* This can be in the form of a textual message at program startup or
|
||||||
|
* in documentation (online or textual) provided with the package.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* 3. All advertising materials mentioning features or use of this software
|
||||||
|
* must display the following acknowledgement:
|
||||||
|
* "This product includes cryptographic software written by
|
||||||
|
* Eric Young (eay@cryptsoft.com)"
|
||||||
|
* The word 'cryptographic' can be left out if the rouines from the library
|
||||||
|
* being used are not cryptographic related :-).
|
||||||
|
* 4. If you include any Windows specific code (or a derivative thereof) from
|
||||||
|
* the apps directory (application code) you must include an acknowledgement:
|
||||||
|
* "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
* SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* The licence and distribution terms for any publically available version or
|
||||||
|
* derivative of this code cannot be changed. i.e. this code cannot simply be
|
||||||
|
* copied and put under another distribution licence
|
||||||
|
* [including the GNU Public Licence.]
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
ISC license used for completely new code in BoringSSL:
|
||||||
|
|
||||||
|
/* Copyright (c) 2015, Google Inc.
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
|
||||||
|
|
||||||
|
|
||||||
|
The code in third_party/fiat carries the MIT license:
|
||||||
|
|
||||||
|
Copyright (c) 2015-2016 the fiat-crypto authors (see
|
||||||
|
https://github.com/mit-plv/fiat-crypto/blob/master/AUTHORS).
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
Licenses for support code
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Parts of the TLS test suite are under the Go license. This code is not included
|
||||||
|
in BoringSSL (i.e. libcrypto and libssl) when compiled, however, so
|
||||||
|
distributing code linked against BoringSSL does not trigger this license:
|
||||||
|
|
||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
|
||||||
|
BoringSSL uses the Chromium test infrastructure to run a continuous build,
|
||||||
|
trybots etc. The scripts which manage this, and the script for generating build
|
||||||
|
metadata, are under the Chromium license. Distributing code linked against
|
||||||
|
BoringSSL does not trigger this license.
|
||||||
|
|
||||||
|
Copyright 2015 The Chromium Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
32
LICENSE.chrome
Normal file
32
LICENSE.chrome
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
A few parts of LiteSpeed QUIC library are based on proto-quic. That
|
||||||
|
code is covered by this additional license:
|
||||||
|
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
Copyright 2015 The Chromium Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
21
LICENSE.lsquic
Normal file
21
LICENSE.lsquic
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 - 2019 LiteSpeed Technologies Inc
|
||||||
|
|
||||||
|
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.
|
25
README.md
25
README.md
|
@ -2,6 +2,12 @@
|
||||||
|
|
||||||
Crystal bindings to the excellent [LSQUIC](https://github.com/litespeedtech/lsquic) library.
|
Crystal bindings to the excellent [LSQUIC](https://github.com/litespeedtech/lsquic) library.
|
||||||
|
|
||||||
|
`libssl.a`, `libcrypto.a` are both licensed under `LICENSE.boringssl`.
|
||||||
|
|
||||||
|
`liblsquic.a` is licensed under `LICENSE.lsquic` and `LICENSE.chrome`.
|
||||||
|
|
||||||
|
This library is available under the MIT license.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Add the dependency to your `shard.yml`:
|
1. Add the dependency to your `shard.yml`:
|
||||||
|
@ -14,6 +20,21 @@ Crystal bindings to the excellent [LSQUIC](https://github.com/litespeedtech/lsqu
|
||||||
|
|
||||||
2. Run `shards install`
|
2. Run `shards install`
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```crystal
|
||||||
|
require "lsquic"
|
||||||
|
|
||||||
|
client = QUIC::Client.new("www.youtube.com")
|
||||||
|
client.get("/") # => #<HTTP::Client::Response>
|
||||||
|
|
||||||
|
client.get("/", headers: HTTP::Headers{
|
||||||
|
"cookie" => "Some value",
|
||||||
|
# ...
|
||||||
|
}) # => #<HTTP::Client::Response>
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
1. Fork it (<https://github.com/omarroth/lsquic.cr/fork>)
|
1. Fork it (<https://github.com/omarroth/lsquic.cr/fork>)
|
||||||
|
@ -25,3 +46,7 @@ Crystal bindings to the excellent [LSQUIC](https://github.com/litespeedtech/lsqu
|
||||||
## Contributors
|
## Contributors
|
||||||
|
|
||||||
- [Omar Roth](https://github.com/omarroth) - creator and maintainer
|
- [Omar Roth](https://github.com/omarroth) - creator and maintainer
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
name: lsquic
|
name: lsquic
|
||||||
version: 0.1.0
|
version: 0.1.9
|
||||||
|
|
||||||
authors:
|
authors:
|
||||||
- Omar Roth <omarroth@protonmail.com>
|
- Omar Roth <omarroth@protonmail.com>
|
||||||
|
|
||||||
crystal: 0.31.1
|
crystal: 0.33.0
|
||||||
|
|
||||||
license: MIT
|
license: MIT
|
||||||
|
|
|
@ -1,9 +1,42 @@
|
||||||
require "./spec_helper"
|
require "./spec_helper"
|
||||||
|
|
||||||
describe Lsquic do
|
describe QUIC do
|
||||||
# TODO: Write tests
|
|
||||||
|
|
||||||
it "works" do
|
it "works" do
|
||||||
false.should eq(true)
|
client = QUIC::Client.new("www.youtube.com")
|
||||||
|
|
||||||
|
5.times do
|
||||||
|
client.get("/").status_code.should eq(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
client.close
|
||||||
|
end
|
||||||
|
|
||||||
|
it "works with fibers" do
|
||||||
|
ch = Channel(Int32).new
|
||||||
|
|
||||||
|
5.times do
|
||||||
|
spawn do
|
||||||
|
client = QUIC::Client.new("www.youtube.com")
|
||||||
|
|
||||||
|
5.times do
|
||||||
|
ch.send client.get("/").status_code
|
||||||
|
end
|
||||||
|
|
||||||
|
client.close
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
(5 * 5).times do
|
||||||
|
ch.receive.should eq(200)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "restarts engine after closing" do
|
||||||
|
client = QUIC::Client.new("www.youtube.com")
|
||||||
|
|
||||||
|
client.get("/").status_code.should eq(200)
|
||||||
|
client.close
|
||||||
|
Fiber.yield
|
||||||
|
client.get("/").status_code.should eq(200)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
require "./lsquic/*"
|
require "./lsquic/*"
|
||||||
require "socket"
|
|
||||||
|
|
||||||
module QUIC
|
module QUIC
|
||||||
VERSION = "0.1.0"
|
VERSION = "#{LibLsquic::MAJOR_VERSION}.#{LibLsquic::MINOR_VERSION}.#{LibLsquic::PATCH_VERSION}"
|
||||||
QUIC_VERSION = "#{LibLsquic::MAJOR_VERSION}.#{LibLsquic::MINOR_VERSION}.#{LibLsquic::PATCH_VERSION}"
|
|
||||||
end
|
end
|
||||||
|
|
89
src/lsquic/channeled_pipe.cr
Normal file
89
src/lsquic/channeled_pipe.cr
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
# Based on https://github.com/anykeyh/channeled_pipe/blob/master/src/channeled_pipe/channeled_pipe.cr
|
||||||
|
class IO::ChanneledPipe < IO
|
||||||
|
BUFFER_SIZE = 8192
|
||||||
|
|
||||||
|
include IO::Buffered
|
||||||
|
|
||||||
|
@channel : Channel(Bytes?)
|
||||||
|
@direction : Symbol
|
||||||
|
@buffer : Bytes?
|
||||||
|
|
||||||
|
getter? closed = false
|
||||||
|
|
||||||
|
protected def initialize(@channel, @direction)
|
||||||
|
end
|
||||||
|
|
||||||
|
def unbuffered_read(slice : Bytes)
|
||||||
|
raise "Cannot read from write side" if @direction == :w
|
||||||
|
return 0 if @channel.closed? && !@buffer
|
||||||
|
|
||||||
|
buffer = @buffer
|
||||||
|
|
||||||
|
if buffer
|
||||||
|
bytes_read = {slice.size, buffer.size}.min
|
||||||
|
slice.copy_from(buffer.to_unsafe, bytes_read)
|
||||||
|
|
||||||
|
if buffer.size == bytes_read
|
||||||
|
@buffer = nil
|
||||||
|
else
|
||||||
|
@buffer = buffer[bytes_read, buffer.size - bytes_read]
|
||||||
|
end
|
||||||
|
|
||||||
|
return bytes_read
|
||||||
|
else
|
||||||
|
buffer = @channel.receive
|
||||||
|
|
||||||
|
if buffer
|
||||||
|
bytes_read = {slice.size, buffer.size}.min
|
||||||
|
slice.copy_from(buffer.to_unsafe, bytes_read)
|
||||||
|
|
||||||
|
if buffer.size > bytes_read
|
||||||
|
@buffer = buffer[bytes_read, buffer.size - bytes_read]
|
||||||
|
end
|
||||||
|
|
||||||
|
return bytes_read
|
||||||
|
else
|
||||||
|
@channel.close
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def unbuffered_write(slice : Bytes)
|
||||||
|
raise "Write not allowed on read side" if @direction == :r
|
||||||
|
raise "Closed stream" if @closed
|
||||||
|
@channel.send slice.clone
|
||||||
|
end
|
||||||
|
|
||||||
|
def close_channel
|
||||||
|
@channel.close
|
||||||
|
end
|
||||||
|
|
||||||
|
def unbuffered_flush
|
||||||
|
# Nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
def unbuffered_rewind
|
||||||
|
raise IO::Error.new("Can't rewind")
|
||||||
|
end
|
||||||
|
|
||||||
|
def unbuffered_close
|
||||||
|
return if @closed
|
||||||
|
@closed = true
|
||||||
|
@channel.send nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.new(mem = BUFFER_SIZE)
|
||||||
|
mem = BUFFER_SIZE if mem <= 0
|
||||||
|
|
||||||
|
capacity = (mem / BUFFER_SIZE) +
|
||||||
|
((mem % BUFFER_SIZE != 0) ? 1 : 0)
|
||||||
|
|
||||||
|
channel = Channel(Bytes?).new(capacity: mem)
|
||||||
|
|
||||||
|
{
|
||||||
|
ChanneledPipe.new(channel, :r),
|
||||||
|
ChanneledPipe.new(channel, :w),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,98 +1,48 @@
|
||||||
require "http/headers"
|
require "http"
|
||||||
require "http/client"
|
require "socket"
|
||||||
require "socket/udp_socket"
|
|
||||||
|
|
||||||
struct QUIC::PeerCtx
|
module QUIC
|
||||||
property socket : UDPSocket
|
class StreamCtx
|
||||||
|
property request : HTTP::Request
|
||||||
|
property io : IO::ChanneledPipe
|
||||||
|
|
||||||
def initialize(@socket)
|
def initialize(@request, @io)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def local_address
|
class Client
|
||||||
@socket.local_address
|
def self.stream_readf(stream_if_ctx : Void*, buf : UInt8*, buf_len : LibC::SizeT, fin : LibC::Int)
|
||||||
end
|
stream_ctx = Box(StreamCtx).unbox(stream_if_ctx)
|
||||||
|
stream_ctx.io.write Slice.new(buf, buf_len)
|
||||||
def remote_address
|
buf_len
|
||||||
@socket.remote_address
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
struct QUIC::StreamCtx
|
|
||||||
property requests : Array(HTTP::Request)
|
|
||||||
property io : IO
|
|
||||||
|
|
||||||
def initialize
|
|
||||||
@requests = [] of HTTP::Request
|
|
||||||
@io = IO::Memory.new
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class QUIC::Client
|
|
||||||
ENGINE_FLAGS = LibLsquic::LSENG_HTTP
|
|
||||||
LibLsquic.global_init(ENGINE_FLAGS & LibLsquic::LSENG_SERVER ? LibLsquic::GLOBAL_SERVER : LibLsquic::GLOBAL_CLIENT)
|
|
||||||
|
|
||||||
# The set of possible valid body types.
|
|
||||||
alias BodyType = String | Bytes | IO | Nil
|
|
||||||
|
|
||||||
getter host : String
|
|
||||||
getter port : Int32
|
|
||||||
getter! tls : OpenSSL::SSL::Context::Client
|
|
||||||
|
|
||||||
@peer_ctx : PeerCtx | Nil
|
|
||||||
@engine : LibLsquic::EngineT | Nil
|
|
||||||
@conn : LibLsquic::ConnT | Nil
|
|
||||||
@engine : LibLsquic::EngineT | Nil
|
|
||||||
@engine_settings : LibLsquic::EngineSettings
|
|
||||||
@stream_if : LibLsquic::StreamIf
|
|
||||||
@engine_api : LibLsquic::EngineApi
|
|
||||||
|
|
||||||
@dns_timeout : Float64?
|
|
||||||
@connect_timeout : Float64?
|
|
||||||
@read_timeout : Float64?
|
|
||||||
|
|
||||||
def initialize(@host : String, port = nil, tls : Bool | OpenSSL::SSL::Context::Client = false)
|
|
||||||
check_host_only(@host)
|
|
||||||
|
|
||||||
@tls = case tls
|
|
||||||
when true
|
|
||||||
OpenSSL::SSL::Context::Client.new
|
|
||||||
when OpenSSL::SSL::Context::Client
|
|
||||||
tls
|
|
||||||
when false
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
@port = (port || 443).to_i
|
|
||||||
|
|
||||||
LibLsquic.engine_init_settings(out @engine_settings, ENGINE_FLAGS)
|
|
||||||
@engine_settings.es_ua = "Chrome/78.0.3904.97 Linux x86_64"
|
|
||||||
@engine_settings.es_ecn = 0
|
|
||||||
|
|
||||||
err_buf = Bytes.new(0x100)
|
|
||||||
err_code = LibLsquic.engine_check_settings(pointerof(@engine_settings), ENGINE_FLAGS, err_buf, err_buf.size)
|
|
||||||
raise String.new(err_buf) if err_code != 0
|
|
||||||
|
|
||||||
@stream_if = LibLsquic::StreamIf.new
|
|
||||||
@stream_if.on_new_conn = ->(stream_if_ctx : Void*, c : LibLsquic::ConnT) { stream_if_ctx }
|
|
||||||
@stream_if.on_conn_closed = ->(c : LibLsquic::ConnT) do
|
|
||||||
Box.box(nil)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@stream_if.on_new_stream = ->(stream_if_ctx : Void*, s : LibLsquic::StreamT) do
|
def self.on_new_conn(stream_if_ctx : Void*, c : LibLsquic::ConnT)
|
||||||
if LibLsquic.stream_is_pushed(s) != 0
|
|
||||||
return stream_if_ctx
|
|
||||||
end
|
|
||||||
|
|
||||||
LibLsquic.stream_wantwrite(s, 1)
|
|
||||||
stream_if_ctx
|
stream_if_ctx
|
||||||
end
|
end
|
||||||
|
|
||||||
@stream_if.on_write = ->(s : LibLsquic::StreamT, stream_if_ctx : Void*) do
|
def self.on_conn_closed(c : LibLsquic::ConnT)
|
||||||
request = Box(StreamCtx).unbox(stream_if_ctx).requests.shift
|
Box.box(nil)
|
||||||
raise "No request" if !request
|
end
|
||||||
|
|
||||||
|
def self.on_new_stream(stream_if_ctx : Void*, s : LibLsquic::StreamT)
|
||||||
|
stream_ctx = LibLsquic.stream_conn(s)
|
||||||
|
.try { |c| LibLsquic.conn_get_ctx(c) }
|
||||||
|
.try { |c| Box(StreamCtx).unbox(c) }
|
||||||
|
|
||||||
|
if LibLsquic.stream_is_pushed(s) != 0
|
||||||
|
return Box.box(stream_ctx)
|
||||||
|
end
|
||||||
|
|
||||||
|
LibLsquic.stream_wantwrite(s, 1)
|
||||||
|
Box.box(stream_ctx)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.on_write(s : LibLsquic::StreamT, stream_if_ctx : Void*)
|
||||||
|
stream_ctx = Box(StreamCtx).unbox(stream_if_ctx)
|
||||||
|
|
||||||
headers = [] of LibLsquic::HttpHeader
|
headers = [] of LibLsquic::HttpHeader
|
||||||
(request.headers.to_a.sort_by { |k, v| {":authority", ":path", ":scheme", ":method"}.index(k) || -1 }).reverse.each do |tuple|
|
(stream_ctx.request.headers.to_a.sort_by { |k, v| {":authority", ":path", ":scheme", ":method"}.index(k) || -1 }).reverse.each do |tuple|
|
||||||
name, values = tuple
|
name, values = tuple
|
||||||
name = name.downcase
|
name = name.downcase
|
||||||
|
|
||||||
|
@ -117,11 +67,9 @@ class QUIC::Client
|
||||||
http_headers.count = headers.size
|
http_headers.count = headers.size
|
||||||
http_headers.headers = headers.to_unsafe
|
http_headers.headers = headers.to_unsafe
|
||||||
|
|
||||||
# For payload, last argument is 0
|
raise "Could not send headers" if LibLsquic.stream_send_headers(s, pointerof(http_headers), stream_ctx.request.body ? 0 : 1) != 0
|
||||||
raise "Could not send headers" if LibLsquic.stream_send_headers(s, pointerof(http_headers), request.body ? 0 : 1) != 0
|
|
||||||
|
|
||||||
if request.body
|
if body = stream_ctx.request.body.try &.gets_to_end
|
||||||
body = request.body.not_nil!.gets_to_end
|
|
||||||
LibLsquic.stream_write(s, body, body.bytesize)
|
LibLsquic.stream_write(s, body, body.bytesize)
|
||||||
LibLsquic.stream_flush(s)
|
LibLsquic.stream_flush(s)
|
||||||
end
|
end
|
||||||
|
@ -130,140 +78,254 @@ class QUIC::Client
|
||||||
LibLsquic.stream_wantwrite(s, 0)
|
LibLsquic.stream_wantwrite(s, 0)
|
||||||
LibLsquic.stream_wantread(s, 1)
|
LibLsquic.stream_wantread(s, 1)
|
||||||
|
|
||||||
stream_if_ctx
|
Box.box(stream_ctx)
|
||||||
end
|
end
|
||||||
|
|
||||||
@stream_if.on_read = ->(s : LibLsquic::StreamT, stream_if_ctx : Void*) do
|
def self.on_read(s : LibLsquic::StreamT, stream_if_ctx : Void*)
|
||||||
stream_ctx = Box(StreamCtx).unbox(stream_if_ctx)
|
stream_ctx = Box(StreamCtx).unbox(stream_if_ctx)
|
||||||
|
bytes_read = LibLsquic.stream_readf(s, ->stream_readf, Box.box(stream_ctx))
|
||||||
|
|
||||||
buffer = Bytes.new(0x200)
|
|
||||||
bytes_read = LibLsquic.stream_read(s, buffer, buffer.size)
|
|
||||||
if bytes_read > 0
|
if bytes_read > 0
|
||||||
stream_ctx.io.write buffer[0, bytes_read]
|
# Nothing
|
||||||
elsif bytes_read == 0
|
elsif bytes_read == 0
|
||||||
LibLsquic.stream_shutdown(s, 0)
|
LibLsquic.stream_shutdown(s, 0)
|
||||||
elsif LibLsquic.stream_is_rejected(s)
|
LibLsquic.stream_wantread(s, 0)
|
||||||
|
elsif LibLsquic.stream_is_rejected(s) == 1
|
||||||
LibLsquic.stream_close(s)
|
LibLsquic.stream_close(s)
|
||||||
else
|
else
|
||||||
raise "Could not read stream"
|
# raise "Could not read response"
|
||||||
end
|
end
|
||||||
|
|
||||||
stream_if_ctx
|
stream_if_ctx
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: Allow engine to break with existing connections
|
def self.on_close(s : LibLsquic::StreamT, stream_if_ctx : Void*)
|
||||||
@stream_if.on_close = ->(s : LibLsquic::StreamT, stream_if_ctx : Void*) do
|
stream_ctx = Box(StreamCtx).unbox(stream_if_ctx)
|
||||||
LibLsquic.conn_close(LibLsquic.stream_conn(s))
|
stream_ctx.io.close
|
||||||
|
GC.free stream_if_ctx
|
||||||
stream_if_ctx
|
stream_if_ctx
|
||||||
end
|
end
|
||||||
|
|
||||||
@engine_api = LibLsquic::EngineApi.new
|
def self.ea_packets_out(peer_ctx : Void*, specs : LibLsquic::OutSpec*, count : LibC::UInt)
|
||||||
@engine_api.ea_settings = pointerof(@engine_settings)
|
|
||||||
@engine_api.ea_stream_if = pointerof(@stream_if)
|
|
||||||
|
|
||||||
@stream_ctx = StreamCtx.new
|
|
||||||
@engine_api.ea_stream_if_ctx = Box.box(@stream_ctx) # TODO
|
|
||||||
|
|
||||||
@engine_api.ea_packets_out = ->(peer_ctx : Void*, specs : LibLsquic::OutSpec*, count : LibC::UInt) do
|
|
||||||
packets_out = 0
|
packets_out = 0
|
||||||
|
|
||||||
count.times do |i|
|
count.times do |i|
|
||||||
spec = specs[i]
|
spec = specs[i]
|
||||||
peer_ctx = Box(PeerCtx).unbox(spec.peer_ctx)
|
socket = Box(UDPSocket).unbox(spec.peer_ctx)
|
||||||
spec.iovlen.times do |j|
|
spec.iovlen.times do |j|
|
||||||
iov = spec.iov[j]
|
iov = spec.iov[j]
|
||||||
begin
|
begin
|
||||||
peer_ctx.socket.send(iov.iov_base.to_slice(iov.iov_len), to: peer_ctx.remote_address)
|
socket.send(iov.iov_base.to_slice(iov.iov_len), to: socket.remote_address)
|
||||||
packets_out += 1
|
packets_out += 1
|
||||||
rescue ex
|
rescue ex
|
||||||
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
packets_out
|
packets_out
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
private def check_host_only(string : String)
|
ENGINE_FLAGS = LibLsquic::LSENG_HTTP
|
||||||
# When parsing a URI with just a host
|
LibLsquic.global_init(ENGINE_FLAGS & LibLsquic::LSENG_SERVER ? LibLsquic::GLOBAL_SERVER : LibLsquic::GLOBAL_CLIENT)
|
||||||
# we end up with a URI with just a path
|
|
||||||
uri = URI.parse(string)
|
property family : Socket::Family = Socket::Family::INET
|
||||||
if uri.scheme || uri.host || uri.port || uri.query || uri.user || uri.password || uri.path.includes?('/')
|
|
||||||
|
# The set of possible valid body types.
|
||||||
|
alias BodyType = String | Bytes | IO | Nil
|
||||||
|
|
||||||
|
getter host : String
|
||||||
|
getter port : Int32
|
||||||
|
getter! tls : OpenSSL::SSL::Context::Client
|
||||||
|
|
||||||
|
@stream_channel : Channel(StreamCtx?)
|
||||||
|
@dns_timeout : Float64?
|
||||||
|
@connect_timeout : Float64?
|
||||||
|
@read_timeout : Float64?
|
||||||
|
@socket : UDPSocket?
|
||||||
|
@stream_ctx : StreamCtx?
|
||||||
|
|
||||||
|
def initialize(@host : String, port = nil, tls : Bool | OpenSSL::SSL::Context::Client = false)
|
||||||
|
check_host_only(@host)
|
||||||
|
|
||||||
|
@tls = case tls
|
||||||
|
when true
|
||||||
|
OpenSSL::SSL::Context::Client.new
|
||||||
|
when OpenSSL::SSL::Context::Client
|
||||||
|
tls
|
||||||
|
when false
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
@port = (port || 443).to_i
|
||||||
|
@stream_channel = Channel(StreamCtx?).new(20)
|
||||||
|
@stream_ctx = nil
|
||||||
|
@engine_open = false
|
||||||
|
end
|
||||||
|
|
||||||
|
def run_engine
|
||||||
|
LibLsquic.engine_init_settings(out engine_settings, ENGINE_FLAGS)
|
||||||
|
engine_settings.es_ua = "Chrome/78.0.3904.97 Linux x86_64"
|
||||||
|
engine_settings.es_ecn = 0
|
||||||
|
|
||||||
|
err_buf = Bytes.new(0x100)
|
||||||
|
err_code = LibLsquic.engine_check_settings(pointerof(engine_settings), ENGINE_FLAGS, err_buf, err_buf.size)
|
||||||
|
raise String.new(err_buf) if err_code != 0
|
||||||
|
|
||||||
|
stream_if = LibLsquic::StreamIf.new
|
||||||
|
stream_if.on_new_conn = ->QUIC::Client.on_new_conn(Void*, LibLsquic::ConnT)
|
||||||
|
stream_if.on_conn_closed = ->QUIC::Client.on_conn_closed(LibLsquic::ConnT)
|
||||||
|
stream_if.on_new_stream = ->QUIC::Client.on_new_stream(Void*, LibLsquic::StreamT)
|
||||||
|
stream_if.on_write = ->QUIC::Client.on_write(LibLsquic::StreamT, Void*)
|
||||||
|
stream_if.on_read = ->QUIC::Client.on_read(LibLsquic::StreamT, Void*)
|
||||||
|
stream_if.on_close = ->QUIC::Client.on_close(LibLsquic::StreamT, Void*)
|
||||||
|
|
||||||
|
engine_api = LibLsquic::EngineApi.new
|
||||||
|
engine_api.ea_settings = pointerof(engine_settings)
|
||||||
|
engine_api.ea_stream_if = pointerof(stream_if)
|
||||||
|
engine_api.ea_packets_out = ->QUIC::Client.ea_packets_out(Void*, LibLsquic::OutSpec*, LibC::UInt)
|
||||||
|
|
||||||
|
# logger_if = LibLsquic::LoggerIf.new
|
||||||
|
# logger_if.log_buf = ->(logger_ctx : Void*, msg_buf : LibC::Char*, msg_size : LibC::SizeT) { puts String.new(msg_buf); 0 }
|
||||||
|
# LibLsquic.logger_init(pointerof(logger_if), nil, LibLsquic::LoggerTimestampStyle::LltsHhmmssms)
|
||||||
|
# LibLsquic.set_log_level("debug")
|
||||||
|
|
||||||
|
engine = LibLsquic.engine_new(ENGINE_FLAGS, pointerof(engine_api))
|
||||||
|
hostname = host.starts_with?('[') && host.ends_with?(']') ? host[1..-2] : host
|
||||||
|
@engine_open = true
|
||||||
|
|
||||||
|
conn = LibLsquic.engine_connect(engine, LibLsquic::Version::Lsqver046, socket.local_address, socket.remote_address, Box.box(socket), nil, hostname, 0, nil, 0, nil, 0)
|
||||||
|
spawn do
|
||||||
|
while stream_ctx = @stream_channel.receive
|
||||||
|
LibLsquic.conn_set_ctx(conn, Box.box(stream_ctx))
|
||||||
|
LibLsquic.conn_make_stream(conn)
|
||||||
|
LibLsquic.engine_process_conns(engine)
|
||||||
|
end
|
||||||
|
@engine_open = false
|
||||||
|
LibLsquic.engine_destroy(engine)
|
||||||
|
end
|
||||||
|
|
||||||
|
buffer = Bytes.new(0x600)
|
||||||
|
loop do
|
||||||
|
begin
|
||||||
|
bytes_read = socket.read buffer
|
||||||
|
rescue ex
|
||||||
|
break
|
||||||
|
end
|
||||||
|
break if !@engine_open
|
||||||
|
LibLsquic.engine_packet_in(engine, buffer[0, bytes_read], bytes_read, socket.local_address, socket.remote_address, Box.box(socket), 0) if bytes_read != 0
|
||||||
|
LibLsquic.engine_process_conns(engine)
|
||||||
|
end
|
||||||
|
@socket.try &.close
|
||||||
|
@socket = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def socket : UDPSocket
|
||||||
|
return @socket.as(UDPSocket) if @socket
|
||||||
|
|
||||||
|
socket = UDPSocket.new @family
|
||||||
|
case @family
|
||||||
|
when Socket::Family::INET
|
||||||
|
socket.bind Socket::IPAddress.new("0.0.0.0", 0)
|
||||||
|
when Socket::Family::INET6
|
||||||
|
socket.bind Socket::IPAddress.new("::", 0)
|
||||||
|
else
|
||||||
|
socket.bind Socket::IPAddress.new("0.0.0.0", 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
Socket::Addrinfo.udp(@host, @port, timeout: @dns_timeout, family: @family) do |addrinfo|
|
||||||
|
socket.connect(addrinfo, timeout: @connect_timeout) do |error|
|
||||||
|
close
|
||||||
|
error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
socket.read_timeout = @read_timeout if @read_timeout
|
||||||
|
socket.sync = false
|
||||||
|
|
||||||
|
@socket = socket
|
||||||
|
end
|
||||||
|
|
||||||
|
private def check_host_only(string : String)
|
||||||
|
# When parsing a URI with just a host
|
||||||
|
# we end up with a URI with just a path
|
||||||
|
uri = URI.parse(string)
|
||||||
|
if uri.scheme || uri.host || uri.port || uri.query || uri.user || uri.password || uri.path.includes?('/')
|
||||||
|
raise_invalid_host(string)
|
||||||
|
end
|
||||||
|
rescue URI::Error
|
||||||
raise_invalid_host(string)
|
raise_invalid_host(string)
|
||||||
end
|
end
|
||||||
rescue URI::Error
|
|
||||||
raise_invalid_host(string)
|
|
||||||
end
|
|
||||||
|
|
||||||
private def raise_invalid_host(string : String)
|
private def raise_invalid_host(string : String)
|
||||||
raise ArgumentError.new("The string passed to create an HTTP::Client must be just a host, not #{string.inspect}")
|
raise ArgumentError.new("The string passed to create an HTTP::Client must be just a host, not #{string.inspect}")
|
||||||
end
|
|
||||||
|
|
||||||
def self.new(uri : URI, tls = nil)
|
|
||||||
tls = tls_flag(uri, tls)
|
|
||||||
host = validate_host(uri)
|
|
||||||
new(host, uri.port, tls)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.new(uri : URI, tls = nil)
|
|
||||||
tls = tls_flag(uri, tls)
|
|
||||||
host = validate_host(uri)
|
|
||||||
client = new(host, uri.port, tls)
|
|
||||||
begin
|
|
||||||
yield client
|
|
||||||
ensure
|
|
||||||
client.close
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def self.new(host : String, port = nil, tls = false)
|
def self.new(uri : URI, tls = nil)
|
||||||
client = new(host, port, tls)
|
tls = tls_flag(uri, tls)
|
||||||
begin
|
host = validate_host(uri)
|
||||||
yield client
|
new(host, uri.port, tls)
|
||||||
ensure
|
|
||||||
client.close
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
# Configures this client to perform basic authentication in every
|
def self.new(uri : URI, tls = nil)
|
||||||
# request.
|
tls = tls_flag(uri, tls)
|
||||||
def basic_auth(username, password)
|
host = validate_host(uri)
|
||||||
header = "Basic #{Base64.strict_encode("#{username}:#{password}")}"
|
client = new(host, uri.port, tls)
|
||||||
before_request do |request|
|
begin
|
||||||
request.headers["Authorization"] = header
|
yield client
|
||||||
|
ensure
|
||||||
|
client.close
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def read_timeout=(read_timeout : Number)
|
def self.new(host : String, port = nil, tls = false)
|
||||||
@read_timeout = read_timeout.to_f
|
client = new(host, port, tls)
|
||||||
end
|
begin
|
||||||
|
yield client
|
||||||
|
ensure
|
||||||
|
client.close
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def read_timeout=(read_timeout : Time::Span)
|
# Configures this client to perform basic authentication in every
|
||||||
self.read_timeout = read_timeout.total_seconds
|
# request.
|
||||||
end
|
def basic_auth(username, password)
|
||||||
|
header = "Basic #{Base64.strict_encode("#{username}:#{password}")}"
|
||||||
|
before_request do |request|
|
||||||
|
request.headers["Authorization"] = header
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def connect_timeout=(connect_timeout : Number)
|
def read_timeout=(read_timeout : Number)
|
||||||
@connect_timeout = connect_timeout.to_f
|
@read_timeout = read_timeout.to_f
|
||||||
end
|
end
|
||||||
|
|
||||||
def connect_timeout=(connect_timeout : Time::Span)
|
def read_timeout=(read_timeout : Time::Span)
|
||||||
self.connect_timeout = connect_timeout.total_seconds
|
self.read_timeout = read_timeout.total_seconds
|
||||||
end
|
end
|
||||||
|
|
||||||
def dns_timeout=(dns_timeout : Number)
|
def connect_timeout=(connect_timeout : Number)
|
||||||
@dns_timeout = dns_timeout.to_f
|
@connect_timeout = connect_timeout.to_f
|
||||||
end
|
end
|
||||||
|
|
||||||
def dns_timeout=(dns_timeout : Time::Span)
|
def connect_timeout=(connect_timeout : Time::Span)
|
||||||
self.dns_timeout = dns_timeout.total_seconds
|
self.connect_timeout = connect_timeout.total_seconds
|
||||||
end
|
end
|
||||||
|
|
||||||
def before_request(&callback : HTTP::Request ->)
|
def dns_timeout=(dns_timeout : Number)
|
||||||
before_request = @before_request ||= [] of (HTTP::Request ->)
|
@dns_timeout = dns_timeout.to_f
|
||||||
before_request << callback
|
end
|
||||||
end
|
|
||||||
|
|
||||||
{% for method in %w(get post put head delete patch options) %}
|
def dns_timeout=(dns_timeout : Time::Span)
|
||||||
|
self.dns_timeout = dns_timeout.total_seconds
|
||||||
|
end
|
||||||
|
|
||||||
|
def before_request(&callback : HTTP::Request ->)
|
||||||
|
before_request = @before_request ||= [] of (HTTP::Request ->)
|
||||||
|
before_request << callback
|
||||||
|
end
|
||||||
|
|
||||||
|
{% for method in %w(get post put head delete patch options) %}
|
||||||
def {{method.id}}(path, headers : HTTP::Headers? = nil, body : BodyType = nil) : HTTP::Client::Response
|
def {{method.id}}(path, headers : HTTP::Headers? = nil, body : BodyType = nil) : HTTP::Client::Response
|
||||||
exec {{method.upcase}}, path, headers, body
|
exec {{method.upcase}}, path, headers, body
|
||||||
end
|
end
|
||||||
|
@ -325,252 +387,185 @@ class QUIC::Client
|
||||||
end
|
end
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
def exec(request : HTTP::Request) : HTTP::Client::Response
|
def exec(request : HTTP::Request) : HTTP::Client::Response
|
||||||
exec_internal(request)
|
exec_internal(request)
|
||||||
end
|
|
||||||
|
|
||||||
private def exec_internal(request)
|
|
||||||
response = exec_internal_single(request)
|
|
||||||
return handle_response(response) if response
|
|
||||||
|
|
||||||
# Server probably closed the connection, so retry one
|
|
||||||
close
|
|
||||||
request.body.try &.rewind
|
|
||||||
response = exec_internal_single(request)
|
|
||||||
return handle_response(response) if response
|
|
||||||
|
|
||||||
raise "Unexpected end of http response"
|
|
||||||
end
|
|
||||||
|
|
||||||
private def exec_internal_single(request)
|
|
||||||
send_request(request)
|
|
||||||
@stream_ctx.io.rewind
|
|
||||||
|
|
||||||
HTTP::Client::Response.from_io?(@stream_ctx.io, ignore_body: request.ignore_body?)
|
|
||||||
end
|
|
||||||
|
|
||||||
private def handle_response(response)
|
|
||||||
close # unless response.keep_alive?
|
|
||||||
response
|
|
||||||
end
|
|
||||||
|
|
||||||
def exec(request : HTTP::Request, &block)
|
|
||||||
exec_internal(request) do |response|
|
|
||||||
yield response
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
private def exec_internal(request, &block : Response -> T) : T forall T
|
private def exec_internal(request)
|
||||||
exec_internal_single(request) do |response|
|
response = exec_internal_single(request)
|
||||||
if response
|
return handle_response(response) if response
|
||||||
return handle_response(response) { yield response }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Server probably closed the connection, so retry once
|
raise "Unexpected end of http response"
|
||||||
close
|
|
||||||
request.body.try &.rewind
|
|
||||||
exec_internal_single(request) do |response|
|
|
||||||
if response
|
|
||||||
return handle_response(response) do
|
|
||||||
yield response
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
raise "Unexpected end of http response"
|
|
||||||
end
|
|
||||||
|
|
||||||
private def exec_internal_single(request)
|
private def exec_internal_single(request)
|
||||||
send_request(request)
|
io = send_request(request)
|
||||||
HTTP::Client::Response.from_io?(stream_ctx.io, ignore_body: request.ignore_body?) do |response|
|
HTTP::Client::Response.from_io?(io, ignore_body: request.ignore_body?)
|
||||||
yield response
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
private def handle_response(response)
|
private def handle_response(response)
|
||||||
value = yield
|
# close unless response.keep_alive?
|
||||||
response.body_io?.try &.close
|
response
|
||||||
close # unless response.keep_alive?
|
|
||||||
value
|
|
||||||
end
|
|
||||||
|
|
||||||
private def send_request(request)
|
|
||||||
set_defaults request
|
|
||||||
run_before_request_callbacks(request)
|
|
||||||
|
|
||||||
@stream_ctx.requests << request
|
|
||||||
LibLsquic.conn_make_stream(conn)
|
|
||||||
|
|
||||||
run_engine
|
|
||||||
end
|
|
||||||
|
|
||||||
private def set_defaults(request)
|
|
||||||
request.headers[":method"] ||= request.method
|
|
||||||
request.headers[":scheme"] ||= "https"
|
|
||||||
request.headers[":path"] ||= request.resource
|
|
||||||
request.headers[":authority"] ||= host_header
|
|
||||||
request.headers["user-agent"] ||= "Chrome/78.0.3904.97 Linux x86_64"
|
|
||||||
end
|
|
||||||
|
|
||||||
private def self.default_one_shot_headers(headers)
|
|
||||||
headers ||= HTTP::Headers.new
|
|
||||||
headers["Connection"] ||= "close"
|
|
||||||
headers
|
|
||||||
end
|
|
||||||
|
|
||||||
private def run_before_request_callbacks(request)
|
|
||||||
@before_request.try &.each &.call(request)
|
|
||||||
end
|
|
||||||
|
|
||||||
def exec(method : String, path, headers : HTTP::Headers? = nil, body : BodyType = nil) : HTTP::Client::Response
|
|
||||||
exec new_request method, path, headers, body
|
|
||||||
end
|
|
||||||
|
|
||||||
def exec(method : String, path, headers : HTTP::Headers? = nil, body : BodyType = nil)
|
|
||||||
exec(new_request(method, path, headers, body)) do |response|
|
|
||||||
yield response
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def self.exec(method, url : String | URI, headers : HTTP::Headers? = nil, body : BodyType = nil, tls = nil) : HTTP::Client::Response
|
def exec(request : HTTP::Request, &block)
|
||||||
headers = default_one_shot_headers(headers)
|
exec_internal(request) do |response|
|
||||||
exec(url, tls) do |client, path|
|
|
||||||
client.exec method, path, headers, body
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.exec(method, url : String | URI, headers : HTTP::Headers? = nil, body : BodyType = nil, tls = nil)
|
|
||||||
headers = default_one_shot_headers(headers)
|
|
||||||
exec(url, tls) do |client, path|
|
|
||||||
client.exec(method, path, headers, body) do |response|
|
|
||||||
yield response
|
yield response
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def close
|
private def exec_internal(request, &block : HTTP::Client::Response -> T) : T forall T
|
||||||
# @conn.try { |c| LibLsquic.conn_close(c) }
|
exec_internal_single(request) do |response|
|
||||||
@conn = nil
|
if response
|
||||||
end
|
return handle_response(response) { yield response }
|
||||||
|
end
|
||||||
private def new_request(method, path, headers, body : BodyType)
|
|
||||||
HTTP::Request.new(method, path, headers, body)
|
|
||||||
end
|
|
||||||
|
|
||||||
private def engine
|
|
||||||
engine = @engine
|
|
||||||
return engine if engine
|
|
||||||
|
|
||||||
engine = LibLsquic.engine_new(ENGINE_FLAGS, pointerof(@engine_api))
|
|
||||||
@engine = engine
|
|
||||||
end
|
|
||||||
|
|
||||||
def run_engine
|
|
||||||
buffer = Bytes.new(0x600)
|
|
||||||
|
|
||||||
loop do
|
|
||||||
LibLsquic.engine_process_conns(engine)
|
|
||||||
|
|
||||||
if LibLsquic.engine_earliest_adv_tick(engine, out diff) == 0
|
|
||||||
break
|
|
||||||
# else
|
|
||||||
# sleep (diff / 1000000).seconds
|
|
||||||
# sleep (diff % 1000000).microseconds
|
|
||||||
end
|
end
|
||||||
|
raise "Unexpected end of http response"
|
||||||
bytes_read = peer_ctx.socket.read(buffer)
|
|
||||||
LibLsquic.engine_packet_in(engine, buffer[0, bytes_read], bytes_read, peer_ctx.local_address, peer_ctx.remote_address, Box.box(peer_ctx), 0)
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
private def peer_ctx
|
private def exec_internal_single(request)
|
||||||
peer_ctx = @peer_ctx
|
io = send_request(request)
|
||||||
return peer_ctx if peer_ctx
|
HTTP::Client::Response.from_io?(io, ignore_body: request.ignore_body?) do |response|
|
||||||
|
yield response
|
||||||
hostname = @host.starts_with?('[') && @host.ends_with?(']') ? @host[1..-2] : @host
|
|
||||||
socket = UDPSocket.new
|
|
||||||
socket.bind Socket::IPAddress.new("0.0.0.0", 0)
|
|
||||||
socket.read_timeout = @read_timeout if @read_timeout
|
|
||||||
Socket::Addrinfo.udp(host, port, timeout: @dns_timeout) do |addrinfo|
|
|
||||||
socket.connect(addrinfo, timeout: @connect_timeout) do |error|
|
|
||||||
error
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
socket.sync = false
|
|
||||||
|
|
||||||
peer_ctx = PeerCtx.new(socket)
|
private def handle_response(response)
|
||||||
@peer_ctx = peer_ctx
|
value = yield
|
||||||
end
|
response.body_io?.try &.close
|
||||||
|
# close unless response.keep_alive?
|
||||||
def conn
|
value
|
||||||
conn = @conn
|
|
||||||
return conn if conn
|
|
||||||
|
|
||||||
hostname = @host.starts_with?('[') && @host.ends_with?(']') ? @host[1..-2] : @host
|
|
||||||
conn = LibLsquic.engine_connect(engine, LibLsquic::Version::Lsqver046, peer_ctx.local_address, peer_ctx.remote_address, Box.box(peer_ctx), nil, hostname, 0, nil, 0, nil, 0)
|
|
||||||
@conn = conn
|
|
||||||
end
|
|
||||||
|
|
||||||
private def host_header
|
|
||||||
if (@tls && @port != 443) || (!@tls && @port != 80)
|
|
||||||
"#{@host}:#{@port}"
|
|
||||||
else
|
|
||||||
@host
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private def self.exec(string : String, tls = nil)
|
|
||||||
uri = URI.parse(string)
|
|
||||||
|
|
||||||
unless uri.scheme && uri.host
|
|
||||||
# Assume http if no scheme and host are specified
|
|
||||||
uri = URI.parse("http://#{string}")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
exec(uri, tls) do |client, path|
|
private def send_request(request)
|
||||||
yield client, path
|
set_defaults request
|
||||||
|
run_before_request_callbacks(request)
|
||||||
|
|
||||||
|
spawn run_engine if !@engine_open
|
||||||
|
|
||||||
|
reader, writer = IO::ChanneledPipe.new
|
||||||
|
# See https://github.com/crystal-lang/crystal/blob/0.32.0/src/openssl/ssl/context.cr#L126
|
||||||
|
@stream_ctx = StreamCtx.new(request, writer)
|
||||||
|
@stream_channel.send @stream_ctx
|
||||||
|
reader
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
protected def self.tls_flag(uri, context : OpenSSL::SSL::Context::Client?)
|
private def set_defaults(request)
|
||||||
scheme = uri.scheme
|
request.headers[":method"] ||= request.method
|
||||||
case {scheme, context}
|
request.headers[":scheme"] ||= "https"
|
||||||
when {nil, _}
|
request.headers[":path"] ||= request.resource
|
||||||
raise ArgumentError.new("Missing scheme: #{uri}")
|
request.headers[":authority"] ||= host_header
|
||||||
when {"http", nil}
|
request.headers["user-agent"] ||= "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36"
|
||||||
false
|
|
||||||
when {"http", OpenSSL::SSL::Context::Client}
|
|
||||||
raise ArgumentError.new("TLS context given for HTTP URI")
|
|
||||||
when {"https", nil}
|
|
||||||
true
|
|
||||||
when {"https", OpenSSL::SSL::Context::Client}
|
|
||||||
context
|
|
||||||
else
|
|
||||||
raise ArgumentError.new "Unsupported scheme: #{scheme}"
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
protected def self.validate_host(uri)
|
private def self.default_one_shot_headers(headers)
|
||||||
host = uri.host
|
headers ||= HTTP::Headers.new
|
||||||
return host if host && !host.empty?
|
headers["connection"] ||= "close"
|
||||||
|
headers
|
||||||
|
end
|
||||||
|
|
||||||
raise ArgumentError.new %(Request URI must have host (URI is: #{uri}))
|
private def run_before_request_callbacks(request)
|
||||||
end
|
@before_request.try &.each &.call(request)
|
||||||
|
end
|
||||||
|
|
||||||
private def self.exec(uri : URI, tls = nil)
|
def exec(method : String, path, headers : HTTP::Headers? = nil, body : BodyType = nil) : HTTP::Client::Response
|
||||||
tls = tls_flag(uri, tls)
|
exec new_request method, path, headers, body
|
||||||
host = validate_host(uri)
|
end
|
||||||
|
|
||||||
port = uri.port
|
def exec(method : String, path, headers : HTTP::Headers? = nil, body : BodyType = nil)
|
||||||
path = uri.full_path
|
exec(new_request(method, path, headers, body)) do |response|
|
||||||
user = uri.user
|
yield response
|
||||||
password = uri.password
|
end
|
||||||
|
end
|
||||||
HTTP::Client.new(host, port, tls) do |client|
|
|
||||||
if user && password
|
def self.exec(method, url : String | URI, headers : HTTP::Headers? = nil, body : BodyType = nil, tls = nil) : HTTP::Client::Response
|
||||||
client.basic_auth(user, password)
|
headers = default_one_shot_headers(headers)
|
||||||
|
exec(url, tls) do |client, path|
|
||||||
|
client.exec method, path, headers, body
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.exec(method, url : String | URI, headers : HTTP::Headers? = nil, body : BodyType = nil, tls = nil)
|
||||||
|
headers = default_one_shot_headers(headers)
|
||||||
|
exec(url, tls) do |client, path|
|
||||||
|
client.exec(method, path, headers, body) do |response|
|
||||||
|
yield response
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def close
|
||||||
|
@stream_channel.send nil
|
||||||
|
Fiber.yield
|
||||||
|
@socket.try &.close
|
||||||
|
@socket = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
private def new_request(method, path, headers, body : BodyType)
|
||||||
|
HTTP::Request.new(method, path, headers, body)
|
||||||
|
end
|
||||||
|
|
||||||
|
private def host_header
|
||||||
|
if (@tls && @port != 443) || (!@tls && @port != 80)
|
||||||
|
"#{@host}:#{@port}"
|
||||||
|
else
|
||||||
|
@host
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private def self.exec(string : String, tls = nil)
|
||||||
|
uri = URI.parse(string)
|
||||||
|
|
||||||
|
unless uri.scheme && uri.host
|
||||||
|
# Assume http if no scheme and host are specified
|
||||||
|
uri = URI.parse("http://#{string}")
|
||||||
|
end
|
||||||
|
|
||||||
|
exec(uri, tls) do |client, path|
|
||||||
|
yield client, path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
protected def self.tls_flag(uri, context : OpenSSL::SSL::Context::Client?)
|
||||||
|
scheme = uri.scheme
|
||||||
|
case {scheme, context}
|
||||||
|
when {nil, _}
|
||||||
|
raise ArgumentError.new("Missing scheme: #{uri}")
|
||||||
|
when {"http", nil}
|
||||||
|
false
|
||||||
|
when {"http", OpenSSL::SSL::Context::Client}
|
||||||
|
raise ArgumentError.new("TLS context given for HTTP URI")
|
||||||
|
when {"https", nil}
|
||||||
|
true
|
||||||
|
when {"https", OpenSSL::SSL::Context::Client}
|
||||||
|
context
|
||||||
|
else
|
||||||
|
raise ArgumentError.new "Unsupported scheme: #{scheme}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
protected def self.validate_host(uri)
|
||||||
|
host = uri.host
|
||||||
|
return host if host && !host.empty?
|
||||||
|
|
||||||
|
raise ArgumentError.new %(Request URI must have host (URI is: #{uri}))
|
||||||
|
end
|
||||||
|
|
||||||
|
private def self.exec(uri : URI, tls = nil)
|
||||||
|
tls = tls_flag(uri, tls)
|
||||||
|
host = validate_host(uri)
|
||||||
|
|
||||||
|
port = uri.port
|
||||||
|
path = uri.full_path
|
||||||
|
user = uri.user
|
||||||
|
password = uri.password
|
||||||
|
|
||||||
|
HTTP::Client.new(host, port, tls) do |client|
|
||||||
|
if user && password
|
||||||
|
client.basic_auth(user, password)
|
||||||
|
end
|
||||||
|
yield client, path
|
||||||
end
|
end
|
||||||
yield client, path
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
BIN
src/lsquic/ext/libcrypto.a
Normal file
BIN
src/lsquic/ext/libcrypto.a
Normal file
Binary file not shown.
BIN
src/lsquic/ext/liblsquic.a
Normal file
BIN
src/lsquic/ext/liblsquic.a
Normal file
Binary file not shown.
BIN
src/lsquic/ext/libssl.a
Normal file
BIN
src/lsquic/ext/libssl.a
Normal file
Binary file not shown.
|
@ -67,10 +67,8 @@ lib LibLsquic
|
||||||
id : Uint64T
|
id : Uint64T
|
||||||
end
|
end
|
||||||
|
|
||||||
alias X__Uint8T = UInt8
|
alias Uint8T = UInt8
|
||||||
alias Uint8T = X__Uint8T
|
alias Uint64T = LibC::ULong
|
||||||
alias X__Uint64T = LibC::ULong
|
|
||||||
alias Uint64T = X__Uint64T
|
|
||||||
alias Engine = Void
|
alias Engine = Void
|
||||||
alias Conn = Void
|
alias Conn = Void
|
||||||
alias ConnCtx = Void
|
alias ConnCtx = Void
|
||||||
|
@ -108,9 +106,7 @@ lib LibLsquic
|
||||||
end
|
end
|
||||||
|
|
||||||
type ConnT = Void*
|
type ConnT = Void*
|
||||||
type ConnCtxT = Void*
|
|
||||||
type StreamT = Void*
|
type StreamT = Void*
|
||||||
type StreamCtxT = Void*
|
|
||||||
enum HskStatus
|
enum HskStatus
|
||||||
LsqHskFail = 0
|
LsqHskFail = 0
|
||||||
LsqHskOk = 1
|
LsqHskOk = 1
|
||||||
|
@ -165,8 +161,7 @@ lib LibLsquic
|
||||||
es_cc_algo : LibC::UInt
|
es_cc_algo : LibC::UInt
|
||||||
end
|
end
|
||||||
|
|
||||||
alias X__Uint32T = LibC::UInt
|
alias Uint32T = LibC::UInt
|
||||||
alias Uint32T = X__Uint32T
|
|
||||||
fun engine_init_settings = lsquic_engine_init_settings(x0 : EngineSettings*, engine_flags : LibC::UInt)
|
fun engine_init_settings = lsquic_engine_init_settings(x0 : EngineSettings*, engine_flags : LibC::UInt)
|
||||||
fun engine_check_settings = lsquic_engine_check_settings(settings : EngineSettings*, engine_flags : LibC::UInt, err_buf : LibC::Char*, err_buf_sz : LibC::SizeT) : LibC::Int
|
fun engine_check_settings = lsquic_engine_check_settings(settings : EngineSettings*, engine_flags : LibC::UInt, err_buf : LibC::Char*, err_buf_sz : LibC::SizeT) : LibC::Int
|
||||||
|
|
||||||
|
@ -185,8 +180,7 @@ lib LibLsquic
|
||||||
shi_lookup : (Void*, Void*, LibC::UInt, Void**, LibC::UInt* -> LibC::Int)
|
shi_lookup : (Void*, Void*, LibC::UInt, Void**, LibC::UInt* -> LibC::Int)
|
||||||
end
|
end
|
||||||
|
|
||||||
alias X__TimeT = LibC::Long
|
alias TimeT = LibC::Long
|
||||||
alias TimeT = X__TimeT
|
|
||||||
|
|
||||||
struct PackoutMemIf
|
struct PackoutMemIf
|
||||||
pmi_allocate : (Void*, Void*, LibC::UShort, LibC::Char -> Void*)
|
pmi_allocate : (Void*, Void*, LibC::UShort, LibC::Char -> Void*)
|
||||||
|
@ -271,15 +265,14 @@ lib LibLsquic
|
||||||
fun engine_send_unsent_packets = lsquic_engine_send_unsent_packets(engine : EngineT)
|
fun engine_send_unsent_packets = lsquic_engine_send_unsent_packets(engine : EngineT)
|
||||||
fun engine_destroy = lsquic_engine_destroy(x0 : EngineT)
|
fun engine_destroy = lsquic_engine_destroy(x0 : EngineT)
|
||||||
fun conn_n_avail_streams = lsquic_conn_n_avail_streams(x0 : ConnT) : LibC::UInt
|
fun conn_n_avail_streams = lsquic_conn_n_avail_streams(x0 : ConnT) : LibC::UInt
|
||||||
fun conn_make_stream = lsquic_conn_make_stream(x0 : ConnT) : ConnCtxT
|
fun conn_make_stream = lsquic_conn_make_stream(x0 : ConnT) : Void*
|
||||||
fun conn_n_pending_streams = lsquic_conn_n_pending_streams(x0 : ConnT) : LibC::UInt
|
fun conn_n_pending_streams = lsquic_conn_n_pending_streams(x0 : ConnT) : LibC::UInt
|
||||||
fun conn_cancel_pending_streams = lsquic_conn_cancel_pending_streams(x0 : ConnT, n : LibC::UInt) : LibC::UInt
|
fun conn_cancel_pending_streams = lsquic_conn_cancel_pending_streams(x0 : ConnT, n : LibC::UInt) : LibC::UInt
|
||||||
fun conn_going_away = lsquic_conn_going_away(x0 : ConnT)
|
fun conn_going_away = lsquic_conn_going_away(x0 : ConnT)
|
||||||
fun conn_close = lsquic_conn_close(x0 : ConnT)
|
fun conn_close = lsquic_conn_close(x0 : ConnT)
|
||||||
fun stream_wantread = lsquic_stream_wantread(s : StreamT, is_want : LibC::Int) : LibC::Int
|
fun stream_wantread = lsquic_stream_wantread(s : StreamT, is_want : LibC::Int) : LibC::Int
|
||||||
fun stream_read = lsquic_stream_read(s : StreamT, buf : Void*, len : LibC::SizeT) : SsizeT
|
fun stream_read = lsquic_stream_read(s : StreamT, buf : Void*, len : LibC::SizeT) : SsizeT
|
||||||
alias X__SsizeT = LibC::Long
|
alias SsizeT = LibC::Long
|
||||||
alias SsizeT = X__SsizeT
|
|
||||||
fun stream_readv = lsquic_stream_readv(s : StreamT, x1 : Iovec*, iovcnt : LibC::Int) : SsizeT
|
fun stream_readv = lsquic_stream_readv(s : StreamT, x1 : Iovec*, iovcnt : LibC::Int) : SsizeT
|
||||||
fun stream_readf = lsquic_stream_readf(s : StreamT, readf : (Void*, UInt8*, LibC::SizeT, LibC::Int -> LibC::SizeT), ctx : Void*) : SsizeT
|
fun stream_readf = lsquic_stream_readf(s : StreamT, readf : (Void*, UInt8*, LibC::SizeT, LibC::Int -> LibC::SizeT), ctx : Void*) : SsizeT
|
||||||
fun stream_wantwrite = lsquic_stream_wantwrite(s : StreamT, is_want : LibC::Int) : LibC::Int
|
fun stream_wantwrite = lsquic_stream_wantwrite(s : StreamT, is_want : LibC::Int) : LibC::Int
|
||||||
|
@ -304,7 +297,7 @@ lib LibLsquic
|
||||||
fun conn_get_server_cert_chain = lsquic_conn_get_server_cert_chain(x0 : ConnT) : StackStX509*
|
fun conn_get_server_cert_chain = lsquic_conn_get_server_cert_chain(x0 : ConnT) : StackStX509*
|
||||||
fun stream_id = lsquic_stream_id(s : StreamT) : StreamIdT
|
fun stream_id = lsquic_stream_id(s : StreamT) : StreamIdT
|
||||||
alias StreamIdT = Uint64T
|
alias StreamIdT = Uint64T
|
||||||
fun stream_get_ctx = lsquic_stream_get_ctx(s : StreamT) : StreamCtxT
|
fun stream_get_ctx = lsquic_stream_get_ctx(s : StreamT) : Void*
|
||||||
fun stream_is_pushed = lsquic_stream_is_pushed(s : StreamT) : LibC::Int
|
fun stream_is_pushed = lsquic_stream_is_pushed(s : StreamT) : LibC::Int
|
||||||
fun stream_is_rejected = lsquic_stream_is_rejected(s : StreamT) : LibC::Int
|
fun stream_is_rejected = lsquic_stream_is_rejected(s : StreamT) : LibC::Int
|
||||||
fun stream_refuse_push = lsquic_stream_refuse_push(s : StreamT) : LibC::Int
|
fun stream_refuse_push = lsquic_stream_refuse_push(s : StreamT) : LibC::Int
|
||||||
|
@ -350,8 +343,8 @@ lib LibLsquic
|
||||||
fun engine_cooldown = lsquic_engine_cooldown(x0 : EngineT)
|
fun engine_cooldown = lsquic_engine_cooldown(x0 : EngineT)
|
||||||
fun hsk_getssl = lsquic_hsk_getssl(conn : ConnT) : SslSt*
|
fun hsk_getssl = lsquic_hsk_getssl(conn : ConnT) : SslSt*
|
||||||
alias SslSt = Void
|
alias SslSt = Void
|
||||||
fun conn_get_ctx = lsquic_conn_get_ctx(x0 : ConnT) : ConnCtxT
|
fun conn_get_ctx = lsquic_conn_get_ctx(x0 : ConnT) : Void*
|
||||||
fun conn_set_ctx = lsquic_conn_set_ctx(x0 : ConnT, x1 : ConnCtxT)
|
fun conn_set_ctx = lsquic_conn_set_ctx(x0 : ConnT, x1 : Void*)
|
||||||
fun conn_get_peer_ctx = lsquic_conn_get_peer_ctx(x0 : ConnT, local_sa : LibC::Sockaddr*) : Void*
|
fun conn_get_peer_ctx = lsquic_conn_get_peer_ctx(x0 : ConnT, local_sa : LibC::Sockaddr*) : Void*
|
||||||
fun conn_abort = lsquic_conn_abort(x0 : ConnT)
|
fun conn_abort = lsquic_conn_abort(x0 : ConnT)
|
||||||
fun get_alt_svc_versions = lsquic_get_alt_svc_versions(versions : LibC::UInt) : LibC::Char*
|
fun get_alt_svc_versions = lsquic_get_alt_svc_versions(versions : LibC::UInt) : LibC::Char*
|
||||||
|
|
|
@ -7,6 +7,9 @@ lib LibCrypto
|
||||||
fun sk_num = sk_num(x0 : Void*) : Int
|
fun sk_num = sk_num(x0 : Void*) : Int
|
||||||
fun sk_pop_free = sk_pop_free(st : Void*, callback : (Void*) ->)
|
fun sk_pop_free = sk_pop_free(st : Void*, callback : (Void*) ->)
|
||||||
fun sk_value = sk_value(x0 : Void*, x1 : Int) : Void*
|
fun sk_value = sk_value(x0 : Void*, x1 : Int) : Void*
|
||||||
|
|
||||||
|
fun openssl_add_all_algorithms = rand : LibC::Int
|
||||||
|
fun err_load_crypto_strings = rand : LibC::Int
|
||||||
end
|
end
|
||||||
|
|
||||||
@[Link(ldflags: "#{__DIR__}/ext/libssl.a")]
|
@[Link(ldflags: "#{__DIR__}/ext/libssl.a")]
|
||||||
|
@ -16,6 +19,12 @@ lib LibSSL
|
||||||
fun ssl_ctx_get_mode = SSL_CTX_get_mode(ctx : SSLContext) : ULong
|
fun ssl_ctx_get_mode = SSL_CTX_get_mode(ctx : SSLContext) : ULong
|
||||||
fun ssl_ctx_set_mode = SSL_CTX_set_mode(ctx : SSLContext, mode : ULong) : ULong
|
fun ssl_ctx_set_mode = SSL_CTX_set_mode(ctx : SSLContext, mode : ULong) : ULong
|
||||||
fun ssl_ctx_clear_mode = SSL_CTX_clear_mode(ctx : SSLContext, mode : ULong) : ULong
|
fun ssl_ctx_clear_mode = SSL_CTX_clear_mode(ctx : SSLContext, mode : ULong) : ULong
|
||||||
|
fun ssl_ctx_get_options = SSL_CTX_get_options(ctx : SSLContext) : ULong
|
||||||
|
fun ssl_ctx_set_options = SSL_CTX_set_options(ctx : SSLContext, larg : ULong) : ULong
|
||||||
|
fun ssl_ctx_clear_options = SSL_CTX_clear_options(ctx : SSLContext, larg : ULong) : ULong
|
||||||
|
|
||||||
|
fun ssl_library_init = rand : LibC::Int
|
||||||
|
fun ssl_load_error_strings = rand : LibC::Int
|
||||||
end
|
end
|
||||||
|
|
||||||
abstract class OpenSSL::SSL::Context
|
abstract class OpenSSL::SSL::Context
|
||||||
|
@ -40,6 +49,19 @@ abstract class OpenSSL::SSL::Context
|
||||||
def remove_modes(mode : OpenSSL::SSL::Modes)
|
def remove_modes(mode : OpenSSL::SSL::Modes)
|
||||||
OpenSSL::SSL::Modes.new LibSSL.ssl_ctx_clear_mode(@handle, mode)
|
OpenSSL::SSL::Modes.new LibSSL.ssl_ctx_clear_mode(@handle, mode)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Returns the current options set on the TLS context.
|
||||||
|
def options
|
||||||
|
OpenSSL::SSL::Options.new LibSSL.ssl_ctx_get_options(@handle)
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_options(options : OpenSSL::SSL::Options)
|
||||||
|
OpenSSL::SSL::Options.new LibSSL.ssl_ctx_set_options(@handle, options)
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_options(options : OpenSSL::SSL::Options)
|
||||||
|
OpenSSL::SSL::Options.new LibSSL.ssl_ctx_clear_options(@handle, options)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
struct OpenSSL::BIO
|
struct OpenSSL::BIO
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue