updates and warp 0.4

This commit is contained in:
Robin Appelman 2026-07-13 17:39:45 +02:00
commit 9a3991d243
9 changed files with 393 additions and 813 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

17
.github/workflows/ci.yaml vendored Normal file
View file

@ -0,0 +1,17 @@
name: "CI"
on:
pull_request:
push:
jobs:
checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v26
- uses: icewind1991/attic-action@v1
with:
name: ci
instance: https://cache.icewind.me
authToken: "${{ secrets.ATTIC_TOKEN }}"
- run: nix flake check --keep-going

View file

@ -1,33 +0,0 @@
on: [push, pull_request]
name: CI
jobs:
check:
name: Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- uses: Swatinem/rust-cache@v1
- uses: actions-rs/cargo@v1
with:
command: check
test:
runs-on: ubuntu-latest
name: Tests
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
- uses: Swatinem/rust-cache@v1
- uses: actions-rs/cargo@v1
with:
command: test

5
.gitignore vendored
View file

@ -1 +1,4 @@
/target .idea
target
result
.direnv

1010
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,19 +1,22 @@
[package] [package]
name = "warp-real-ip" name = "warp-real-ip"
description = "Warp filter to get the \"real ip\" of the remote client" description = "Warp filter to get the \"real ip\" of the remote client"
version = "0.2.0" version = "0.3.0"
authors = ["Robin Appelman <robin@icewind.nl>"] authors = ["Robin Appelman <robin@icewind.nl>"]
edition = "2018" edition = "2024"
keywords = ["warp"] keywords = ["warp"]
categories = ["web-programming::http-server"] categories = ["web-programming::http-server"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
repository = "https://github.com/icewind1991/warp-real-ip" repository = "https://github.com/icewind1991/warp-real-ip"
documentation = "https://docs.rs/warp-real-ip" documentation = "https://docs.rs/warp-real-ip"
rust-version = "1.85.0"
[dependencies] [dependencies]
warp = { version = "0.3" } warp = { version = "0.4" }
rfc7239 = "0.1" rfc7239 = "0.1.3"
ipnetwork = "0.18" ipnetwork = "0.21"
comma-separated = "0.1.0"
[dev-dependencies] [dev-dependencies]
tokio = { version = "1.0", features = ["macros"] } tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
warp = { version = "0.4", features = ["test"] }

107
flake.lock generated Normal file
View file

@ -0,0 +1,107 @@
{
"nodes": {
"crane": {
"locked": {
"lastModified": 1780099841,
"narHash": "sha256-EVZd2RsbpreRUDSi9rBwPY+ZxoyMaiEBbZxxhljbaS4=",
"owner": "ipetkov",
"repo": "crane",
"rev": "0532eb17955225173906d671fb36306bdeb1e2dc",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"flakelight": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1783953866,
"narHash": "sha256-S2qmQTzeI270WnguYV6R0vXDgtekjjCB7HQI9QiqDGI=",
"owner": "nix-community",
"repo": "flakelight",
"rev": "c9219d15f7d74ba690cba3b613f4ef2a08c16840",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "flakelight",
"type": "github"
}
},
"mill-scale": {
"inputs": {
"crane": "crane",
"flakelight": [
"flakelight"
],
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1780231986,
"narHash": "sha256-OyafczPtzE0Xa2zl3j/KvV2+ZVYGhYQHt0MOVWtDXlY=",
"ref": "refs/heads/main",
"rev": "f5cbda29b945df03256bf63c22fa4cd5fa429e67",
"revCount": 72,
"type": "git",
"url": "https://codeberg.org/icewind/mill-scale.git"
},
"original": {
"type": "git",
"url": "https://codeberg.org/icewind/mill-scale.git"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1783856661,
"narHash": "sha256-ZGP04e+Q6WyQJGA9ZvI5CL6+heGQldbAG9U1T9NGvmU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "569d578509928497eddc3fdbf94a799027050be4",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-26.05",
"type": "indirect"
}
},
"root": {
"inputs": {
"flakelight": "flakelight",
"mill-scale": "mill-scale",
"nixpkgs": "nixpkgs"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"mill-scale",
"flakelight",
"nixpkgs"
]
},
"locked": {
"lastModified": 1780197589,
"narHash": "sha256-FVCr2Ij/jKf59a4LW481eeOF6rJRreOBrVgW/aUBTrw=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "21632e942d89bf1cce4e5a63d7e58a215a0cbfcc",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

14
flake.nix Normal file
View file

@ -0,0 +1,14 @@
{
inputs = {
nixpkgs.url = "nixpkgs/nixos-26.05";
flakelight = {
url = "github:nix-community/flakelight";
inputs.nixpkgs.follows = "nixpkgs";
};
mill-scale = {
url = "git+https://codeberg.org/icewind/mill-scale.git";
inputs.flakelight.follows = "flakelight";
};
};
outputs = {mill-scale, ...}: mill-scale ./. {};
}

View file

@ -1,3 +1,4 @@
use comma_separated::CommaSeparatedIterator;
use ipnetwork::IpNetwork; use ipnetwork::IpNetwork;
use rfc7239::{parse, Forwarded, NodeIdentifier, NodeName}; use rfc7239::{parse, Forwarded, NodeIdentifier, NodeName};
use std::borrow::Cow; use std::borrow::Cow;
@ -117,106 +118,12 @@ pub fn get_forwarded_for() -> impl Filter<Extract = (Vec<IpAddr>,), Error = Infa
.unify() .unify()
} }
#[derive(Copy, Clone)]
enum CommaSeparatedIteratorState {
/// Start of string or after a ',' (including whitespace)
Default,
/// Inside a double quote
Quoted,
/// After escape character inside quote
QuotedPair,
/// Non quoted part
Token,
/// After closing double quote
PostAmbleForQuoted,
}
struct CommaSeparatedIterator<'a> {
/// target
target: &'a str,
/// iterator
char_indices: std::str::CharIndices<'a>,
/// current scanner state
state: CommaSeparatedIteratorState,
/// start position of the last token found
s: usize,
}
impl<'a> CommaSeparatedIterator<'a> {
pub fn new(target: &'a str) -> Self {
Self {
target,
char_indices: target.char_indices(),
state: CommaSeparatedIteratorState::Default,
s: 0,
}
}
}
impl<'a> Iterator for CommaSeparatedIterator<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
for (i, c) in &mut self.char_indices {
let (next, next_state) = match (self.state, c) {
(CommaSeparatedIteratorState::Default, '"') => {
self.s = i;
(None, CommaSeparatedIteratorState::Quoted)
}
(CommaSeparatedIteratorState::Default, ' ' | '\t') => {
(None, CommaSeparatedIteratorState::Default)
}
(CommaSeparatedIteratorState::Default, ',') => (
Some(Some(&self.target[i..i])),
CommaSeparatedIteratorState::Default,
),
(CommaSeparatedIteratorState::Default, _) => {
self.s = i;
(None, CommaSeparatedIteratorState::Token)
}
(CommaSeparatedIteratorState::Quoted, '"') => (
Some(Some(&self.target[self.s..i + 1])),
CommaSeparatedIteratorState::PostAmbleForQuoted,
),
(CommaSeparatedIteratorState::Quoted, '\\') => {
(None, CommaSeparatedIteratorState::QuotedPair)
}
(CommaSeparatedIteratorState::QuotedPair, _) => {
(None, CommaSeparatedIteratorState::Quoted)
}
(CommaSeparatedIteratorState::Token, ',') => (
Some(Some(&self.target[self.s..i])),
CommaSeparatedIteratorState::Default,
),
(CommaSeparatedIteratorState::PostAmbleForQuoted, ',') => {
(None, CommaSeparatedIteratorState::Default)
}
(current_state, _) => (None, current_state),
};
self.state = next_state;
if let Some(next) = next {
return next;
}
}
match self.state {
CommaSeparatedIteratorState::Default
| CommaSeparatedIteratorState::PostAmbleForQuoted => None,
CommaSeparatedIteratorState::Quoted
| CommaSeparatedIteratorState::QuotedPair
| CommaSeparatedIteratorState::Token => {
self.state = CommaSeparatedIteratorState::Default;
Some(&self.target[self.s..])
}
}
}
}
enum EscapeState { enum EscapeState {
Normal, Normal,
Escaped, Escaped,
} }
fn maybe_quoted(x: &str) -> Cow<str> { fn maybe_quoted(x: &str) -> Cow<'_, str> {
let mut i = x.chars(); let mut i = x.chars();
if i.next() == Some('"') { if i.next() == Some('"') {
let mut s = String::with_capacity(x.len()); let mut s = String::with_capacity(x.len());
@ -278,8 +185,10 @@ mod tests {
#[test] #[test]
fn test_comma_separated_iterator() { fn test_comma_separated_iterator() {
assert_eq!( assert_eq!(
vec!["abc", "def", "ghi", "jkl ", "mno", "pqr"], vec!["abc", "def", "ghi", "jkl", "mno", "pqr"],
CommaSeparatedIterator::new("abc,def, ghi,\tjkl , mno,\tpqr").collect::<Vec<&str>>() CommaSeparatedIterator::new("abc,def, ghi,\tjkl , mno,\tpqr")
.map(str::trim)
.collect::<Vec<&str>>()
); );
assert_eq!( assert_eq!(
vec![ vec![
@ -294,6 +203,7 @@ mod tests {
CommaSeparatedIterator::new( CommaSeparatedIterator::new(
"abc,\"def\", \"ghi\",\t\"jkl\" , \"mno\",\tpqr, \"abc, def\"" "abc,\"def\", \"ghi\",\t\"jkl\" , \"mno\",\tpqr, \"abc, def\""
) )
.map(str::trim)
.collect::<Vec<&str>>() .collect::<Vec<&str>>()
); );
} }