pem-3.0.4/.cargo_vcs_info.json0000644000000001360000000000100116210ustar { "git": { "sha1": "81f9c18c78a9a5b67301c36434ab265da7013b98" }, "path_in_vcs": "" }pem-3.0.4/.github/workflows/ci.yml000064400000000000000000000017250072674642500151610ustar 00000000000000name: ci on: pull_request: push: branches: - master jobs: msrv: name: msrv runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: toolchain: 1.60.0 components: clippy, rustfmt - run: cargo fmt --check - run: cargo clippy --all-features -- --deny warnings # For no_std we use check instead of test because # std is required for the test suite to run. - run: cargo clippy --no-default-features -- --deny warnings - run: cargo check --no-default-features # Ensure that serde support works without std - run: cargo check --no-default-features --features serde stable: name: stable runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: toolchain: stable - run: cargo test --all-features - run: cargo bench --all-features pem-3.0.4/.gitignore000064400000000000000000000000450072674642500124300ustar 00000000000000target Cargo.lock *.bk .*.swp .idea/ pem-3.0.4/CHANGELOG.md000064400000000000000000000020320072674642500122470ustar 00000000000000# 3.0.3 - allow general whitespace separators instead of just newlines # 3.0.2 - allow EncodeConfig to be built in a const context # 3.0.1 - reduce allocations in `pem::encode` # 3.0.0 - trim `proptest` features to prevent an MSRV break for testing - make EncodeConfig struct extendable and add a line_wrap config option # 2.0.1 - Fix serde support on no\_std - Drop MSRV to 1.60 # 2.0 - Add no\_std support - Bump MSRV to 1.67 - Refactor API to prevent direct modification and access of elements and to allow access to the optional rfc1421-described headers. # 1.1.1 - Allow PEM files to be parsed with the optional rfc1421-described headers (although you cannot retrieve the headers) # 1.1.0 - Add optional serde support # 1.0.2 - Remove dependency on Regex in favor of a hand-rolled parser # 1.0.1 - hide the ASCII\_ARMOR symbol to work around a linking issue with 32-bit windows builds # 1.0 - `pem::parse_many` now returns a `Result>` instead of a `Vec` that silently discarded invalid sections. pem-3.0.4/Cargo.toml0000644000000027250000000000100076250ustar # 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 are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.60.0" name = "pem" version = "3.0.4" authors = ["Jonathan Creekmore "] description = "Parse and encode PEM-encoded data." homepage = "https://github.com/jcreekmore/pem-rs.git" documentation = "https://docs.rs/pem/" readme = "README.md" keywords = [ "no-std", "no_std", "pem", ] categories = ["cryptography"] license = "MIT" repository = "https://github.com/jcreekmore/pem-rs.git" resolver = "2" [[bench]] name = "pem_benchmark" harness = false [dependencies.base64] version = "0.22.0" features = ["alloc"] default-features = false [dependencies.serde] version = "1" optional = true default-features = false [dev-dependencies.criterion] version = "0.3.0" [dev-dependencies.proptest] version = "1" features = ["std"] default-features = false [dev-dependencies.serde_json] version = "1" [features] default = ["std"] serde = ["dep:serde"] std = [ "base64/std", "serde?/std", ] [badges.travis-ci] repository = "jcreekmore/pem-rs" pem-3.0.4/Cargo.toml.orig000064400000000000000000000020360072674642500133310ustar 00000000000000[package] authors = ["Jonathan Creekmore "] description = "Parse and encode PEM-encoded data." documentation = "https://docs.rs/pem/" homepage = "https://github.com/jcreekmore/pem-rs.git" license = "MIT" name = "pem" readme = "README.md" repository = "https://github.com/jcreekmore/pem-rs.git" version = "3.0.4" categories = [ "cryptography" ] keywords = [ "no-std", "no_std", "pem", ] edition = "2021" rust-version = "1.60.0" [features] default = ["std"] std = [ "base64/std", # enable serde's std feature iff the serde and std features are both activated "serde?/std", ] serde = ["dep:serde"] [dependencies.base64] version = "0.22.0" default-features = false features = ["alloc"] [dependencies.serde] version = "1" default-features = false optional = true [dev-dependencies] criterion = "0.3.0" proptest = { version = "1", default-features = false, features = ["std"] } serde_json = "1" [[bench]] name = "pem_benchmark" harness = false [badges] travis-ci = { repository = "jcreekmore/pem-rs" } pem-3.0.4/LICENSE.md000064400000000000000000000020750072674642500120510ustar 00000000000000The MIT License (MIT) Copyright (c) 2016 Jonathan Creekmore 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. pem-3.0.4/README.md000064400000000000000000000017610072674642500117250ustar 00000000000000pem === A Rust library for parsing and encoding PEM-encoded data. ![Build Status](https://github.com/github/docs/actions/workflows/ci.yml/badge.svg) ### Documentation [Module documentation with examples](https://docs.rs/pem/) ### Usage Add this to your `Cargo.toml`: ```toml [dependencies] pem = "3.0" ``` Here is a simple example that parse PEM-encoded data and prints the tag: ```rust use pem::parse; const SAMPLE: &'static str = "-----BEGIN RSA PRIVATE KEY----- MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ -----END RSA PRIVATE KEY----- "; let pem = parse(SAMPLE)?; println!("PEM tag: {}", pem.tag); ``` pem-3.0.4/benches/pem_benchmark.rs000064400000000000000000000157630072674642500152250ustar 00000000000000use criterion::{criterion_group, criterion_main, Criterion}; const SAMPLE: &str = "-----BEGIN RSA PRIVATE KEY-----\r MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc\r dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO\r 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei\r AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un\r DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT\r TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh\r ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ\r -----END RSA PRIVATE KEY-----\r \r -----BEGIN RSA PUBLIC KEY-----\r MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo\r QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0\r RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI\r sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk\r ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6\r /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g\r RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg\r -----END RSA PUBLIC KEY-----\r "; fn pem_parse() { pem::parse(SAMPLE).unwrap(); } fn pem_parse_many() { pem::parse_many(SAMPLE).unwrap(); } fn pem_encode(pem: &pem::Pem) { pem::encode(pem); } fn pem_encode_many(pems: &[pem::Pem]) { pem::encode_many(pems); } fn criterion_benchmark(c: &mut Criterion) { // Parse c.bench_function("pem::parse", |b| b.iter(pem_parse)); c.bench_function("pem::parse_many", |b| b.iter(pem_parse_many)); // Encode let pem = pem::Pem::new( "RSA PRIVATE KEY", [ 48, 130, 1, 61, 2, 1, 0, 2, 65, 0, 235, 31, 139, 144, 6, 98, 23, 81, 179, 252, 122, 171, 145, 251, 145, 39, 49, 3, 66, 179, 206, 167, 186, 88, 142, 160, 127, 175, 173, 115, 162, 47, 41, 2, 104, 230, 180, 28, 117, 101, 146, 67, 73, 209, 26, 221, 161, 56, 240, 206, 251, 126, 77, 42, 20, 4, 140, 20, 49, 62, 31, 239, 238, 125, 2, 3, 1, 0, 1, 2, 65, 0, 225, 154, 4, 12, 174, 195, 66, 2, 200, 67, 114, 228, 219, 14, 218, 9, 19, 211, 64, 22, 77, 44, 204, 245, 153, 236, 208, 119, 157, 99, 125, 114, 17, 166, 228, 184, 91, 235, 48, 33, 227, 177, 193, 5, 238, 237, 216, 232, 240, 123, 2, 125, 200, 206, 138, 1, 29, 200, 108, 20, 55, 162, 0, 1, 2, 33, 0, 255, 37, 168, 93, 119, 52, 121, 43, 112, 58, 175, 52, 226, 118, 96, 31, 212, 240, 109, 170, 46, 246, 247, 161, 39, 90, 255, 22, 190, 151, 192, 1, 2, 33, 0, 235, 232, 192, 133, 217, 35, 133, 39, 13, 120, 224, 103, 215, 164, 185, 72, 217, 55, 225, 148, 65, 16, 21, 150, 65, 4, 162, 17, 139, 87, 46, 125, 2, 33, 0, 247, 69, 109, 168, 55, 80, 185, 101, 169, 188, 149, 244, 112, 205, 60, 87, 93, 45, 45, 122, 211, 77, 49, 172, 18, 210, 19, 137, 221, 104, 128, 1, 2, 33, 0, 192, 104, 89, 125, 209, 152, 18, 229, 195, 43, 47, 124, 2, 121, 171, 85, 43, 53, 84, 154, 226, 102, 7, 245, 64, 154, 82, 197, 20, 108, 202, 209, 2, 33, 0, 210, 70, 115, 176, 5, 147, 135, 242, 20, 63, 206, 123, 56, 112, 24, 118, 214, 154, 143, 210, 249, 183, 222, 2, 34, 33, 217, 43, 54, 23, 18, 137, ], ); c.bench_function("pem::encode", move |b| b.iter(|| pem_encode(&pem))); let pems = vec![ pem::Pem::new( "RSA PRIVATE KEY", vec![ 48, 130, 1, 61, 2, 1, 0, 2, 65, 0, 235, 31, 139, 144, 6, 98, 23, 81, 179, 252, 122, 171, 145, 251, 145, 39, 49, 3, 66, 179, 206, 167, 186, 88, 142, 160, 127, 175, 173, 115, 162, 47, 41, 2, 104, 230, 180, 28, 117, 101, 146, 67, 73, 209, 26, 221, 161, 56, 240, 206, 251, 126, 77, 42, 20, 4, 140, 20, 49, 62, 31, 239, 238, 125, 2, 3, 1, 0, 1, 2, 65, 0, 225, 154, 4, 12, 174, 195, 66, 2, 200, 67, 114, 228, 219, 14, 218, 9, 19, 211, 64, 22, 77, 44, 204, 245, 153, 236, 208, 119, 157, 99, 125, 114, 17, 166, 228, 184, 91, 235, 48, 33, 227, 177, 193, 5, 238, 237, 216, 232, 240, 123, 2, 125, 200, 206, 138, 1, 29, 200, 108, 20, 55, 162, 0, 1, 2, 33, 0, 255, 37, 168, 93, 119, 52, 121, 43, 112, 58, 175, 52, 226, 118, 96, 31, 212, 240, 109, 170, 46, 246, 247, 161, 39, 90, 255, 22, 190, 151, 192, 1, 2, 33, 0, 235, 232, 192, 133, 217, 35, 133, 39, 13, 120, 224, 103, 215, 164, 185, 72, 217, 55, 225, 148, 65, 16, 21, 150, 65, 4, 162, 17, 139, 87, 46, 125, 2, 33, 0, 247, 69, 109, 168, 55, 80, 185, 101, 169, 188, 149, 244, 112, 205, 60, 87, 93, 45, 45, 122, 211, 77, 49, 172, 18, 210, 19, 137, 221, 104, 128, 1, 2, 33, 0, 192, 104, 89, 125, 209, 152, 18, 229, 195, 43, 47, 124, 2, 121, 171, 85, 43, 53, 84, 154, 226, 102, 7, 245, 64, 154, 82, 197, 20, 108, 202, 209, 2, 33, 0, 210, 70, 115, 176, 5, 147, 135, 242, 20, 63, 206, 123, 56, 112, 24, 118, 214, 154, 143, 210, 249, 183, 222, 2, 34, 33, 217, 43, 54, 23, 18, 137, ], ), pem::Pem::new( "RSA PUBLIC KEY", vec![ 48, 130, 1, 58, 2, 1, 0, 2, 65, 0, 194, 30, 10, 121, 253, 27, 254, 224, 217, 158, 137, 250, 161, 206, 19, 101, 194, 44, 187, 143, 162, 30, 77, 29, 51, 182, 22, 111, 143, 111, 48, 111, 105, 240, 104, 210, 134, 40, 65, 85, 114, 237, 250, 243, 198, 99, 170, 199, 168, 136, 100, 28, 83, 201, 240, 16, 168, 19, 206, 220, 26, 249, 244, 19, 2, 3, 1, 0, 1, 2, 64, 95, 227, 227, 28, 251, 177, 118, 168, 176, 23, 166, 229, 78, 68, 180, 68, 82, 53, 50, 185, 243, 69, 176, 166, 58, 67, 58, 182, 5, 78, 209, 199, 122, 175, 150, 120, 108, 49, 139, 186, 193, 253, 202, 50, 61, 136, 143, 56, 128, 244, 99, 43, 64, 51, 202, 101, 197, 152, 17, 174, 68, 65, 200, 177, 2, 33, 0, 242, 159, 226, 154, 126, 62, 187, 238, 182, 10, 182, 107, 44, 160, 149, 230, 66, 132, 26, 245, 12, 46, 23, 158, 123, 159, 4, 219, 90, 179, 12, 175, 2, 33, 0, 204, 209, 149, 42, 3, 111, 206, 232, 100, 138, 58, 20, 92, 128, 235, 184, 148, 4, 24, 81, 175, 101, 59, 34, 212, 61, 145, 122, 21, 226, 79, 221, 2, 33, 0, 184, 28, 228, 20, 96, 160, 178, 116, 30, 232, 90, 45, 82, 28, 58, 36, 21, 109, 178, 23, 42, 58, 255, 233, 95, 221, 248, 13, 140, 242, 58, 57, 2, 32, 80, 249, 147, 21, 119, 34, 95, 20, 245, 154, 200, 119, 128, 82, 223, 222, 171, 118, 42, 255, 48, 110, 191, 64, 119, 212, 151, 141, 66, 213, 164, 105, 2, 32, 31, 160, 71, 49, 215, 210, 89, 9, 151, 212, 173, 178, 23, 127, 236, 102, 237, 235, 159, 208, 98, 175, 175, 251, 27, 192, 121, 61, 2, 163, 34, 32, ], ), ]; c.bench_function("pem::encode_many", move |b| { b.iter(|| pem_encode_many(&pems)) }); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); pem-3.0.4/src/errors.rs000064400000000000000000000040670072674642500131210ustar 00000000000000// Copyright 2017 Jonathan Creekmore // // Licensed under the MIT license . This file may not be // copied, modified, or distributed except according to those terms. use core::fmt; #[cfg(any(feature = "std", test))] use std::error::Error; #[cfg(not(any(feature = "std", test)))] use alloc::string::String; /// The `pem` error type. #[derive(Debug, Eq, PartialEq)] #[allow(missing_docs)] pub enum PemError { MismatchedTags(String, String), MalformedFraming, MissingBeginTag, MissingEndTag, MissingData, InvalidData(::base64::DecodeError), InvalidHeader(String), NotUtf8(::core::str::Utf8Error), } impl fmt::Display for PemError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { PemError::MismatchedTags(b, e) => { write!(f, "mismatching BEGIN (\"{b}\") and END (\"{e}\") tags") } PemError::MalformedFraming => write!(f, "malformedframing"), PemError::MissingBeginTag => write!(f, "missing BEGIN tag"), PemError::MissingEndTag => write!(f, "missing END tag"), PemError::MissingData => write!(f, "missing data"), PemError::InvalidData(e) => write!(f, "invalid data: {e}"), PemError::InvalidHeader(hdr) => write!(f, "invalid header: {hdr}"), PemError::NotUtf8(e) => write!(f, "invalid utf-8 value: {e}"), } } } #[cfg(any(feature = "std", test))] impl Error for PemError { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { // Errors originating from other libraries. PemError::InvalidData(e) => Some(e), PemError::NotUtf8(e) => Some(e), // Errors directly originating from `pem-rs`. _ => None, } } } /// The `pem` result type. pub type Result = ::core::result::Result; #[allow(missing_docs)] #[macro_export] macro_rules! ensure { ($cond:expr, $err:expr) => { if !$cond { return Err($err); } }; } pem-3.0.4/src/lib.rs000064400000000000000000001073770072674642500123630ustar 00000000000000// Copyright 2016-2017 Jonathan Creekmore // // Licensed under the MIT license . This file may not be // copied, modified, or distributed except according to those terms. //! This crate provides a parser and encoder for PEM-encoded binary data. //! PEM-encoded binary data is essentially a beginning and matching end //! tag that encloses base64-encoded binary data (see: //! https://en.wikipedia.org/wiki/Privacy-enhanced_Electronic_Mail). //! //! This crate's documentation provides a few simple examples along with //! documentation on the public methods for the crate. //! //! # Usage //! //! This crate is [on crates.io](https://crates.io/crates/pem) and can be used //! by adding `pem` to your dependencies in your project's `Cargo.toml`. //! //! ```toml //! [dependencies] //! pem = "2" //! ``` //! //! Using the `serde` feature will implement the serde traits for //! the `Pem` struct. //! //! # Example: parse a single chunk of PEM-encoded text //! //! Generally, PEM-encoded files contain a single chunk of PEM-encoded //! text. Commonly, this is in some sort of a key file or an x.509 //! certificate. //! //! ```rust //! //! use pem::parse; //! //! const SAMPLE: &'static str = "-----BEGIN RSA PRIVATE KEY----- //! MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc //! dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO //! 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei //! AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un //! DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT //! TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh //! ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ //! -----END RSA PRIVATE KEY----- //! "; //! //! let pem = parse(SAMPLE).unwrap(); //! assert_eq!(pem.tag(), "RSA PRIVATE KEY"); //! ``` //! //! # Example: parse a set of PEM-encoded text //! //! Sometimes, PEM-encoded files contain multiple chunks of PEM-encoded //! text. You might see this if you have an x.509 certificate file that //! also includes intermediate certificates. //! //! ```rust //! //! use pem::parse_many; //! //! const SAMPLE: &'static str = "-----BEGIN INTERMEDIATE CERT----- //! MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc //! dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO //! 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei //! AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un //! DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT //! TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh //! ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ //! -----END INTERMEDIATE CERT----- //! //! -----BEGIN CERTIFICATE----- //! MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc //! dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO //! 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei //! AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un //! DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT //! TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh //! ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ //! -----END CERTIFICATE----- //! "; //! //! let pems = parse_many(SAMPLE).unwrap(); //! assert_eq!(pems.len(), 2); //! assert_eq!(pems[0].tag(), "INTERMEDIATE CERT"); //! assert_eq!(pems[1].tag(), "CERTIFICATE"); //! ``` //! //! # Features //! //! This crate supports two features: `std` and `serde`. //! //! The `std` feature is enabled by default. If you specify //! `default-features = false` to disable `std`, be aware that //! this crate still needs an allocator. //! //! The `serde` feature implements `serde::{Deserialize, Serialize}` //! for this crate's `Pem` struct. #![deny( missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts, trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces, unused_qualifications )] #![cfg_attr(not(feature = "std"), no_std)] #[cfg(not(any(feature = "std", test)))] extern crate alloc; #[cfg(not(any(feature = "std", test)))] use alloc::{ format, string::{String, ToString}, vec::Vec, }; mod errors; mod parser; use parser::{parse_captures, parse_captures_iter, Captures}; pub use crate::errors::{PemError, Result}; use base64::Engine as _; use core::fmt::Write; use core::{fmt, slice, str}; /// The line length for PEM encoding const LINE_WRAP: usize = 64; /// Enum describing line endings #[derive(Debug, Clone, Copy)] pub enum LineEnding { /// Windows-like (`\r\n`) CRLF, /// Unix-like (`\n`) LF, } /// Configuration for Pem encoding #[derive(Debug, Clone, Copy)] pub struct EncodeConfig { /// Line ending to use during encoding line_ending: LineEnding, /// Line length to use during encoding line_wrap: usize, } /// A representation of Pem-encoded data #[derive(PartialEq, Debug, Clone)] pub struct Pem { tag: String, headers: HeaderMap, contents: Vec, } /// Provides access to the headers that might be found in a Pem-encoded file #[derive(Clone, Debug, Default, PartialEq)] pub struct HeaderMap(Vec); fn decode_data(raw_data: &str) -> Result> { // We need to get rid of newlines/whitespaces for base64::decode // As base64 requires an AsRef<[u8]>, this must involve a copy let data: String = raw_data.chars().filter(|c| !c.is_whitespace()).collect(); // And decode it from Base64 into a vector of u8 let contents = base64::engine::general_purpose::STANDARD .decode(data) .map_err(PemError::InvalidData)?; Ok(contents) } /// Iterator across all headers in the Pem-encoded data #[derive(Debug)] pub struct HeadersIter<'a> { cur: slice::Iter<'a, String>, } impl<'a> Iterator for HeadersIter<'a> { type Item = (&'a str, &'a str); fn next(&mut self) -> Option { self.cur.next().and_then(HeaderMap::split_header) } } impl<'a> DoubleEndedIterator for HeadersIter<'a> { fn next_back(&mut self) -> Option { self.cur.next_back().and_then(HeaderMap::split_header) } } impl HeaderMap { #[allow(clippy::ptr_arg)] fn split_header(header: &String) -> Option<(&str, &str)> { header .split_once(':') .map(|(key, value)| (key.trim(), value.trim())) } fn parse(headers: Vec) -> Result { headers.iter().try_for_each(|hline| { Self::split_header(hline) .map(|_| ()) .ok_or_else(|| PemError::InvalidHeader(hline.to_string())) })?; Ok(HeaderMap(headers)) } /// Get an iterator across all header key-value pairs pub fn iter(&self) -> HeadersIter<'_> { HeadersIter { cur: self.0.iter() } } /// Get the last set value corresponding to the header key pub fn get(&self, key: &str) -> Option<&str> { self.iter().rev().find(|(k, _)| *k == key).map(|(_, v)| v) } /// Get the last set value corresponding to the header key pub fn add(&mut self, key: &str, value: &str) -> Result<()> { ensure!( !(key.contains(':') || key.contains('\n')), PemError::InvalidHeader(key.to_string()) ); ensure!( !(value.contains(':') || value.contains('\n')), PemError::InvalidHeader(value.to_string()) ); self.0.push(format!("{}: {}", key.trim(), value.trim())); Ok(()) } } impl EncodeConfig { /// Create a new encode config with default values. pub const fn new() -> Self { Self { line_ending: LineEnding::CRLF, line_wrap: LINE_WRAP, } } /// Set the line ending to use for the encoding. pub const fn set_line_ending(mut self, line_ending: LineEnding) -> Self { self.line_ending = line_ending; self } /// Set the line length to use for the encoding. pub const fn set_line_wrap(mut self, line_wrap: usize) -> Self { self.line_wrap = line_wrap; self } } impl Default for EncodeConfig { fn default() -> Self { Self::new() } } impl Pem { /// Create a new Pem struct pub fn new(tag: impl ToString, contents: impl Into>) -> Pem { Pem { tag: tag.to_string(), headers: HeaderMap::default(), contents: contents.into(), } } /// Get the tag extracted from the Pem-encoded data pub fn tag(&self) -> &str { &self.tag } /// Get the binary contents extracted from the Pem-encoded data pub fn contents(&self) -> &[u8] { &self.contents } /// Consume the Pem struct to get an owned copy of the binary contents pub fn into_contents(self) -> Vec { self.contents } /// Get the header map for the headers in the Pem-encoded data pub fn headers(&self) -> &HeaderMap { &self.headers } /// Get the header map for modification pub fn headers_mut(&mut self) -> &mut HeaderMap { &mut self.headers } fn new_from_captures(caps: Captures) -> Result { fn as_utf8(bytes: &[u8]) -> Result<&str> { str::from_utf8(bytes).map_err(PemError::NotUtf8) } // Verify that the begin section exists let tag = as_utf8(caps.begin)?; if tag.is_empty() { return Err(PemError::MissingBeginTag); } // as well as the end section let tag_end = as_utf8(caps.end)?; if tag_end.is_empty() { return Err(PemError::MissingEndTag); } // The beginning and the end sections must match if tag != tag_end { return Err(PemError::MismatchedTags(tag.into(), tag_end.into())); } // If they did, then we can grab the data section let raw_data = as_utf8(caps.data)?; let contents = decode_data(raw_data)?; let headers: Vec = as_utf8(caps.headers)?.lines().map(str::to_string).collect(); let headers = HeaderMap::parse(headers)?; let mut file = Pem::new(tag, contents); file.headers = headers; Ok(file) } } impl str::FromStr for Pem { type Err = PemError; fn from_str(s: &str) -> Result { parse(s) } } impl TryFrom<&[u8]> for Pem { type Error = PemError; fn try_from(s: &[u8]) -> Result { parse(s) } } impl fmt::Display for Pem { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", encode(self)) } } /// Parses a single PEM-encoded data from a data-type that can be dereferenced as a [u8]. /// /// # Example: parse PEM-encoded data from a Vec /// ```rust /// /// use pem::parse; /// /// const SAMPLE: &'static str = "-----BEGIN RSA PRIVATE KEY----- /// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc /// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO /// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei /// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un /// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT /// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh /// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ /// -----END RSA PRIVATE KEY----- /// "; /// let SAMPLE_BYTES: Vec = SAMPLE.into(); /// /// let pem = parse(SAMPLE_BYTES).unwrap(); /// assert_eq!(pem.tag(), "RSA PRIVATE KEY"); /// ``` /// /// # Example: parse PEM-encoded data from a String /// ```rust /// /// use pem::parse; /// /// const SAMPLE: &'static str = "-----BEGIN RSA PRIVATE KEY----- /// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc /// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO /// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei /// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un /// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT /// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh /// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ /// -----END RSA PRIVATE KEY----- /// "; /// let SAMPLE_STRING: String = SAMPLE.into(); /// /// let pem = parse(SAMPLE_STRING).unwrap(); /// assert_eq!(pem.tag(), "RSA PRIVATE KEY"); /// ``` pub fn parse>(input: B) -> Result { parse_captures(input.as_ref()) .ok_or(PemError::MalformedFraming) .and_then(Pem::new_from_captures) } /// Parses a set of PEM-encoded data from a data-type that can be dereferenced as a [u8]. /// /// # Example: parse a set of PEM-encoded data from a Vec /// /// ```rust /// /// use pem::parse_many; /// /// const SAMPLE: &'static str = "-----BEGIN INTERMEDIATE CERT----- /// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc /// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO /// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei /// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un /// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT /// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh /// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ /// -----END INTERMEDIATE CERT----- /// /// -----BEGIN CERTIFICATE----- /// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc /// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO /// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei /// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un /// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT /// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh /// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ /// -----END CERTIFICATE----- /// "; /// let SAMPLE_BYTES: Vec = SAMPLE.into(); /// /// let pems = parse_many(SAMPLE_BYTES).unwrap(); /// assert_eq!(pems.len(), 2); /// assert_eq!(pems[0].tag(), "INTERMEDIATE CERT"); /// assert_eq!(pems[1].tag(), "CERTIFICATE"); /// ``` /// /// # Example: parse a set of PEM-encoded data from a String /// /// ```rust /// /// use pem::parse_many; /// /// const SAMPLE: &'static str = "-----BEGIN INTERMEDIATE CERT----- /// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc /// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO /// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei /// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un /// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT /// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh /// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ /// -----END INTERMEDIATE CERT----- /// /// -----BEGIN CERTIFICATE----- /// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc /// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO /// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei /// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un /// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT /// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh /// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ /// -----END CERTIFICATE----- /// "; /// let SAMPLE_STRING: Vec = SAMPLE.into(); /// /// let pems = parse_many(SAMPLE_STRING).unwrap(); /// assert_eq!(pems.len(), 2); /// assert_eq!(pems[0].tag(), "INTERMEDIATE CERT"); /// assert_eq!(pems[1].tag(), "CERTIFICATE"); /// ``` pub fn parse_many>(input: B) -> Result> { // Each time our regex matches a PEM section, we need to decode it. parse_captures_iter(input.as_ref()) .map(Pem::new_from_captures) .collect() } /// Encode a PEM struct into a PEM-encoded data string /// /// # Example /// ```rust /// use pem::{Pem, encode}; /// /// let pem = Pem::new("FOO", [1, 2, 3, 4]); /// encode(&pem); /// ``` pub fn encode(pem: &Pem) -> String { encode_config(pem, EncodeConfig::default()) } /// Encode a PEM struct into a PEM-encoded data string with additional /// configuration options /// /// # Example /// ```rust /// use pem::{Pem, encode_config, EncodeConfig, LineEnding}; /// /// let pem = Pem::new("FOO", [1, 2, 3, 4]); /// encode_config(&pem, EncodeConfig::new().set_line_ending(LineEnding::LF)); /// ``` pub fn encode_config(pem: &Pem, config: EncodeConfig) -> String { let line_ending = match config.line_ending { LineEnding::CRLF => "\r\n", LineEnding::LF => "\n", }; let mut output = String::new(); let contents = if pem.contents.is_empty() { String::from("") } else { base64::engine::general_purpose::STANDARD.encode(&pem.contents) }; write!(output, "-----BEGIN {}-----{}", pem.tag, line_ending).unwrap(); if !pem.headers.0.is_empty() { for line in &pem.headers.0 { write!(output, "{}{}", line.trim(), line_ending).unwrap(); } output.push_str(line_ending); } for c in contents.as_bytes().chunks(config.line_wrap) { write!(output, "{}{}", str::from_utf8(c).unwrap(), line_ending).unwrap(); } write!(output, "-----END {}-----{}", pem.tag, line_ending).unwrap(); output } /// Encode multiple PEM structs into a PEM-encoded data string /// /// # Example /// ```rust /// use pem::{Pem, encode_many}; /// /// let data = vec![ /// Pem::new("FOO", [1, 2, 3, 4]), /// Pem::new("BAR", [5, 6, 7, 8]), /// ]; /// encode_many(&data); /// ``` pub fn encode_many(pems: &[Pem]) -> String { pems.iter() .map(encode) .collect::>() .join("\r\n") } /// Encode multiple PEM structs into a PEM-encoded data string with additional /// configuration options /// /// Same config will be used for each PEM struct. /// /// # Example /// ```rust /// use pem::{Pem, encode_many_config, EncodeConfig, LineEnding}; /// /// let data = vec![ /// Pem::new("FOO", [1, 2, 3, 4]), /// Pem::new("BAR", [5, 6, 7, 8]), /// ]; /// encode_many_config(&data, EncodeConfig::new().set_line_ending(LineEnding::LF)); /// ``` pub fn encode_many_config(pems: &[Pem], config: EncodeConfig) -> String { let line_ending = match config.line_ending { LineEnding::CRLF => "\r\n", LineEnding::LF => "\n", }; pems.iter() .map(|value| encode_config(value, config)) .collect::>() .join(line_ending) } #[cfg(feature = "serde")] mod serde_impl { use super::{encode, parse, Pem}; use core::fmt; use serde::{ de::{Error, Visitor}, Deserialize, Serialize, }; impl Serialize for Pem { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_str(&encode(self)) } } struct PemVisitor; impl<'de> Visitor<'de> for PemVisitor { type Value = Pem; fn expecting(&self, _formatter: &mut fmt::Formatter) -> fmt::Result { Ok(()) } fn visit_str(self, v: &str) -> Result where E: Error, { parse(v).map_err(Error::custom) } } impl<'de> Deserialize<'de> for Pem { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { deserializer.deserialize_str(PemVisitor) } } } #[cfg(test)] mod test { use super::*; use proptest::prelude::*; use std::error::Error; const SAMPLE_CRLF: &str = "-----BEGIN RSA PRIVATE KEY-----\r MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc\r dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO\r 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei\r AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un\r DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT\r TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh\r ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ\r -----END RSA PRIVATE KEY-----\r \r -----BEGIN RSA PUBLIC KEY-----\r MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo\r QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0\r RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI\r sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk\r ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6\r /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g\r RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg\r -----END RSA PUBLIC KEY-----\r "; const SAMPLE_LF: &str = "-----BEGIN RSA PRIVATE KEY----- MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ -----END RSA PRIVATE KEY----- -----BEGIN RSA PUBLIC KEY----- MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg -----END RSA PUBLIC KEY----- "; const SAMPLE_WS: &str = "-----BEGIN RSA PRIVATE KEY----- MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc \ dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO \ 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei \ AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un \ DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT \ TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh \ ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ -----END RSA PRIVATE KEY----- -----BEGIN RSA PUBLIC KEY----- MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo \ QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 \ RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI \ sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk \ ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 \ /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g \ RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg -----END RSA PUBLIC KEY----- "; const SAMPLE_DEFAULT_LINE_WRAP: &str = "-----BEGIN TEST-----\r AQIDBA==\r -----END TEST-----\r "; const SAMPLE_CUSTOM_LINE_WRAP_4: &str = "-----BEGIN TEST-----\r AQID\r BA==\r -----END TEST-----\r "; #[test] fn test_parse_works() { let pem = parse(SAMPLE_CRLF).unwrap(); assert_eq!(pem.tag(), "RSA PRIVATE KEY"); } #[test] fn test_parse_empty_space() { let pem = parse(SAMPLE_WS).unwrap(); assert_eq!(pem.tag(), "RSA PRIVATE KEY"); } #[test] fn test_parse_invalid_framing() { let input = "--BEGIN data----- -----END data-----"; assert_eq!(parse(input), Err(PemError::MalformedFraming)); } #[test] fn test_parse_invalid_begin() { let input = "-----BEGIN ----- MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg -----END RSA PUBLIC KEY-----"; assert_eq!(parse(input), Err(PemError::MissingBeginTag)); } #[test] fn test_parse_invalid_end() { let input = "-----BEGIN DATA----- MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg -----END -----"; assert_eq!(parse(input), Err(PemError::MissingEndTag)); } #[test] fn test_parse_invalid_data() { let input = "-----BEGIN DATA----- MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oY? QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg -----END DATA-----"; match parse(input) { Err(e @ PemError::InvalidData(_)) => { assert_eq!( &format!("{}", e.source().unwrap()), "Invalid symbol 63, offset 63." ); } _ => unreachable!(), } } #[test] fn test_parse_empty_data() { let input = "-----BEGIN DATA----- -----END DATA-----"; let pem = parse(input).unwrap(); assert_eq!(pem.contents().len(), 0); } #[test] fn test_parse_many_works() { let pems = parse_many(SAMPLE_CRLF).unwrap(); assert_eq!(pems.len(), 2); assert_eq!(pems[0].tag(), "RSA PRIVATE KEY"); assert_eq!(pems[1].tag(), "RSA PUBLIC KEY"); } #[test] fn test_parse_many_errors_on_invalid_section() { let input = SAMPLE_LF.to_owned() + "-----BEGIN -----\n-----END -----"; assert_eq!(parse_many(input), Err(PemError::MissingBeginTag)); } #[test] fn test_encode_default_line_wrap() { let pem = Pem::new("TEST", vec![1, 2, 3, 4]); assert_eq!(encode(&pem), SAMPLE_DEFAULT_LINE_WRAP); } #[test] fn test_encode_custom_line_wrap_4() { let pem = Pem::new("TEST", vec![1, 2, 3, 4]); assert_eq!( encode_config(&pem, EncodeConfig::default().set_line_wrap(4)), SAMPLE_CUSTOM_LINE_WRAP_4 ); } #[test] fn test_encode_empty_contents() { let pem = Pem::new("FOO", vec![]); let encoded = encode(&pem); assert!(!encoded.is_empty()); let pem_out = parse(&encoded).unwrap(); assert_eq!(&pem, &pem_out); } #[test] fn test_encode_contents() { let pem = Pem::new("FOO", [1, 2, 3, 4]); let encoded = encode(&pem); assert!(!encoded.is_empty()); let pem_out = parse(&encoded).unwrap(); assert_eq!(&pem, &pem_out); } #[test] fn test_encode_many() { let pems = parse_many(SAMPLE_CRLF).unwrap(); let encoded = encode_many(&pems); assert_eq!(SAMPLE_CRLF, encoded); } #[test] fn test_encode_config_contents() { let pem = Pem::new("FOO", [1, 2, 3, 4]); let config = EncodeConfig::default().set_line_ending(LineEnding::LF); let encoded = encode_config(&pem, config); assert!(!encoded.is_empty()); let pem_out = parse(&encoded).unwrap(); assert_eq!(&pem, &pem_out); } #[test] fn test_encode_many_config() { let pems = parse_many(SAMPLE_LF).unwrap(); let config = EncodeConfig::default().set_line_ending(LineEnding::LF); let encoded = encode_many_config(&pems, config); assert_eq!(SAMPLE_LF, encoded); } #[cfg(feature = "serde")] #[test] fn test_serde() { let pem = Pem::new("Mock tag", "Mock contents".as_bytes()); let value = serde_json::to_string_pretty(&pem).unwrap(); let result = serde_json::from_str(&value).unwrap(); assert_eq!(pem, result); } const HEADER_CRLF: &str = "-----BEGIN CERTIFICATE-----\r MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc\r dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO\r 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei\r AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un\r DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT\r TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh\r ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ\r -----END CERTIFICATE-----\r -----BEGIN RSA PRIVATE KEY-----\r Proc-Type: 4,ENCRYPTED\r DEK-Info: AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6\r \r MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo\r QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0\r RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI\r sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk\r ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6\r /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g\r RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg\r -----END RSA PRIVATE KEY-----\r "; const HEADER_CRLF_DATA: [&str; 2] = [ "MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc\r dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO\r 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei\r AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un\r DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT\r TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh\r ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ\r", "MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo\r QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0\r RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI\r sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk\r ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6\r /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g\r RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg\r", ]; const HEADER_LF: &str = "-----BEGIN CERTIFICATE----- MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ -----END CERTIFICATE----- -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6 MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg -----END RSA PRIVATE KEY----- "; const HEADER_LF_DATA: [&str; 2] = [ "MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ", "MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg", ]; fn cmp_data(left: &[u8], right: &[u8]) -> bool { if left.len() != right.len() { false } else { left.iter() .zip(right.iter()) .all(|(left, right)| left == right) } } #[test] fn test_parse_many_with_headers_crlf() { let pems = parse_many(HEADER_CRLF).unwrap(); assert_eq!(pems.len(), 2); assert_eq!(pems[0].tag(), "CERTIFICATE"); assert!(cmp_data( pems[0].contents(), &decode_data(HEADER_CRLF_DATA[0]).unwrap() )); assert_eq!(pems[1].tag(), "RSA PRIVATE KEY"); assert!(cmp_data( pems[1].contents(), &decode_data(HEADER_CRLF_DATA[1]).unwrap() )); } #[test] fn test_parse_many_with_headers_lf() { let pems = parse_many(HEADER_LF).unwrap(); assert_eq!(pems.len(), 2); assert_eq!(pems[0].tag(), "CERTIFICATE"); assert!(cmp_data( pems[0].contents(), &decode_data(HEADER_LF_DATA[0]).unwrap() )); assert_eq!(pems[1].tag(), "RSA PRIVATE KEY"); assert!(cmp_data( pems[1].contents(), &decode_data(HEADER_LF_DATA[1]).unwrap() )); } proptest! { #[test] fn test_str_parse_and_display(tag in "[A-Z ]+", contents in prop::collection::vec(0..255u8, 0..200)) { let pem = Pem::new(tag, contents); prop_assert_eq!(&pem, &pem.to_string().parse::().unwrap()); } #[test] fn test_str_parse_and_display_with_headers(tag in "[A-Z ]+", key in "[a-zA-Z]+", value in "[a-zA-A]+", contents in prop::collection::vec(0..255u8, 0..200)) { let mut pem = Pem::new(tag, contents); pem.headers_mut().add(&key, &value).unwrap(); prop_assert_eq!(&pem, &pem.to_string().parse::().unwrap()); } } #[test] fn test_extract_headers() { let pems = parse_many(HEADER_CRLF).unwrap(); let headers = pems[1].headers().iter().collect::>(); assert_eq!(headers.len(), 2); assert_eq!(headers[0].0, "Proc-Type"); assert_eq!(headers[0].1, "4,ENCRYPTED"); assert_eq!(headers[1].0, "DEK-Info"); assert_eq!(headers[1].1, "AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6"); let headers = pems[1].headers().iter().rev().collect::>(); assert_eq!(headers.len(), 2); assert_eq!(headers[1].0, "Proc-Type"); assert_eq!(headers[1].1, "4,ENCRYPTED"); assert_eq!(headers[0].0, "DEK-Info"); assert_eq!(headers[0].1, "AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6"); } #[test] fn test_get_header() { let pems = parse_many(HEADER_CRLF).unwrap(); let headers = pems[1].headers(); assert_eq!(headers.get("Proc-Type"), Some("4,ENCRYPTED")); assert_eq!( headers.get("DEK-Info"), Some("AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6") ); } #[test] fn test_only_get_latest() { const LATEST: &str = "-----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6 Proc-Type: 42,DECRYPTED MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg -----END RSA PRIVATE KEY----- "; let pem = parse(LATEST).unwrap(); let headers = pem.headers(); assert_eq!(headers.get("Proc-Type"), Some("42,DECRYPTED")); assert_eq!( headers.get("DEK-Info"), Some("AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6") ); } } pem-3.0.4/src/parser.rs000064400000000000000000000067750072674642500131110ustar 00000000000000pub struct Captures<'a> { pub begin: &'a [u8], pub headers: &'a [u8], pub data: &'a [u8], pub end: &'a [u8], } pub fn parse_captures(input: &[u8]) -> Option> { parser_inner(input).map(|(_, cap)| cap) } pub fn parse_captures_iter(input: &[u8]) -> CaptureMatches<'_> { CaptureMatches { input } } pub struct CaptureMatches<'a> { input: &'a [u8], } impl<'a> Iterator for CaptureMatches<'a> { type Item = Captures<'a>; fn next(&mut self) -> Option { if self.input.is_empty() { return None; } match parser_inner(self.input) { Some((remaining, captures)) => { self.input = remaining; Some(captures) } None => { self.input = &[]; None } } } } fn parse_begin(input: &[u8]) -> Option<(&[u8], &[u8])> { let (input, _) = read_until(input, b"-----BEGIN ")?; let (input, begin) = read_until(input, b"-----")?; let input = skip_whitespace(input); Some((input, begin)) } fn parse_payload(input: &[u8]) -> Option<(&[u8], &[u8])> { read_until(input, b"-----END ") } fn extract_headers_and_data(input: &[u8]) -> (&[u8], &[u8]) { if let Some((rest, headers)) = read_until(input, b"\n\n") { (headers, rest) } else if let Some((rest, headers)) = read_until(input, b"\r\n\r\n") { (headers, rest) } else { (&[], input) } } fn parse_end(input: &[u8]) -> Option<(&[u8], &[u8])> { let (remaining, end) = read_until(input, b"-----")?; let remaining = skip_whitespace(remaining); Some((remaining, end)) } fn parser_inner(input: &[u8]) -> Option<(&[u8], Captures<'_>)> { // Should be equivalent to the regex // "(?s)-----BEGIN (?P.*?)-----[ \t\n\r]*(?P.*?)-----END (?P.*?)-----[ \t\n\r]*" // (?s) # Enable dotall (. matches all characters incl \n) // -----BEGIN (?P.*?)-----[ \t\n\r]* # Parse begin // (?P.*?) # Parse data // -----END (?P.*?)-----[ \t\n\r]* # Parse end let (input, begin) = parse_begin(input)?; let (input, payload) = parse_payload(input)?; let (headers, data) = extract_headers_and_data(payload); let (remaining, end) = parse_end(input)?; let captures = Captures { begin, headers, data, end, }; Some((remaining, captures)) } // Equivalent to the regex [ \t\n\r]* fn skip_whitespace(mut input: &[u8]) -> &[u8] { while let Some(b) = input.first() { match b { b' ' | b'\t' | b'\n' | b'\r' => { input = &input[1..]; } _ => break, } } input } // Equivalent to (.*?) followed by a string // Returns the remaining input (after the secondary matched string) and the matched data fn read_until<'a>(input: &'a [u8], marker: &[u8]) -> Option<(&'a [u8], &'a [u8])> { // If there is no end condition, short circuit if marker.is_empty() { return Some((&[], input)); } let mut index = 0; let mut found = 0; while input.len() - index >= marker.len() - found { if input[index] == marker[found] { found += 1; } else { found = 0; } index += 1; if found == marker.len() { let remaining = &input[index..]; let matched = &input[..index - found]; return Some((remaining, matched)); } } None }