node: Use `gix_packetline`
`gix-packetline` already is in the dependency closure of `radicle-node`. Use the well-tested and -used reader instead of ours.
This commit is contained in:
parent
c96aea0699
commit
5fa68ed814
|
|
@ -3133,6 +3133,7 @@ dependencies = [
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"cyphernet",
|
"cyphernet",
|
||||||
"fastrand",
|
"fastrand",
|
||||||
|
"gix-packetline",
|
||||||
"lexopt",
|
"lexopt",
|
||||||
"log",
|
"log",
|
||||||
"mio 1.0.4",
|
"mio 1.0.4",
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ dunce = "1.0.5"
|
||||||
fastrand = { version = "2.0.0", default-features = false }
|
fastrand = { version = "2.0.0", default-features = false }
|
||||||
git2 = { version = "0.19.0", default-features = false, features = ["vendored-libgit2"] }
|
git2 = { version = "0.19.0", default-features = false, features = ["vendored-libgit2"] }
|
||||||
gix-hash = { version = "0.22.1", default-features = false, features = ["sha1"] }
|
gix-hash = { version = "0.22.1", default-features = false, features = ["sha1"] }
|
||||||
|
gix-packetline = { version = "0.21.1", default-features = false }
|
||||||
human-panic = "2.0.6"
|
human-panic = "2.0.6"
|
||||||
itertools = "0.14"
|
itertools = "0.14"
|
||||||
lexopt = "0.3.0"
|
lexopt = "0.3.0"
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ colored = { workspace = true }
|
||||||
crossbeam-channel = { workspace = true }
|
crossbeam-channel = { workspace = true }
|
||||||
cyphernet = { workspace = true, features = ["tor", "dns", "ed25519", "p2p-ed25519", "noise-framework", "noise_sha2"] }
|
cyphernet = { workspace = true, features = ["tor", "dns", "ed25519", "p2p-ed25519", "noise-framework", "noise_sha2"] }
|
||||||
fastrand = { workspace = true }
|
fastrand = { workspace = true }
|
||||||
|
gix-packetline = { workspace = true, features = ["blocking-io"] }
|
||||||
lexopt = { workspace = true }
|
lexopt = { workspace = true }
|
||||||
log = { workspace = true, features = ["kv", "std"] }
|
log = { workspace = true, features = ["kv", "std"] }
|
||||||
mio = { version = "1", features = ["net", "os-poll"] }
|
mio = { version = "1", features = ["net", "os-poll"] }
|
||||||
|
|
|
||||||
|
|
@ -141,15 +141,51 @@ impl Worker {
|
||||||
|
|
||||||
let timeout = channels.timeout();
|
let timeout = channels.timeout();
|
||||||
let (mut stream_r, stream_w) = channels.split();
|
let (mut stream_r, stream_w) = channels.split();
|
||||||
let header = match upload_pack::pktline::git_request(&mut stream_r) {
|
|
||||||
Ok(header) => header,
|
let mut iter = gix_packetline::blocking_io::StreamingPeekableIter::new(
|
||||||
Err(e) => {
|
&mut stream_r,
|
||||||
|
&[gix_packetline::PacketLineRef::Flush],
|
||||||
|
false, /* packet tracing */
|
||||||
|
);
|
||||||
|
|
||||||
|
let header = match iter.read_line() {
|
||||||
|
None => {
|
||||||
|
return FetchResult::Responder {
|
||||||
|
rid: None,
|
||||||
|
result: Err(UploadError::PacketLine(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::UnexpectedEof,
|
||||||
|
"unexpected end of stream while reading upload-pack header",
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Err(e)) => {
|
||||||
return FetchResult::Responder {
|
return FetchResult::Responder {
|
||||||
rid: None,
|
rid: None,
|
||||||
result: Err(UploadError::PacketLine(e)),
|
result: Err(UploadError::PacketLine(e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some(Ok(Err(e))) => {
|
||||||
|
return FetchResult::Responder {
|
||||||
|
rid: None,
|
||||||
|
result: Err(UploadError::PacketLine(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::InvalidData,
|
||||||
|
format!("invalid upload-pack header: {e}"),
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Ok(Ok(header))) => header,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let Some(header) = upload_pack::GitRequest::from_packetline(header) else {
|
||||||
|
return FetchResult::Responder {
|
||||||
|
rid: None,
|
||||||
|
result: Err(UploadError::PacketLine(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::InvalidData,
|
||||||
|
"failed to parse upload-pack header",
|
||||||
|
))),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
log::debug!(target: "worker", "Spawning upload-pack process for {} on stream {stream}..", header.repo);
|
log::debug!(target: "worker", "Spawning upload-pack process for {} on stream {stream}..", header.repo);
|
||||||
|
|
||||||
if let Err(e) = self.is_authorized(remote, header.repo) {
|
if let Err(e) = self.is_authorized(remote, header.repo) {
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ pub fn upload_pack<R, W>(
|
||||||
remote: NodeId,
|
remote: NodeId,
|
||||||
storage: &Storage,
|
storage: &Storage,
|
||||||
emitter: &Emitter<Event>,
|
emitter: &Emitter<Event>,
|
||||||
header: &pktline::GitRequest,
|
header: &GitRequest,
|
||||||
mut recv: R,
|
mut recv: R,
|
||||||
send: W,
|
send: W,
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
|
|
@ -239,119 +239,63 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) mod pktline {
|
/// The Git request packet-line for a repository.
|
||||||
use std::io;
|
///
|
||||||
use std::io::Read;
|
/// See <https://git-scm.com/docs/pack-protocol.html#_git_transport>.
|
||||||
use std::str;
|
///
|
||||||
|
/// Example: `0032git-upload-pack /rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5.git\0host=myserver.com\0`
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GitRequest {
|
||||||
|
pub repo: RepoId,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub path: String,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub host: Option<(String, Option<u16>)>,
|
||||||
|
pub extra: Vec<(String, Option<String>)>,
|
||||||
|
}
|
||||||
|
|
||||||
use radicle::prelude::RepoId;
|
impl GitRequest {
|
||||||
|
pub(super) fn from_packetline(
|
||||||
pub const HEADER_LEN: usize = 4;
|
packet_line: gix_packetline::PacketLineRef<'_>,
|
||||||
|
) -> Option<GitRequest> {
|
||||||
/// Read and parse the `GitRequest` data from the client side.
|
packet_line.as_slice().and_then(Self::parse)
|
||||||
pub fn git_request<R>(reader: &mut R) -> io::Result<GitRequest>
|
|
||||||
where
|
|
||||||
R: io::Read,
|
|
||||||
{
|
|
||||||
let mut reader = Reader::new(reader);
|
|
||||||
let (header, _) = reader.read_request_pktline()?;
|
|
||||||
Ok(header)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Reader<'a, R> {
|
/// Parse a Git command from a packet-line.
|
||||||
stream: &'a mut R,
|
fn parse(input: &[u8]) -> Option<Self> {
|
||||||
}
|
let input = std::str::from_utf8(input).ok()?;
|
||||||
|
let mut parts = input
|
||||||
|
.strip_prefix("git-upload-pack ")?
|
||||||
|
.split_terminator('\0');
|
||||||
|
|
||||||
impl<'a, R: io::Read> Reader<'a, R> {
|
let path = parts.next()?.to_owned();
|
||||||
/// Create a new packet-line reader.
|
let repo = path.strip_prefix('/')?.parse().ok()?;
|
||||||
pub fn new(stream: &'a mut R) -> Self {
|
let host = match parts.next() {
|
||||||
Self { stream }
|
None | Some("") => None,
|
||||||
}
|
Some(host) => {
|
||||||
|
let host = host.strip_prefix("host=")?;
|
||||||
/// Parse a Git request packet-line.
|
match host.split_once(':') {
|
||||||
///
|
None => Some((host.to_owned(), None)),
|
||||||
/// Example: `0032git-upload-pack /project.git\0host=myserver.com\0`
|
Some((host, port)) => {
|
||||||
///
|
let port = port.parse::<u16>().ok()?;
|
||||||
fn read_request_pktline(&mut self) -> io::Result<(GitRequest, Vec<u8>)> {
|
Some((host.to_owned(), Some(port)))
|
||||||
let mut pktline = [0u8; 1024];
|
|
||||||
let length = self.read_pktline(&mut pktline)?;
|
|
||||||
let Some(cmd) = GitRequest::parse(&pktline[4..length]) else {
|
|
||||||
return Err(io::ErrorKind::InvalidInput.into());
|
|
||||||
};
|
|
||||||
Ok((cmd, Vec::from(&pktline[..length])))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse a Git packet-line.
|
|
||||||
fn read_pktline(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
self.read_exact(&mut buf[..HEADER_LEN])?;
|
|
||||||
|
|
||||||
let length = str::from_utf8(&buf[..HEADER_LEN])
|
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?;
|
|
||||||
let length = usize::from_str_radix(length, 16)
|
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.to_string()))?;
|
|
||||||
|
|
||||||
self.read_exact(&mut buf[HEADER_LEN..length])?;
|
|
||||||
|
|
||||||
Ok(length)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: io::Read> io::Read for Reader<'_, R> {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
self.stream.read(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The Git request packet-line for a Heartwood repository.
|
|
||||||
///
|
|
||||||
/// Example: `0032git-upload-pack /rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5.git\0host=myserver.com\0`
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct GitRequest {
|
|
||||||
pub repo: RepoId,
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub path: String,
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub host: Option<(String, Option<u16>)>,
|
|
||||||
pub extra: Vec<(String, Option<String>)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GitRequest {
|
|
||||||
/// Parse a Git command from a packet-line.
|
|
||||||
fn parse(input: &[u8]) -> Option<Self> {
|
|
||||||
let input = str::from_utf8(input).ok()?;
|
|
||||||
let mut parts = input
|
|
||||||
.strip_prefix("git-upload-pack ")?
|
|
||||||
.split_terminator('\0');
|
|
||||||
|
|
||||||
let path = parts.next()?.to_owned();
|
|
||||||
let repo = path.strip_prefix('/')?.parse().ok()?;
|
|
||||||
let host = match parts.next() {
|
|
||||||
None | Some("") => None,
|
|
||||||
Some(host) => {
|
|
||||||
let host = host.strip_prefix("host=")?;
|
|
||||||
match host.split_once(':') {
|
|
||||||
None => Some((host.to_owned(), None)),
|
|
||||||
Some((host, port)) => {
|
|
||||||
let port = port.parse::<u16>().ok()?;
|
|
||||||
Some((host.to_owned(), Some(port)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
let extra = parts
|
};
|
||||||
.skip_while(|part| part.is_empty())
|
let extra = parts
|
||||||
.map(|part| match part.split_once('=') {
|
.skip_while(|part| part.is_empty())
|
||||||
None => (part.to_owned(), None),
|
.map(|part| match part.split_once('=') {
|
||||||
Some((k, v)) => (k.to_owned(), Some(v.to_owned())),
|
None => (part.to_owned(), None),
|
||||||
})
|
Some((k, v)) => (k.to_owned(), Some(v.to_owned())),
|
||||||
.collect();
|
|
||||||
|
|
||||||
Some(Self {
|
|
||||||
repo,
|
|
||||||
path,
|
|
||||||
host,
|
|
||||||
extra,
|
|
||||||
})
|
})
|
||||||
}
|
.collect();
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
repo,
|
||||||
|
path,
|
||||||
|
host,
|
||||||
|
extra,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue