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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a511f7da334b54141cfacb97b1dc47f21c88dc2f3942843a39bb113f254c970b
4
- data.tar.gz: d6d0fab60a963cceffabaf545bb31d5b0400dcd966ae33ff280288da9dfb380c
3
+ metadata.gz: a2fec88d9b1fec3458e5d890cbd2b517c6c58cff123a2d28b26a00571edc456f
4
+ data.tar.gz: c460f900b09ba2602b8a0a9211d330e953fb8490497b24ae5390de9652780f42
5
5
  SHA512:
6
- metadata.gz: 180375c8dbd4841da13bca51a86d3f28e131cc8e0209935a64d3e8e474bd388b43b4582bc5abf44eea309fe93cd924795509af1122e46cd455081f9c50b03433
7
- data.tar.gz: c9e41ad7eeffc356b9522585ed1709d670419f573e0d1384704365e5ea48d28e5f2c7cd470cae9c4f438f787bc3619f865f19b8b1318da70504c5fa55d8e18ec
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.13"
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",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "itsi-scheduler"
3
- version = "0.2.13"
3
+ version = "0.2.14"
4
4
  edition = "2021"
5
5
  authors = ["Wouter Coppieters <wc@pico.net.nz>"]
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "itsi-server"
3
- version = "0.2.13"
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().append(header_name, header_value);
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
- let header_value = unsafe { HeaderValue::from_maybe_shared_unchecked(value) };
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, and custom header support. Supports relative path rewriting and file range requests."
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(["fallthrough", "index", "redirect", "internal_server_error"]),
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
- static_response \\
7
- code: ${1|200,404,500|}, \\
8
- headers: [${2|["Content-Type","text/plain"],["Cache-Control","max-age=60"]|}], \\
9
- body: ${3|"OK".bytes, "Not Found".bytes|}
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: (Type(Integer) & Required()),
17
+ code: (Type(Integer) & Required()),
17
18
  headers: Array(Array(Type(String), Type(String))).default([]),
18
- body: Type(String).default("")
19
+ body: Type(String).default("")
19
20
  }
20
21
  end
21
22
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Itsi
4
4
  class Server
5
- VERSION = "0.2.13"
5
+ VERSION = "0.2.14"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: itsi-server
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.13
4
+ version: 0.2.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wouter Coppieters