feat: openssl based hoppscotch-relay for request forwarding (#4442)

This commit is contained in:
Shreyas
2024-10-24 14:20:51 +05:30
committed by GitHub
parent deedf35bf0
commit f52219bb95
28 changed files with 2284 additions and 859 deletions

View File

@@ -0,0 +1,3 @@
source_url "https://raw.githubusercontent.com/cachix/devenv/95f329d49a8a5289d31e0982652f7058a189bfca/direnvrc" "sha256-d+8cBpDfDBj41inrADaJt+bDWhOktwslgoP5YiGJ1v0="
use devenv

10
packages/hoppscotch-relay/.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
/target
# Devenv
.devenv*
devenv.local.nix
# direnv
.direnv
# pre-commit
.pre-commit-config.yaml

644
packages/hoppscotch-relay/Cargo.lock generated Normal file
View File

@@ -0,0 +1,644 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "addr2line"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"gimli",
]
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
[[package]]
name = "anstyle-parse"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]]
name = "backtrace"
version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets",
]
[[package]]
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "bytes"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
[[package]]
name = "cc"
version = "1.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "colorchoice"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
[[package]]
name = "curl"
version = "0.4.47"
source = "git+https://github.com/CuriousCorrelation/curl-rust.git#1ec8079cf527b9cf47cc7a48c68b458affdae273"
dependencies = [
"curl-sys",
"libc",
"openssl-probe",
"openssl-sys",
"socket2",
]
[[package]]
name = "curl-sys"
version = "0.4.77+curl-8.10.1"
source = "git+https://github.com/CuriousCorrelation/curl-rust.git#1ec8079cf527b9cf47cc7a48c68b458affdae273"
dependencies = [
"cc",
"libc",
"libz-sys",
"openssl-sys",
"pkg-config",
"windows-sys",
]
[[package]]
name = "env_filter"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"humantime",
"log",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "futures-core"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-sink"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]]
name = "gimli"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "hoppscotch-relay"
version = "0.1.1"
dependencies = [
"curl",
"env_logger",
"http",
"log",
"openssl",
"openssl-sys",
"serde",
"serde_json",
"thiserror",
"tokio-util",
"url-escape",
]
[[package]]
name = "http"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "libc"
version = "0.2.161"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
[[package]]
name = "libz-sys"
version = "1.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "log"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "miniz_oxide"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
dependencies = [
"adler2",
]
[[package]]
name = "object"
version = "0.36.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "openssl"
version = "0.10.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-src"
version = "300.4.0+3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a709e02f2b4aca747929cca5ed248880847c650233cf8b8cdc48f40aaf4898a6"
dependencies = [
"cc",
]
[[package]]
name = "openssl-sys"
version = "0.9.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
dependencies = [
"cc",
"libc",
"openssl-src",
"pkg-config",
"vcpkg",
]
[[package]]
name = "percent-encoding"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pin-project-lite"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
[[package]]
name = "pkg-config"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
[[package]]
name = "proc-macro2"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "serde"
version = "1.0.213"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.213"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.132"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "socket2"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "syn"
version = "2.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio"
version = "1.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb"
dependencies = [
"backtrace",
"pin-project-lite",
]
[[package]]
name = "tokio-util"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
]
[[package]]
name = "unicode-ident"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "url-escape"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44e0ce4d1246d075ca5abec4b41d33e87a6054d08e2366b63205665e950db218"
dependencies = [
"percent-encoding",
]
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

View File

