itsi-server 0.2.13 → 0.2.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Cargo.lock +2 -1
- data/ext/itsi_scheduler/Cargo.toml +1 -1
- data/ext/itsi_server/Cargo.toml +2 -1
- data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +46 -5
- data/lib/itsi/server/config/middleware/static_assets.rb +3 -2
- data/lib/itsi/server/config/middleware/static_response.rb +7 -6
- data/lib/itsi/server/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a2fec88d9b1fec3458e5d890cbd2b517c6c58cff123a2d28b26a00571edc456f
|
4
|
+
data.tar.gz: c460f900b09ba2602b8a0a9211d330e953fb8490497b24ae5390de9652780f42
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 34402433ec5e6d9f755a2ac41e5fb7b17d6be27d9d6a30d23fefdf595c5d9619b05ad3a00751f72923db579685daf537e336db83d772f6307a6f5046de056cd0
|
7
|
+
data.tar.gz: 46e05ae1dd1c4c0b9219fee35783a55294edf6d344afe938f041779ba8ab2ab98625418b3d631b6c1d1cde9ff9d346f6e215bbe15032756caabef25e279dc386
|
data/Cargo.lock
CHANGED
@@ -1644,7 +1644,7 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
|
1644
1644
|
|
1645
1645
|
[[package]]
|
1646
1646
|
name = "itsi-server"
|
1647
|
-
version = "0.2.
|
1647
|
+
version = "0.2.14"
|
1648
1648
|
dependencies = [
|
1649
1649
|
"argon2",
|
1650
1650
|
"async-channel",
|
@@ -1674,6 +1674,7 @@ dependencies = [
|
|
1674
1674
|
"jsonwebtoken",
|
1675
1675
|
"magnus",
|
1676
1676
|
"md5",
|
1677
|
+
"memchr",
|
1677
1678
|
"moka",
|
1678
1679
|
"nix",
|
1679
1680
|
"notify",
|
data/ext/itsi_server/Cargo.toml
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
[package]
|
2
2
|
name = "itsi-server"
|
3
|
-
version = "0.2.
|
3
|
+
version = "0.2.14"
|
4
4
|
edition = "2021"
|
5
5
|
authors = ["Wouter Coppieters <wc@pico.net.nz>"]
|
6
6
|
license = "MIT"
|
@@ -88,3 +88,4 @@ percent-encoding = "2.3.1"
|
|
88
88
|
sha-crypt = "0.5.0"
|
89
89
|
argon2 = "0.5.3"
|
90
90
|
core_affinity = "0.8.3"
|
91
|
+
memchr = "2.7.4"
|
@@ -1,4 +1,4 @@
|
|
1
|
-
use bytes::{Bytes, BytesMut};
|
1
|
+
use bytes::{Buf, Bytes, BytesMut};
|
2
2
|
use derive_more::Debug;
|
3
3
|
use futures::stream::{unfold, StreamExt};
|
4
4
|
use http::{
|
@@ -12,6 +12,7 @@ use hyper_util::rt::TokioIo;
|
|
12
12
|
use itsi_error::Result;
|
13
13
|
use itsi_tracing::error;
|
14
14
|
use magnus::error::Result as MagnusResult;
|
15
|
+
use memchr::{memchr, memchr_iter};
|
15
16
|
use parking_lot::RwLock;
|
16
17
|
use std::{
|
17
18
|
collections::HashMap,
|
@@ -345,13 +346,54 @@ impl ItsiHttpResponse {
|
|
345
346
|
let header_name: HeaderName = HeaderName::from_bytes(&name).map_err(|e| {
|
346
347
|
itsi_error::ItsiError::InvalidInput(format!("Invalid header name {:?}: {:?}", name, e))
|
347
348
|
})?;
|
348
|
-
let header_value = unsafe { HeaderValue::from_maybe_shared_unchecked(value) };
|
349
349
|
if let Some(ref mut resp) = *self.data.response.write() {
|
350
|
-
resp.headers_mut()
|
350
|
+
let headers_mut = resp.headers_mut();
|
351
|
+
self.insert_header(headers_mut, &header_name, value);
|
351
352
|
}
|
352
353
|
Ok(())
|
353
354
|
}
|
354
355
|
|
356
|
+
pub fn insert_header(
|
357
|
+
&self,
|
358
|
+
headers_mut: &mut HeaderMap,
|
359
|
+
header_name: &HeaderName,
|
360
|
+
value: Bytes,
|
361
|
+
) {
|
362
|
+
static MAX_SPLIT_HEADERS: usize = 100;
|
363
|
+
|
364
|
+
let mut start = 0usize;
|
365
|
+
let mut emitted = 0usize;
|
366
|
+
|
367
|
+
for idx in memchr_iter(b'\n', &value).chain(std::iter::once(value.len())) {
|
368
|
+
if idx == start {
|
369
|
+
start += 1;
|
370
|
+
continue;
|
371
|
+
}
|
372
|
+
|
373
|
+
let mut part = value.slice(start..idx);
|
374
|
+
if part.ends_with(b"\r") {
|
375
|
+
part.truncate(part.len() - 1);
|
376
|
+
}
|
377
|
+
if let Some(&(b' ' | b'\t')) = part.first() {
|
378
|
+
part.advance(1);
|
379
|
+
}
|
380
|
+
if memchr(0, &part).is_some() || part.iter().any(|&b| b < 0x20) {
|
381
|
+
warn!("stripped control char from header {:?}", header_name);
|
382
|
+
start = idx + 1;
|
383
|
+
continue;
|
384
|
+
}
|
385
|
+
|
386
|
+
emitted += 1;
|
387
|
+
if emitted > MAX_SPLIT_HEADERS {
|
388
|
+
break;
|
389
|
+
}
|
390
|
+
|
391
|
+
let hv = unsafe { HeaderValue::from_maybe_shared_unchecked(part) };
|
392
|
+
headers_mut.append(header_name, hv);
|
393
|
+
start = idx + 1;
|
394
|
+
}
|
395
|
+
}
|
396
|
+
|
355
397
|
pub fn add_headers(&self, headers: HashMap<Bytes, Vec<Bytes>>) -> MagnusResult<()> {
|
356
398
|
if let Some(ref mut resp) = *self.data.response.write() {
|
357
399
|
let headers_mut = resp.headers_mut();
|
@@ -363,8 +405,7 @@ impl ItsiHttpResponse {
|
|
363
405
|
))
|
364
406
|
})?;
|
365
407
|
for value in values {
|
366
|
-
|
367
|
-
headers_mut.append(&header_name, header_value);
|
408
|
+
self.insert_header(headers_mut, &header_name, value);
|
368
409
|
}
|
369
410
|
}
|
370
411
|
}
|
@@ -18,7 +18,8 @@ module Itsi
|
|
18
18
|
serve_hidden_files: ${11|true,false|}
|
19
19
|
SNIPPET
|
20
20
|
|
21
|
-
detail "Serves static files from a designated directory with options for auto indexing, in-memory caching,
|
21
|
+
detail "Serves static files from a designated directory with options for auto indexing, in-memory caching, "\
|
22
|
+
"and custom header support. Supports relative path rewriting and file range requests."
|
22
23
|
|
23
24
|
ErrorResponse = TypedStruct.new do
|
24
25
|
{
|
@@ -42,7 +43,7 @@ module Itsi
|
|
42
43
|
{
|
43
44
|
root_dir: (Type(String) & Required()).default("./"),
|
44
45
|
not_found_behavior: Or(
|
45
|
-
Enum([
|
46
|
+
Enum(%w[fallthrough index redirect internal_server_error]),
|
46
47
|
Type(IndexResponse),
|
47
48
|
Type(RedirectResponse),
|
48
49
|
Type(ErrorResponse)
|
@@ -2,20 +2,21 @@ module Itsi
|
|
2
2
|
class Server
|
3
3
|
module Config
|
4
4
|
class StaticResponse < Middleware
|
5
|
+
|
5
6
|
insert_text <<~SNIPPET
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
static_response \\
|
8
|
+
code: ${1|200,404,500|},
|
9
|
+
headers: [${2|%w[content-type text/plain],%w[cache-control max-age=60]|}],
|
10
|
+
body: ${3|"OK", "Not Found"|}
|
10
11
|
SNIPPET
|
11
12
|
|
12
13
|
detail "Immediately return a fixed HTTP response with code, headers, and body."
|
13
14
|
|
14
15
|
schema do
|
15
16
|
{
|
16
|
-
code:
|
17
|
+
code: (Type(Integer) & Required()),
|
17
18
|
headers: Array(Array(Type(String), Type(String))).default([]),
|
18
|
-
body:
|
19
|
+
body: Type(String).default("")
|
19
20
|
}
|
20
21
|
end
|
21
22
|
|
data/lib/itsi/server/version.rb
CHANGED