Skip to content

Commit

Permalink
add read_one_from_slice
Browse files Browse the repository at this point in the history
  • Loading branch information
japaric authored and djc committed Nov 10, 2023
1 parent 6e36748 commit 0227a3a
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 33 deletions.
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ mod tests;

/// --- Main crate APIs:
mod pemfile;
pub use pemfile::{read_all, read_one, Error, Item};
pub use pemfile::{read_all, read_one, read_one_from_slice, Error, Item};
use pki_types::PrivateKeyDer;
use pki_types::{
CertificateDer, CertificateRevocationListDer, PrivatePkcs1KeyDer, PrivatePkcs8KeyDer,
Expand Down
26 changes: 26 additions & 0 deletions src/pemfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,32 @@ pub enum Error {
Base64Decode(String),
}

/// Extract and decode the next PEM section from `input`
///
/// - `Ok(None)` is returned if there is no PEM section to read from `input`
/// - Syntax errors and decoding errors produce a `Err(...)`
/// - Otherwise each decoded section is returned with a `Ok(Some((Item::..., remainder)))` where
/// `remainder` is the part of the `input` that follows the returned section
pub fn read_one_from_slice(mut input: &[u8]) -> Result<Option<(Item, &[u8])>, Error> {
let mut b64buf = Vec::with_capacity(1024);
let mut section = None::<(Vec<_>, Vec<_>)>;

loop {
let next_line = if let Some(index) = input.iter().position(|byte| *byte == b'\n') {
let (line, newline_plus_remainder) = input.split_at(index);
input = &newline_plus_remainder[1..];
Some(line)
} else {
None
};

match read_one_impl(next_line, &mut section, &mut b64buf)? {
ControlFlow::Continue(()) => continue,
ControlFlow::Break(item) => return Ok(item.map(|item| (item, input))),
}
}
}

/// Extract and decode the next PEM section from `rd`.
///
/// - Ok(None) is returned if there is no PEM section read from `rd`.
Expand Down
95 changes: 63 additions & 32 deletions src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,100 +1,131 @@
#[cfg(test)]
mod unit {
fn check(data: &[u8]) -> Result<Vec<crate::Item>, std::io::Error> {
let mut reader = std::io::BufReader::new(data);
crate::read_all(&mut reader).collect()
}
use crate::{Error, Item};

#[test]
fn skips_leading_junk() {
assert_eq!(
check(
check_both(
b"junk\n\
-----BEGIN RSA PRIVATE KEY-----\n\
qw\n\
-----END RSA PRIVATE KEY-----\n"
)
.unwrap(),
vec![crate::Item::Pkcs1Key(vec![0xab].into())]
),
vec![Item::Pkcs1Key(vec![0xab].into())]
);
}

#[test]
fn skips_trailing_junk() {
assert_eq!(
check(
check_both(
b"-----BEGIN RSA PRIVATE KEY-----\n\
qw\n\
-----END RSA PRIVATE KEY-----\n\
junk"
)
.unwrap(),
vec![crate::Item::Pkcs1Key(vec![0xab].into())]
),
vec![Item::Pkcs1Key(vec![0xab].into())]
);
}

#[test]
fn skips_non_utf8_junk() {
assert_eq!(
check(
check_both(
b"\x00\x00\n\
-----BEGIN RSA PRIVATE KEY-----\n\
qw\n\
-----END RSA PRIVATE KEY-----\n
\x00\x00"
)
.unwrap(),
vec![crate::Item::Pkcs1Key(vec![0xab].into())]
),
vec![Item::Pkcs1Key(vec![0xab].into())]
);
}

#[test]
fn rejects_invalid_base64() {
assert_eq!(
format!(
"{:?}",
check(
b"-----BEGIN RSA PRIVATE KEY-----\n\
let input = b"-----BEGIN RSA PRIVATE KEY-----\n\
q=w\n\
-----END RSA PRIVATE KEY-----\n"
)
),
-----END RSA PRIVATE KEY-----\n";
assert_eq!(
format!("{:?}", check_io(input)),
"Err(Custom { kind: InvalidData, error: \"InvalidByte(1, 61)\" })"
);
assert!(matches!(check_slice(input), Err(Error::Base64Decode(_))));
}

#[test]
fn rejects_unclosed_start_section() {
let input = b"-----BEGIN RSA PRIVATE KEY-----\n\
qw\n";
assert_eq!(
format!("{:?}",
check(b"-----BEGIN RSA PRIVATE KEY-----\n\
qw\n")),
check_io(input)),
"Err(Custom { kind: InvalidData, error: \"section end \\\"-----END RSA PRIVATE KEY-----\\\" missing\" })"
);
assert_eq!(
check_slice(input),
Err(Error::MissingSectionEnd {
end_marker: b"-----END RSA PRIVATE KEY-----".to_vec()
})
)
}

#[test]
fn rejects_bad_start() {
let input = b"-----BEGIN RSA PRIVATE KEY----\n\
qw\n\
-----END RSA PRIVATE KEY-----\n";
assert_eq!(
format!("{:?}",
check(b"-----BEGIN RSA PRIVATE KEY----\n\
qw\n\
-----END RSA PRIVATE KEY-----\n")),
check_io(input)),
"Err(Custom { kind: InvalidData, error: \"illegal section start: \\\"-----BEGIN RSA PRIVATE KEY----\\\\n\\\"\" })"
);
assert_eq!(
check_slice(input),
Err(Error::IllegalSectionStart {
line: b"-----BEGIN RSA PRIVATE KEY----".to_vec()
})
)
}

#[test]
fn skips_unrecognised_section() {
assert_eq!(
check(
check_both(
b"junk\n\
-----BEGIN BREAKFAST CLUB-----\n\
qw\n\
-----END BREAKFAST CLUB-----\n"
)
.unwrap(),
),
vec![]
);
}

fn check_both(data: &[u8]) -> Vec<Item> {
let mut reader = std::io::BufReader::new(data);
let io_outcome = crate::read_all(&mut reader)
.collect::<Result<Vec<_>, _>>()
.unwrap();
let slice_outcome = check_slice(data).unwrap();

assert_eq!(io_outcome, slice_outcome);

io_outcome
}

fn check_slice(mut data: &[u8]) -> Result<Vec<Item>, Error> {
let mut items = vec![];
while let Some((item, rest)) = crate::read_one_from_slice(data)? {
items.push(item);
data = rest;
}

Ok(items)
}

fn check_io(data: &[u8]) -> Result<Vec<Item>, std::io::Error> {
let mut reader = std::io::BufReader::new(data);
crate::read_all(&mut reader).collect()
}
}

0 comments on commit 0227a3a

Please sign in to comment.