Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
@@ -0,0 +1 @@
{"$comment":"This file only protects against accidental modifications. It is not a security mechanism and does not protect against malicious changes.","files":{".cargo_vcs_info.json":"3b7542197a21da69c91349e9bfdfbed0e26be300af3cd931f4595756dbc4178a",".github/workflows/ci.yml":"cc5561abaa5c1058aef9666c76cdf75023d53efa3c5135e242757038da67eba4","CHANGES.md":"d176b15b5b8b458850507f3925cf995670cfc7e48d63a16d391f6e1da1ccf757","Cargo.toml":"2b73c4bab031d2633131cdf80a25cd252e804f60cd8f50e795b1b982db6ca2dc","Cargo.toml.orig":"d5bfa44ca28e78976fa55844fc3744bc25a95428063e74ee745c5e73a8f55c63","LICENSE.txt":"2c9064d970934e10f8fed27aa99752b13000a4d4bb745baf92ef2ee474c7d3a1","README.md":"c8381f7bbb2b1e08dae7574b61a4843ff5acad8580a9ce13e455a75084418be7","src/lib.rs":"d51d22e8509a6e8d9b01c4ce53555b9ed33b49c97d0f1d7a7ff25dd674bb1d2d"},"package":"8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9"}
@@ -0,0 +1,5 @@
{
"git": {
"sha1": "5da7789b73de7494bb34af846c8a051597fbe26b"
}
}
@@ -0,0 +1,56 @@
name: CI
on:
push:
branches: [ master ]
pull_request:
env:
CARGO_TERM_COLOR: always
jobs:
style:
name: Check Style
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Install rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: rustfmt
default: true
- name: cargo fmt -- --check
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: rustfmt
default: true
- name: Test
uses: actions-rs/cargo@v1
with:
command: test
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: rustfmt
default: true
- name: cargo audit
uses: actions-rs/audit-check@v1.2.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,6 @@
ChangeLog
=========
0.1.1
-----
- Initial release
+27
View File
@@ -0,0 +1,27 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies
#
# If you believe there's an error in this file please file an
# issue against the rust-lang/cargo repository. If you're
# editing this file be aware that the upstream Cargo.toml
# will likely look very different (and much more reasonable)
[package]
edition = "2018"
name = "forwarded-header-value"
version = "0.1.1"
authors = ["James Brown <jbrown@easypost.com>"]
description = "Parser for values from the Forwarded header (RFC 7239)"
readme = "README.md"
keywords = ["http"]
license = "ISC"
repository = "https://github.com/EasyPost/rust-forwarded-header-value"
[dependencies.nonempty]
version = "0.7"
[dependencies.thiserror]
version = "1"
+16
View File
@@ -0,0 +1,16 @@
[package]
name = "forwarded-header-value"
version = "0.1.1"
edition = "2018"
authors = ["James Brown <jbrown@easypost.com>"]
description = "Parser for values from the Forwarded header (RFC 7239)"
readme = "README.md"
repository = "https://github.com/EasyPost/rust-forwarded-header-value"
license = "ISC"
keywords = ["http"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
thiserror = "1"
nonempty = "0.7"
+13
View File
@@ -0,0 +1,13 @@
Copyright (c) 2021, EasyPost <oss@easypost.com>
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.
+7
View File
@@ -0,0 +1,7 @@
This project implements a small parser for the HTTP Forwarded header, as defined in [RFC 7239 Section 4](https://www.rfc-editor.org/rfc/rfc7239#section-4).
[![CI](https://github.com/EasyPost/rust-forwarded-header-value/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/EasyPost/rust-forwarded-header-value/actions/workflows/ci.yml)
[![crates.io](https://img.shields.io/crates/v/forwarded-header-value.svg)](https://crates.io/crates/forwarded-header-value)
[![docs.rs](https://docs.rs/forwarded-header-value/badge.svg)](https://docs.rs/forwarded-header-value/)
This work is licensed under the ISC License, a copy of which can be found at [LICENSE.txt](LICENSE.txt).
+503
View File
@@ -0,0 +1,503 @@
//! This module implements parsing for the Forwarded header as defined by
//! RFC 7239 and RFC 7230. You should generally interact with the [`ForwardedHeaderValue`] struct.
#![warn(missing_docs)]
#![forbid(unsafe_code)]
use std::net::{IpAddr, SocketAddr};
use std::str::FromStr;
use nonempty::NonEmpty;
use thiserror::Error;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
/// HTTP protocol from the remote host
pub enum Protocol {
/// HTTP
Http,
/// HTTPS
Https,
}
impl FromStr for Protocol {
type Err = ForwardedHeaderValueParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"http" => Ok(Protocol::Http),
"https" => Ok(Protocol::Https),
_ => Err(ForwardedHeaderValueParseError::InvalidProtocol),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[allow(missing_docs)]
/// Remote identifier. This can be an IP:port pair, a bare IP, an underscore-prefixed
/// "obfuscated string", or unknown.
pub enum Identifier {
SocketAddr(SocketAddr),
IpAddr(IpAddr),
String(String),
Unknown,
}
impl Identifier {
#[cfg(test)]
fn for_string<T: ToString>(t: T) -> Self {
Identifier::String(t.to_string())
}
/// Return the IP address for this identifier, if there is one. This will extract it from the
/// SocketAddr if necessary
pub fn ip(&self) -> Option<IpAddr> {
match self {
Identifier::SocketAddr(sa) => Some(sa.ip()),
Identifier::IpAddr(ip) => Some(*ip),
_ => None,
}
}
}
impl FromStr for Identifier {
type Err = ForwardedHeaderValueParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim().trim_matches('"').trim_matches('\'');
if s == "unknown" {
return Ok(Identifier::Unknown);
}
if let Ok(socket_addr) = s.parse::<SocketAddr>() {
Ok(Identifier::SocketAddr(socket_addr))
} else if let Ok(ip_addr) = s.parse::<IpAddr>() {
Ok(Identifier::IpAddr(ip_addr))
} else if s.starts_with('[') && s.ends_with(']') {
if let Ok(ip_addr) = s[1..(s.len() - 1)].parse::<IpAddr>() {
Ok(Identifier::IpAddr(ip_addr))
} else {
Err(ForwardedHeaderValueParseError::InvalidAddress)
}
} else if s.starts_with('_') {
Ok(Identifier::String(s.to_string()))
} else {
Err(ForwardedHeaderValueParseError::InvalidObfuscatedNode(
s.to_string(),
))
}
}
}
#[derive(Debug, Default)]
/// A single forwarded-for line; there may be a sequence of these in a Forwarded header.
///
/// Any parts not specified will be None
#[allow(missing_docs)]
pub struct ForwardedStanza {
pub forwarded_by: Option<Identifier>,
pub forwarded_for: Option<Identifier>,
pub forwarded_host: Option<String>,
pub forwarded_proto: Option<Protocol>,
}
impl ForwardedStanza {
/// Get the forwarded-for IP, if one is present
pub fn forwarded_for_ip(&self) -> Option<IpAddr> {
self.forwarded_for.as_ref().and_then(|fa| fa.ip())
}
/// Get the forwarded-by IP, if one is present
pub fn forwarded_by_ip(&self) -> Option<IpAddr> {
self.forwarded_by.as_ref().and_then(|fa| fa.ip())
}
}
impl FromStr for ForwardedStanza {
type Err = ForwardedHeaderValueParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut rv = ForwardedStanza::default();
let s = s.trim();
for part in s.split(';') {
let part = part.trim();
if part.is_empty() {
continue;
}
if let Some((key, value)) = part.split_once('=') {
match key.to_ascii_lowercase().as_str() {
"by" => rv.forwarded_by = Some(value.parse()?),
"for" => rv.forwarded_for = Some(value.parse()?),
"host" => {
rv.forwarded_host = {
if value.starts_with('"') && value.ends_with('"') {
Some(
value[1..(value.len() - 1)]
.replace("\\\"", "\"")
.replace("\\\\", "\\"),
)
} else {
Some(value.to_string())
}
}
}
"proto" => rv.forwarded_proto = Some(value.parse()?),
_other => continue,
}
} else {
return Err(ForwardedHeaderValueParseError::InvalidPart(part.to_owned()));
}
}
Ok(rv)
}
}
/// Iterator over stanzas in a ForwardedHeaderValue
pub struct ForwardedHeaderValueIterator<'a> {
head: Option<&'a ForwardedStanza>,
tail: &'a [ForwardedStanza],
}
impl<'a> Iterator for ForwardedHeaderValueIterator<'a> {
type Item = &'a ForwardedStanza;
fn next(&mut self) -> Option<Self::Item> {
if let Some(head) = self.head.take() {
Some(head)
} else if let Some((first, rest)) = self.tail.split_first() {
self.tail = rest;
Some(first)
} else {
None
}
}
}
impl<'a> DoubleEndedIterator for ForwardedHeaderValueIterator<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
if let Some((last, rest)) = self.tail.split_last() {
self.tail = rest;
Some(last)
} else if let Some(head) = self.head.take() {
Some(head)
} else {
None
}
}
}
impl<'a> ExactSizeIterator for ForwardedHeaderValueIterator<'a> {
fn len(&self) -> usize {
self.tail.len() + if self.head.is_some() { 1 } else { 0 }
}
}
impl<'a> core::iter::FusedIterator for ForwardedHeaderValueIterator<'a> {}
fn values_from_header(header_value: &str) -> impl Iterator<Item = &str> {
header_value.trim().split(',').filter_map(|i| {
let trimmed = i.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed)
}
})
}
/// This structure represents the contents of the Forwarded header
///
/// It should contain one or more ForwardedStanzas
#[derive(Debug)]
pub struct ForwardedHeaderValue {
values: NonEmpty<ForwardedStanza>,
}
impl ForwardedHeaderValue {
/// The number of valid stanzas in this value
pub fn len(&self) -> usize {
self.values.len()
}
/// This can never be empty
pub fn is_empty(&self) -> bool {
false
}
/// Get the value farthest from this system (the left-most value)
///
/// This may represent the remote client
pub fn remotest(&self) -> &ForwardedStanza {
self.values.first()
}
/// Get the value farthest from this system (the left-most value), consuming this object
///
/// This may represent the remote client
pub fn into_remotest(mut self) -> ForwardedStanza {
if !self.values.tail.is_empty() {
self.values.tail.pop().unwrap()
} else {
self.values.head
}
}
/// Get the value closest to this system (the right-most value).
///
/// This is typically the only trusted value in a well-architected system.
pub fn proximate(&self) -> &ForwardedStanza {
self.values.last()
}
/// Get the value closest to this system (the right-most value), consuming this object
///
/// This is typically the only trusted value in a well-architected system.
pub fn into_proximate(mut self) -> ForwardedStanza {
if !self.values.tail.is_empty() {
self.values.tail.pop().unwrap()
} else {
self.values.head
}
}
/// Iterate through all ForwardedStanzas
pub fn iter(&self) -> ForwardedHeaderValueIterator {
ForwardedHeaderValueIterator {
head: Some(&self.values.head),
tail: &self.values.tail,
}
}
/// Return the rightmost non-empty forwarded-for IP, if one is present
pub fn proximate_forwarded_for_ip(&self) -> Option<IpAddr> {
self.iter().rev().find_map(|i| i.forwarded_for_ip())
}
/// Return the leftmost forwarded-for IP, if one is present
pub fn remotest_forwarded_for_ip(&self) -> Option<IpAddr> {
self.iter().find_map(|i| i.forwarded_for_ip())
}
/// Parse the value from a Forwarded header into this structure
/// ### Example
/// ```rust
/// # use forwarded_header_value::{ForwardedHeaderValue, ForwardedHeaderValueParseError};
/// # fn main() -> Result<(), ForwardedHeaderValueParseError> {
/// let input = "for=1.2.3.4;by=\"[::1]:1234\"";
/// let value = ForwardedHeaderValue::from_forwarded(input)?;
/// assert_eq!(value.len(), 1);
/// assert_eq!(value.remotest_forwarded_for_ip(), Some("1.2.3.4".parse()?));
/// # Ok(())
/// # }
/// ```
pub fn from_forwarded(header_value: &str) -> Result<Self, ForwardedHeaderValueParseError> {
values_from_header(header_value)
.map(|stanza| stanza.parse::<ForwardedStanza>())
.collect::<Result<Vec<_>, _>>()
.and_then(|v| {
NonEmpty::from_vec(v).ok_or(ForwardedHeaderValueParseError::HeaderIsEmpty)
})
.map(|v| ForwardedHeaderValue { values: v })
}
/// Parse the value from an X-Forwarded-For header into this structure
/// ### Example
/// ```rust
/// # use forwarded_header_value::{ForwardedHeaderValue, ForwardedHeaderValueParseError};
/// # fn main() -> Result<(), ForwardedHeaderValueParseError> {
/// let input = "1.2.3.4, 5.6.7.8";
/// let value = ForwardedHeaderValue::from_x_forwarded_for(input)?;
/// assert_eq!(value.len(), 2);
/// assert_eq!(value.remotest_forwarded_for_ip(), Some("1.2.3.4".parse()?));
/// assert_eq!(value.proximate_forwarded_for_ip(), Some("5.6.7.8".parse()?));
/// # Ok(())
/// # }
/// ```
pub fn from_x_forwarded_for(
header_value: &str,
) -> Result<Self, ForwardedHeaderValueParseError> {
values_from_header(header_value)
.map(|address| {
let a = address.parse::<IpAddr>()?;
Ok(ForwardedStanza {
forwarded_for: Some(Identifier::IpAddr(a)),
..Default::default()
})
})
.collect::<Result<Vec<_>, _>>()
.and_then(|v| {
NonEmpty::from_vec(v).ok_or(ForwardedHeaderValueParseError::HeaderIsEmpty)
})
.map(|v| ForwardedHeaderValue { values: v })
}
}
impl IntoIterator for ForwardedHeaderValue {
type Item = ForwardedStanza;
type IntoIter = std::iter::Chain<std::iter::Once<Self::Item>, std::vec::IntoIter<Self::Item>>;
fn into_iter(self) -> Self::IntoIter {
self.values.into_iter()
}
}
#[derive(Error, Debug)]
#[allow(missing_docs)]
/// Errors that can occur while parsing a ForwardedHeaderValue
pub enum ForwardedHeaderValueParseError {
#[error("Header is empty")]
HeaderIsEmpty,
#[error("Stanza contained illegal part {0}")]
InvalidPart(String),
#[error("Stanza specified an invalid protocol")]
InvalidProtocol,
#[error("Identifier specified an invalid or malformed IP address")]
InvalidAddress,
#[error("Identifier specified an invalid or malformed port")]
InvalidPort,
#[error("Identifier specified uses an obfuscated node ({0:?}) that is invalid")]
InvalidObfuscatedNode(String),
#[error("Identifier specified an invalid or malformed IP address")]
IpParseErr(#[from] std::net::AddrParseError),
}
impl FromStr for ForwardedHeaderValue {
type Err = ForwardedHeaderValueParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_forwarded(s)
}
}
#[cfg(test)]
mod tests {
use super::{ForwardedHeaderValue, Identifier, Protocol};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
#[test]
fn test_basic() {
let s: ForwardedHeaderValue =
"for=192.0.2.43;proto=https, for=198.51.100.17;by=\"[::1]:1234\";host=\"example.com\""
.parse()
.unwrap();
assert_eq!(s.len(), 2);
assert_eq!(
s.proximate().forwarded_for_ip(),
Some("198.51.100.17".parse().unwrap())
);
assert_eq!(
s.proximate().forwarded_by_ip(),
Some("::1".parse().unwrap())
);
assert_eq!(
s.proximate().forwarded_host,
Some(String::from("example.com")),
);
assert_eq!(
s.remotest().forwarded_for_ip(),
Some("192.0.2.43".parse().unwrap())
);
assert_eq!(s.remotest().forwarded_proto, Some(Protocol::Https));
}
#[test]
fn test_rfc_examples() {
let s = "for=\"_gazonk\"".parse::<ForwardedHeaderValue>().unwrap();
assert_eq!(
s.into_proximate().forwarded_for.unwrap(),
Identifier::for_string("_gazonk")
);
let s = "For=\"[2001:db8:cafe::17]:4711\""
.parse::<ForwardedHeaderValue>()
.unwrap();
assert_eq!(s.len(), 1);
assert_eq!(
s.into_proximate().forwarded_for.unwrap(),
Identifier::SocketAddr(SocketAddr::new(
IpAddr::V6(Ipv6Addr::new(
0x2001, 0xdb8, 0xcafe, 0x0, 0x0, 0x0, 0x0, 0x17
)),
4711
))
);
let s = "for=192.0.2.60;proto=http;by=203.0.113.43"
.parse::<ForwardedHeaderValue>()
.unwrap();
assert_eq!(s.len(), 1);
let proximate = s.into_proximate();
assert_eq!(
proximate.forwarded_for.unwrap(),
Identifier::IpAddr(IpAddr::V4(Ipv4Addr::new(192, 0, 2, 60)))
);
assert_eq!(proximate.forwarded_proto.unwrap(), Protocol::Http);
assert_eq!(
proximate.forwarded_by.unwrap(),
Identifier::IpAddr(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 43)))
);
assert_eq!(proximate.forwarded_host, None);
let s = ForwardedHeaderValue::from_forwarded(
"for=192.0.2.43,for=\"[2001:db8:cafe::17]\",for=unknown",
)
.unwrap();
assert_eq!(
s.proximate_forwarded_for_ip().unwrap(),
IpAddr::V6(Ipv6Addr::new(
0x2001, 0xdb8, 0xcafe, 0x0, 0x0, 0x0, 0x0, 0x17
))
);
assert_eq!(
s.remotest_forwarded_for_ip().unwrap(),
IpAddr::V4(Ipv4Addr::new(192, 0, 2, 43))
);
}
#[test]
fn test_garbage() {
let s =
ForwardedHeaderValue::from_forwarded("for=unknown, for=unknown, for=_poop").unwrap();
assert_eq!(s.remotest_forwarded_for_ip(), None);
assert_eq!(s.proximate_forwarded_for_ip(), None);
}
#[test]
fn test_weird_identifiers() {
let s: ForwardedHeaderValue = "for=unknown, for=_private, for=_secret, ".parse().unwrap();
assert_eq!(s.len(), 3);
assert_eq!(
vec![
Identifier::Unknown,
Identifier::for_string("_private"),
Identifier::for_string("_secret")
],
s.into_iter()
.map(|s| s.forwarded_for.unwrap())
.collect::<Vec<Identifier>>()
);
}
#[test]
fn test_iter_both_directions() {
let s = ForwardedHeaderValue::from_x_forwarded_for("0.0.0.1, 0.0.0.2, 0.0.0.3").unwrap();
let forward = s
.iter()
.map(|s| {
if let Some(IpAddr::V4(i)) = s.forwarded_for_ip() {
i.octets()[3]
} else {
panic!("bad forward")
}
})
.collect::<Vec<_>>();
assert_eq!(forward, vec![1u8, 2u8, 3u8]);
let reverse = s
.iter()
.rev()
.map(|s| {
if let Some(IpAddr::V4(i)) = s.forwarded_for_ip() {
i.octets()[3]
} else {
panic!("bad forward")
}
})
.collect::<Vec<_>>();
assert_eq!(reverse, vec![3u8, 2u8, 1u8]);
}
}