@@ -0,0 +1,21 @@
[package]
name = "hoppscotch-relay"
version = "0.1.1"
description = "A HTTP request-response relay used by Hoppscotch Desktop and Hoppscotch Agent for advanced request handling including custom headers, certificates, proxies, and local system integration."
authors = ["CuriousCorrelation"]
edition = "2021"
[dependencies]
curl = { git = "https://github.com/CuriousCorrelation/curl-rust.git", features = ["ntlm"] }
tokio-util = "0.7.12"
openssl = { version = "0.10.66", features = ["vendored"] }
# NOTE: This crate follows `openssl-sys` from https://github.com/CuriousCorrelation/curl-rust.git
# to avoid issues from version mismatch when compiling from source.
openssl-sys = { version = "0.9.64", features = ["vendored"] }
log = "0.4.22"
env_logger = "0.11.5"
thiserror = "1.0.64"
http = "1.1.0"
url-escape = "0.1.1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 CuriousCorrelation
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.

View File

@@ -0,0 +1,201 @@
# Hoppscotch Relay
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
A high-performance HTTP request-response relay used by Hoppscotch Desktop and Hoppscotch Agent for advanced request handling including CORS override, custom headers, certificates, proxies, and local system integration. It uses a custom fork of curl-rust with static OpenSSL builds for consistent SSL/TLS behavior across different platforms.
## Features
- 🚀 **Full HTTP Support**: Handle GET, POST, PUT, DELETE, and other HTTP methods
- 📦 **Multiple Body Types**:
- Raw text/JSON
- URL-encoded forms
- Multipart form data
- File uploads
- 🔒 **Security**:
- Client certificate authentication (PEM & PFX/PKCS#12)
- Custom root certificate bundles
- Certificate validation control
- 🌐 **Proxy Support**:
- HTTP/HTTPS proxy configuration
- Authentication support
- NTLM support
-**Performance**:
- Async design
- Request cancellation support
- Progress logs
- 📊 **Detailed Metrics**:
- Response timing
- Status tracking
- Header parsing
## Installation
Add this to your `Cargo.toml`:
```toml
[dependencies]
hoppscotch-relay = "0.1.1"
```
## Usage
### Basic Request
```rust
use hoppscotch_relay::{RequestWithMetadata, KeyValuePair};
use tokio_util::sync::CancellationToken;
// Create a basic GET request
let request = RequestWithMetadata::new(
1, // Request ID
"GET".to_string(), // Method
"https://api.example.com/data".to_string(), // Endpoint
vec![ // Headers
KeyValuePair {
key: "Accept".to_string(),
value: "application/json".to_string(),
}
],
None, // Body
true, // Validate certificates
vec![], // Root certificate bundles
None, // Client certificate
None, // Proxy configuration
);
// Execute the request with cancellation support
let cancel_token = CancellationToken::new();
let response = hoppscotch_relay::run_request_task(&request, cancel_token)?;
println!("Status: {} {}", response.status, response.status_text);
println!("Response time: {}ms", response.time_end_ms - response.time_start_ms);
```
### POST Request with JSON Body
```rust
let mut request = RequestWithMetadata::new(
2,
"POST".to_string(),
"https://api.example.com/users".to_string(),
vec![
KeyValuePair {
key: "Content-Type".to_string(),
value: "application/json".to_string(),
}
],
Some(BodyDef::Text(r#"{"name": "John Doe"}"#.to_string())),
true,
vec![],
None,
None,
);
let response = hoppscotch_relay::run_request_task(&request, CancellationToken::new())?;
```
### File Upload with Form Data
```rust
let form_data = vec![
FormDataEntry {
key: "file".to_string(),
value: FormDataValue::File {
filename: "document.pdf".to_string(),
data: std::fs::read("document.pdf")?,
mime: "application/pdf".to_string(),
},
},
FormDataEntry {
key: "description".to_string(),
value: FormDataValue::Text("Important document".to_string()),
},
];
let mut request = RequestWithMetadata::new(
3,
"POST".to_string(),
"https://api.example.com/upload".to_string(),
vec![],
Some(BodyDef::FormData(form_data)),
true,
vec![],
None,
None,
);
```
### Client Certificate Authentication
```rust
let client_cert = ClientCertDef::PEMCert {
certificate_pem: std::fs::read("client.crt")?,
key_pem: std::fs::read("client.key")?,
};
let mut request = RequestWithMetadata::new(
4,
"GET".to_string(),
"https://secure-api.example.com".to_string(),
vec![],
None,
true,
vec![],
Some(client_cert),
None,
);
```
### Proxy Configuration
```rust
let proxy_config = ProxyConfig {
url: "http://proxy.example.com:8080".to_string(),
};
let mut request = RequestWithMetadata::new(
5,
"GET".to_string(),
"https://api.example.com".to_string(),
vec![],
None,
true,
vec![],
None,
Some(proxy_config),
);
```
## Request Cancellation
The library supports request cancellation through Tokio's `CancellationToken`:
```rust
use tokio_util::sync::CancellationToken;
let cancel_token = CancellationToken::new();
let cancel_token_clone = cancel_token.clone();
// Spawn the request in a separate task
let request_handle = tokio::spawn(async move {
hoppscotch_relay::run_request_task(&request, cancel_token_clone)
});
// Cancel the request after 5 seconds
tokio::time::sleep(Duration::from_secs(5)).await;
cancel_token.cancel();
```
## Building from Source
1. Clone the repository:
```bash
git clone https://github.com/hoppscotch/hoppscotch-relay
cd hoppscotch-relay
```
2. Build the project:
```bash
cargo build --release
```

View File

@@ -0,0 +1,153 @@
{
"nodes": {
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1729641677,
"owner": "cachix",
"repo": "devenv",
"rev": "4f634c92037d3fb7a7cc2feddc4d686ace83b57f",
"type": "github"
},
"original": {
"dir": "src/modules",
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"fenix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1729578683,
"owner": "nix-community",
"repo": "fenix",
"rev": "d66cda53e8193a878742dcadb5bb75f4df7c3c0a",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1729501122,
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "56c7c4a3f5fdbef5bf81c7d9c28fbb45dc626611",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1729449015,
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "89172919243df199fe237ba0f776c3e3e3d72367",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1729104314,
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "3c3e88f0f544d6bb54329832616af7eb971b6be6",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"fenix": "fenix",
"nixpkgs": "nixpkgs",
"pre-commit-hooks": "pre-commit-hooks"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1729618852,
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "c2867868889a549562a7b53fb572719f852a8a6f",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@@ -0,0 +1,54 @@
{ pkgs, lib, config, inputs, ... }:
{
# https://devenv.sh/packages/
packages = with pkgs; [
git
# Cargo
cargo-edit
];
# https://devenv.sh/basics/
env = {
APP_GREET = "Hoppscotch";
};
# https://devenv.sh/scripts/
scripts.hello.exec = "echo hello from $APP_GREET";
enterShell = ''
git --version
'';
# https://devenv.sh/tests/
enterTest = ''
echo "Running tests"
'';
# https://devenv.sh/integrations/dotenv/
dotenv.enable = true;
# https://devenv.sh/languages/
languages.rust = {
enable = true;
channel = "nightly";
components = [
"rustc"
"cargo"
"clippy"
"rustfmt"
"rust-analyzer"
"llvm-tools-preview"
"rust-src"
"rustc-codegen-cranelift-preview"
];
};
# https://devenv.sh/pre-commit-hooks/
# pre-commit.hooks.shellcheck.enable = true;
# https://devenv.sh/processes/
# processes.ping.exec = "ping example.com";
# See full reference at https://devenv.sh/reference/options/
}

View File

@@ -0,0 +1,23 @@
# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json
inputs:
# For NodeJS-22 and above
nixpkgs:
url: github:NixOS/nixpkgs/nixpkgs-unstable
# nixpkgs:
# url: github:cachix/devenv-nixpkgs/rolling
fenix:
url: github:nix-community/fenix
inputs:
nixpkgs:
follows: nixpkgs
# If you're using non-OSS software, you can set allowUnfree to true.
allowUnfree: true
# If you're willing to use a package that's vulnerable
# permittedInsecurePackages:
# - "openssl-1.1.1w"
# If you have more than one devenv you can merge them
#imports:
# - ./backend

View File

@@ -0,0 +1,16 @@
use serde::Serialize;
use thiserror::Error;
#[derive(Debug, Error, Serialize)]
pub enum RelayError {
#[error("Invalid method")]
InvalidMethod,
#[error("Invalid URL")]
InvalidUrl,
#[error("Invalid headers")]
InvalidHeaders,
#[error("Request run error: {0}")]
RequestRunError(String),
}
pub type RelayResult<T> = std::result::Result<T, RelayError>;

View File

@@ -0,0 +1,96 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct KeyValuePair {
pub key: String,
pub value: String,
}
#[derive(Debug, Deserialize)]
pub enum FormDataValue {
Text(String),
File {
filename: String,
data: Vec<u8>,
mime: String,
},
}
#[derive(Debug, Deserialize)]
pub struct FormDataEntry {
pub key: String,
pub value: FormDataValue,
}
#[derive(Debug, Deserialize)]
pub enum BodyDef {
Text(String),
URLEncoded(Vec<KeyValuePair>),
FormData(Vec<FormDataEntry>),
}
#[derive(Debug, Deserialize)]
pub struct RequestWithMetadata {
pub req_id: usize,
pub method: String,
pub endpoint: String,
pub headers: Vec<KeyValuePair>,
pub body: Option<BodyDef>,
pub validate_certs: bool,
pub root_cert_bundle_files: Vec<Vec<u8>>,
pub client_cert: Option<ClientCertDef>,
pub proxy: Option<ProxyConfig>,
}
impl RequestWithMetadata {
pub fn new(
req_id: usize,
method: String,
endpoint: String,
headers: Vec<KeyValuePair>,
body: Option<BodyDef>,
validate_certs: bool,
root_cert_bundle_files: Vec<Vec<u8>>,
client_cert: Option<ClientCertDef>,
proxy: Option<ProxyConfig>,
) -> Self {
Self {
req_id,
method,
endpoint,
headers,
body,
validate_certs,
root_cert_bundle_files,
client_cert,
proxy,
}
}
}
#[derive(Debug, Deserialize)]
pub struct ProxyConfig {
pub url: String,
}
#[derive(Debug, Deserialize)]
pub enum ClientCertDef {
PEMCert {
certificate_pem: Vec<u8>,
key_pem: Vec<u8>,
},
PFXCert {
certificate_pfx: Vec<u8>,
password: String,
},
}
#[derive(Debug, Serialize)]
pub struct ResponseWithMetadata {
pub status: u16,
pub status_text: String,
pub headers: Vec<KeyValuePair>,
pub data: Vec<u8>,
pub time_start_ms: u128,
pub time_end_ms: u128,
}

View File

@@ -0,0 +1,23 @@
pub(crate) mod error;
pub(crate) mod interop;
pub(crate) mod relay;
pub(crate) mod util;
pub use error::{RelayError, RelayResult};
pub use interop::{RequestWithMetadata, ResponseWithMetadata};
pub use relay::run_request_task;
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View File

@@ -0,0 +1,595 @@
use curl::easy::{Easy, List};
use openssl::{pkcs12::Pkcs12, ssl::SslContextBuilder, x509::X509};
use openssl_sys::SSL_CTX;
use std::time::SystemTime;
use tokio_util::sync::CancellationToken;
use crate::{
error::RelayError,
interop::{
BodyDef, ClientCertDef, FormDataValue, KeyValuePair, RequestWithMetadata,
ResponseWithMetadata,
},
util::get_status_text,
};
pub fn run_request_task(
req: &RequestWithMetadata,
cancel_token: CancellationToken,
) -> Result<ResponseWithMetadata, RelayError> {
log::info!(
"Starting request task: [Method: {}] [URL: {}] [Validate Certs: {}] [Has Body: {}] [Proxy Enabled: {}]",
req.method,
req.endpoint,
req.validate_certs,
req.body.is_some(),
req.proxy.is_some()
);
let mut curl_handle = Easy::new();
log::debug!("Initialized new curl handle with default settings");
match curl_handle.progress(true) {
Ok(_) => log::debug!("Progress tracking enabled for request monitoring"),
Err(err) => {
log::error!(
"Critical failure enabling progress tracking: {}\nError details: {:?}",
err,
err
);
return Err(RelayError::RequestRunError(err.description().to_string()));
}
}
match curl_handle.custom_request(&req.method) {
Ok(_) => log::debug!("HTTP method set: {}", req.method),
Err(err) => {
log::error!("Failed to set HTTP method '{}'. Error: {}", req.method, err);
return Err(RelayError::InvalidMethod);
}
}
match curl_handle.url(&req.endpoint) {
Ok(_) => log::debug!("Target URL configured: {}", req.endpoint),
Err(err) => {
log::error!(
"URL configuration failed for '{}'\nError: {}",
req.endpoint,
err
);
return Err(RelayError::InvalidUrl);
}
}
let headers = match get_headers_list(&req) {
Ok(headers) => {
log::debug!("Generated headers list");
headers
}
Err(err) => {
log::error!("Header generation failed:\nError: {:?}", err);
return Err(err);
}
};
match curl_handle.http_headers(headers) {
Ok(_) => log::debug!("Successfully configured request headers"),
Err(err) => {
log::error!("Failed to set HTTP headers: {}", err);
return Err(RelayError::InvalidHeaders);
}
}
if let Err(err) = apply_body_to_curl_handle(&mut curl_handle, &req) {
log::error!(
"Request body application failed:\nError: {:?}\nContent-Type: {:?}",
err,
req.headers
.iter()
.find(|h| h.key.to_lowercase() == "content-type")
.map(|h| &h.value)
);
return Err(err);
}
log::debug!("Request body configured successfully");
match curl_handle.ssl_verify_peer(req.validate_certs) {
Ok(_) => log::debug!(
"SSL peer verification setting applied: {}",
req.validate_certs
),
Err(err) => {
log::error!(
"SSL peer verification configuration failed: {}\nRequested setting: {}",
err,
req.validate_certs
);
return Err(RelayError::RequestRunError(err.description().to_string()));
}
}
match curl_handle.ssl_verify_host(req.validate_certs) {
Ok(_) => log::debug!(
"SSL host verification setting applied: {}",
req.validate_certs
),
Err(err) => {
log::error!(
"SSL host verification configuration failed: {}\nRequested setting: {}",
err,
req.validate_certs
);
return Err(RelayError::RequestRunError(err.description().to_string()));
}
}
if let Err(err) = apply_client_cert_to_curl_handle(&mut curl_handle, &req) {
log::error!(
"Client certificate configuration failed:\nError: {:?}\nCert Info: {:#?}",
err,
req.client_cert.as_ref()
);
return Err(err);
}
log::debug!("Client certificate configuration successful");
if let Err(err) = apply_proxy_config_to_curl_handle(&mut curl_handle, &req) {
log::error!(
"Proxy configuration failed:\nError: {:?}\nProxy Info: {:?}",
err,
req.proxy.as_ref()
);
return Err(err);
}
log::debug!("Proxy configuration applied successfully");
let mut response_body = Vec::new();
let mut response_headers = Vec::new();
let (start_time_ms, end_time_ms) = {
let mut transfer = curl_handle.transfer();
log::debug!("Created curl transfer object for request execution");
match transfer.ssl_ctx_function(|ssl_ctx_ptr| {
let cert_list = match get_x509_certs_from_root_cert_bundle_safe(&req) {
Ok(certs) => {
log::debug!("Found {} certificates in root bundle", certs.len());
certs
}
Err(e) => {
log::error!("Failed to load certificates from bundle: {:?}", e);
return Ok(());
}
};
if !cert_list.is_empty() {
let mut ssl_ctx_builder =
unsafe { SslContextBuilder::from_ptr(ssl_ctx_ptr as *mut SSL_CTX) };
let cert_store = ssl_ctx_builder.cert_store_mut();
for (index, cert) in cert_list.iter().enumerate() {
log::debug!(
"Processing certificate {}: Subject: {:?}, Not Before: {:?}, Not After: {:?}",
index,
cert.subject_name(),
cert.not_before(),
cert.not_after()
);
if let Err(e) = cert_store.add_cert(cert.clone()) {
log::warn!(
"Failed to add certificate {} to store\nError: {}\nCert details: {:?}",
index,
e,
cert.subject_name()
);
} else {
log::debug!(
"Successfully added certificate {} to store\nSubject: {:?}",
index,
cert.subject_name()
);
}
}
// SAFETY: We need to prevent Rust from dropping the `SslContextBuilder` because
// the underlying `SSL_CTX` pointer is owned and managed by curl, not us.
// From curl docs: "libcurl does not guarantee the lifetime of the passed in
// object once this callback function has returned"
// and `SslContextBuilder` is just a safe wrapper around curl's `SSL_CTX` from
// `openssl_sys::SSL_CTX`.
// If dropped, Rust would try to free the `SSL_CTX` which curl still needs.
//
// This intentional "leak" is safe because:
// - We're only leaking the thin Rust wrapper
// - Curl manages the actual `SSL_CTX` memory
// - Curl will free the `SSL_CTX` during connection cleanup
//
// See: https://curl.se/libcurl/c/CURLOPT_SSL_CTX_FUNCTION.html
std::mem::forget(ssl_ctx_builder);
}
Ok(())
}) {
Ok(_) => log::debug!("SSL context function configured successfully"),
Err(err) => {
log::error!("SSL context function setup failed: {}", err);
return Err(RelayError::RequestRunError(err.description().to_string()));
}
}
match transfer.progress_function(|dltotal, dlnow, ultotal, ulnow| {
let cancelled = cancel_token.is_cancelled();
if cancelled {
log::warn!(
"Request cancelled by user\nDownload: {}/{} bytes\nUpload: {}/{} bytes",
dlnow,
dltotal,
ulnow,
ultotal
);
} else {
log::debug!(
"Progress - Download: {}/{} bytes, Upload: {}/{} bytes",
dlnow,
dltotal,
ulnow,
ultotal
);
}
!cancelled
}) {
Ok(_) => log::debug!("Progress monitoring function configured"),
Err(err) => {
log::error!("Progress function setup failed: {}", err);
return Err(RelayError::RequestRunError(err.description().to_string()));
}
}
match transfer.header_function(|header| {
let header = String::from_utf8_lossy(header).into_owned();
if let Some((key, value)) = header.split_once(':') {
log::debug!("Received header: [{}] = [{}]", key.trim(), value.trim());
response_headers.push(KeyValuePair {
key: key.trim().to_string(),
value: value.trim().to_string(),
});
} else {
log::debug!("Received header line (no key-value): {}", header.trim());
}
true
}) {
Ok(_) => log::debug!("Header processing function configured"),
Err(err) => {
log::error!("Header function setup failed: {}", err);
return Err(RelayError::RequestRunError(err.description().to_string()));
}
}
match transfer.write_function(|data| {
let chunk_size = data.len();
response_body.extend_from_slice(data);
log::debug!(
"Received response chunk: {} bytes (Total size so far: {} bytes)",
chunk_size,
response_body.len()
);
Ok(chunk_size)
}) {
Ok(_) => log::debug!("Response body processing function configured"),
Err(err) => {
log::error!("Write function setup failed: {}", err);
return Err(RelayError::RequestRunError(err.description().to_string()));
}
}
let start_time_ms = SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis();
log::info!(
"Initiating request transfer at timestamp: {}",
start_time_ms
);
if let Err(err) = transfer.perform() {
log::error!(
"Request transfer failed:\nError: {}\nTime elapsed: {}ms",
err,
SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis()
- start_time_ms,
);
return Err(RelayError::RequestRunError(err.description().to_string()));
}
let end_time_ms = SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis();
log::info!(
"Request transfer completed:\nDuration: {}ms",
end_time_ms - start_time_ms,
);
(start_time_ms, end_time_ms)
};
let response_status = match curl_handle.response_code() {
Ok(status) => {
let status = status as u16;
log::info!(
"Response status code: {} ({})",
status,
get_status_text(status)
);
status
}
Err(err) => {
log::error!("Failed to retrieve response code: {}", err);
return Err(RelayError::RequestRunError(err.description().to_string()));
}
};
let response_status_text = get_status_text(response_status).to_string();
log::info!(
"Request completed successfully:\nStatus: {} ({})\nDuration: {}ms\n\
Response size: {} bytes\nHeaders: {} received\nEndpoint: {}",
response_status,
response_status_text,
end_time_ms - start_time_ms,
response_body.len(),
response_headers.len(),
req.endpoint
);
Ok(ResponseWithMetadata {
status: response_status,
status_text: response_status_text,
headers: response_headers,
data: response_body,
time_start_ms: start_time_ms,
time_end_ms: end_time_ms,
})
}
fn get_headers_list(req: &RequestWithMetadata) -> Result<List, RelayError> {
let mut result = List::new();
for KeyValuePair { key, value } in &req.headers {
result
.append(&format!("{}: {}", key, value))
.map_err(|err| RelayError::RequestRunError(err.description().to_string()))?;
}
Ok(result)
}
fn apply_body_to_curl_handle(
curl_handle: &mut Easy,
req: &RequestWithMetadata,
) -> Result<(), RelayError> {
match &req.body {
Some(BodyDef::Text(text)) => {
curl_handle
.post_fields_copy(text.as_bytes())
.map_err(|err| {
RelayError::RequestRunError(format!(
"Error while setting body: {}",
err.description()
))
})?;
}
Some(BodyDef::FormData(entries)) => {
let mut form = curl::easy::Form::new();
for entry in entries {
let mut part = form.part(&entry.key);
match &entry.value {
FormDataValue::Text(data) => {
part.contents(data.as_bytes());
}
FormDataValue::File {
filename,
data,
mime,
} => {
part.buffer(filename, data.clone()).content_type(mime);
}
};
part.add().map_err(|err| {
RelayError::RequestRunError(format!(
"Error while setting body: {}",
err.description()
))
})?;
}
curl_handle.httppost(form).map_err(|err| {
RelayError::RequestRunError(format!(
"Error while setting body: {}",
err.description()
))
})?;
}
Some(BodyDef::URLEncoded(entries)) => {
let data = entries
.iter()
.map(|KeyValuePair { key, value }| {
format!(
"{}={}",
&url_escape::encode_www_form_urlencoded(key),
url_escape::encode_www_form_urlencoded(value)
)
})
.collect::<Vec<String>>()
.join("&");
curl_handle
.post_fields_copy(data.as_bytes())
.map_err(|err| {
RelayError::RequestRunError(format!(
"Error while setting body: {}",
err.description()
))
})?;
}
None => {}
};
Ok(())
}
fn apply_client_cert_to_curl_handle(
handle: &mut Easy,
req: &RequestWithMetadata,
) -> Result<(), RelayError> {
match &req.client_cert {
Some(ClientCertDef::PEMCert {
certificate_pem,
key_pem,
}) => {
handle.ssl_cert_type("PEM").map_err(|err| {
RelayError::RequestRunError(format!(
"Failed setting PEM Cert Type: {}",
err.description()
))
})?;
handle.ssl_cert_blob(certificate_pem).map_err(|err| {
RelayError::RequestRunError(format!(
"Failed setting PEM Cert Blob: {}",
err.description()
))
})?;
handle.ssl_key_type("PEM").map_err(|err| {
RelayError::RequestRunError(format!(
"Failed setting PEM key type: {}",
err.description()
))
})?;
handle.ssl_key_blob(key_pem).map_err(|err| {
RelayError::RequestRunError(format!(
"Failed setting PEM Cert blob: {}",
err.description()
))
})?;
}
Some(ClientCertDef::PFXCert {
certificate_pfx,
password,
}) => {
let pkcs12 = Pkcs12::from_der(&certificate_pfx).map_err(|err| {
RelayError::RequestRunError(format!(
"Failed to parse PFX certificate from DER: {}",
err
))
})?;
let parsed = pkcs12.parse2(password).map_err(|err| {
RelayError::RequestRunError(format!(
"Failed to parse PFX certificate with provided password: {}",
err
))
})?;
if let (Some(cert), Some(key)) = (parsed.cert, parsed.pkey) {
let certificate_pem = cert.to_pem().map_err(|err| {
RelayError::RequestRunError(format!(
"Failed to convert PFX certificate to PEM format: {}",
err
))
})?;
let key_pem = key.private_key_to_pem_pkcs8().map_err(|err| {
RelayError::RequestRunError(format!(
"Failed to convert PFX private key to PEM format: {}",
err
))
})?;
handle.ssl_cert_type("PEM").map_err(|err| {
RelayError::RequestRunError(format!(
"Failed setting PEM Cert Type for converted PFX: {}",
err.description()
))
})?;
handle.ssl_cert_blob(&certificate_pem).map_err(|err| {
RelayError::RequestRunError(format!(
"Failed setting PEM Cert Blob for converted PFX: {}",
err.description()
))
})?;
handle.ssl_key_type("PEM").map_err(|err| {
RelayError::RequestRunError(format!(
"Failed setting PEM key type for converted PFX: {}",
err.description()
))
})?;
handle.ssl_key_blob(&key_pem).map_err(|err| {
RelayError::RequestRunError(format!(
"Failed setting PEM key blob for converted PFX: {}",
err.description()
))
})?;
} else {
return Err(RelayError::RequestRunError(
"PFX certificate parsing succeeded, but either cert or private key is missing"
.to_string(),
));
}
}
None => {}
};
Ok(())
}
fn get_x509_certs_from_root_cert_bundle_safe(
req: &RequestWithMetadata,
) -> Result<Vec<X509>, openssl::error::ErrorStack> {
let mut certs = Vec::new();
for pem_bundle in &req.root_cert_bundle_files {
match openssl::x509::X509::stack_from_pem(pem_bundle) {
Ok(mut bundle_certs) => certs.append(&mut bundle_certs),
Err(e) => {
log::warn!("Failed to parse certificate bundle: {:?}", e);
}
}
}
Ok(certs)
}
fn apply_proxy_config_to_curl_handle(
handle: &mut Easy,
req: &RequestWithMetadata,
) -> Result<(), RelayError> {
if let Some(proxy_config) = &req.proxy {
handle
.proxy_auth(curl::easy::Auth::new().auto(true))
.map_err(|err| {
RelayError::RequestRunError(format!(
"Failed to set proxy Auth Mode: {}",
err.description()
))
})?;
handle.proxy(&proxy_config.url).map_err(|err| {
RelayError::RequestRunError(format!("Failed to set proxy URL: {}", err.description()))
})?;
}
Ok(())
}

View File

@@ -0,0 +1,6 @@
pub fn get_status_text(status: u16) -> &'static str {
http::StatusCode::from_u16(status)
.map(|status| status.canonical_reason())
.unwrap_or(Some("Unknown Status"))
.unwrap_or("Unknown Status")
}