base64ct-1.5.1/.cargo_vcs_info.json0000644000000001460000000000100124540ustar { "git": { "sha1": "cd857c28acca50b2e396fd07ac5df9d438d0b731" }, "path_in_vcs": "base64ct" }base64ct-1.5.1/CHANGELOG.md000064400000000000000000000063240072674642500131110ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## 1.5.1 (2022-06-26) ### Fixed - Last block validation ([#680]) [#680]: https://github.com/RustCrypto/formats/pull/680 ## 1.5.0 (2022-03-29) ### Fixed - Ensure checked arithmetic with `clippy::integer_arithmetic` lint ([#557]) - Prevent foreign impls of `Encoding` by bounding sealed `Variant` trait ([#562]) [#557]: https://github.com/RustCrypto/formats/pull/557 [#562]: https://github.com/RustCrypto/formats/pull/562 ## 1.4.1 (2022-03-11) ### Changed - Rename `Decoder::decoded_len` => `::remaining_len` ([#500]) [#500]: https://github.com/RustCrypto/formats/pull/500 ## 1.4.0 (2022-03-10) [YANKED] ### Added - Buffered `Encoder` type ([#366], [#455], [#457]) - `Decoder::decoded_len` method ([#403]) - Impl `std::io::Read` for `Decoder` ([#404]) - Bounds for `Encoding`/`Variant` ZSTs ([#405], [#408]) [#366]: https://github.com/RustCrypto/formats/pull/366 [#403]: https://github.com/RustCrypto/formats/pull/403 [#404]: https://github.com/RustCrypto/formats/pull/404 [#405]: https://github.com/RustCrypto/formats/pull/405 [#408]: https://github.com/RustCrypto/formats/pull/408 [#455]: https://github.com/RustCrypto/formats/pull/455 [#457]: https://github.com/RustCrypto/formats/pull/457 ## 1.3.3 (2021-12-28) ### Fixed - Potential infinite loop in `Decoder::decode` ([#305]) [#305]: https://github.com/RustCrypto/formats/pull/305 ## 1.3.2 (2021-12-26) [YANKED] ### Fixed - `Decoder` unpadding ([#299]) - Edge case when using `Decoder::new_wrapped` ([#300]) [#299]: https://github.com/RustCrypto/formats/pull/299 [#300]: https://github.com/RustCrypto/formats/pull/300 ## 1.3.1 (2021-12-20) [YANKED] ### Added - `Decoder::new_wrapped` with support for line-wrapped Base64 ([#292], [#293], [#294]) [#292]: https://github.com/RustCrypto/formats/pull/292 [#293]: https://github.com/RustCrypto/formats/pull/292 [#294]: https://github.com/RustCrypto/formats/pull/294 ## 1.3.0 (2021-12-02) [YANKED] ### Added - Stateful `Decoder` type ([#266]) [#266]: https://github.com/RustCrypto/formats/pull/266 ## 1.2.0 (2021-11-03) ### Changed - Rust 2021 edition upgrade; MSRV 1.56 ([#136]) ### Fixed - Benchmarks ([#135]) [#135]: https://github.com/RustCrypto/formats/pull/135 [#136]: https://github.com/RustCrypto/formats/pull/136 ## 1.1.1 (2021-10-14) ### Changed - Update `Util::Lookup` paper references ([#32]) [#32]: https://github.com/RustCrypto/formats/pull/32 ## 1.1.0 (2021-09-14) ### Changed - Moved to `formats` repo; MSRV 1.51+ ([#2]) [#2]: https://github.com/RustCrypto/formats/pull/2 ## 1.0.1 (2021-08-14) ### Fixed - Make `Encoding::decode` reject invalid padding ## 1.0.0 (2021-03-17) ### Changed - Bump MSRV to 1.47+ ### Fixed - MSRV-dependent TODOs in implementation ## 0.2.1 (2021-03-07) ### Fixed - MSRV docs ## 0.2.0 (2021-02-01) ### Changed - Refactor with `Encoding` trait - Internal refactoring ## 0.1.2 (2021-01-31) ### Added - bcrypt encoding - `crypt(3)` encoding ### Changed - Internal refactoring ## 0.1.1 (2021-01-27) - Minor code improvements ## 0.1.0 (2021-01-26) - Initial release base64ct-1.5.1/Cargo.toml0000644000000025500000000000100104530ustar # 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.56" name = "base64ct" version = "1.5.1" authors = ["RustCrypto Developers"] description = """ Pure Rust implementation of Base64 (RFC 4648) which avoids any usages of data-dependent branches/LUTs and thereby provides portable \"best effort\" constant-time operation and embedded-friendly no_std support """ documentation = "https://docs.rs/base64ct" readme = "README.md" keywords = [ "crypto", "base64", "pem", "phc", ] categories = [ "cryptography", "encoding", "no-std", "parser-implementations", ] license = "Apache-2.0 OR MIT" repository = "https://github.com/RustCrypto/formats/tree/master/base64ct" resolver = "2" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", ] [dev-dependencies.base64] version = "0.13" [dev-dependencies.proptest] version = "1" [features] alloc = [] std = ["alloc"] base64ct-1.5.1/Cargo.toml.orig0000644000000015470000000000100114170ustar [package] name = "base64ct" version = "1.5.1" # Also update html_root_url in lib.rs when bumping this description = """ Pure Rust implementation of Base64 (RFC 4648) which avoids any usages of data-dependent branches/LUTs and thereby provides portable "best effort" constant-time operation and embedded-friendly no_std support """ authors = ["RustCrypto Developers"] license = "Apache-2.0 OR MIT" documentation = "https://docs.rs/base64ct" repository = "https://github.com/RustCrypto/formats/tree/master/base64ct" categories = ["cryptography", "encoding", "no-std", "parser-implementations"] keywords = ["crypto", "base64", "pem", "phc"] readme = "README.md" edition = "2021" rust-version = "1.56" [dev-dependencies] base64 = "0.13" proptest = "1" [features] alloc = [] std = ["alloc"] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] base64ct-1.5.1/Cargo.toml.orig000064400000000000000000000015470072674642500141710ustar 00000000000000[package] name = "base64ct" version = "1.5.1" # Also update html_root_url in lib.rs when bumping this description = """ Pure Rust implementation of Base64 (RFC 4648) which avoids any usages of data-dependent branches/LUTs and thereby provides portable "best effort" constant-time operation and embedded-friendly no_std support """ authors = ["RustCrypto Developers"] license = "Apache-2.0 OR MIT" documentation = "https://docs.rs/base64ct" repository = "https://github.com/RustCrypto/formats/tree/master/base64ct" categories = ["cryptography", "encoding", "no-std", "parser-implementations"] keywords = ["crypto", "base64", "pem", "phc"] readme = "README.md" edition = "2021" rust-version = "1.56" [dev-dependencies] base64 = "0.13" proptest = "1" [features] alloc = [] std = ["alloc"] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] base64ct-1.5.1/LICENSE-APACHE000064400000000000000000000251410072674642500132220ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. base64ct-1.5.1/LICENSE-MIT000064400000000000000000000021670072674642500127350ustar 00000000000000Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com) Copyright (c) 2021 The RustCrypto Project Developers 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. base64ct-1.5.1/README.md000064400000000000000000000064320072674642500125570ustar 00000000000000# [RustCrypto]: Constant-Time Base64 [![crate][crate-image]][crate-link] [![Docs][docs-image]][docs-link] [![Build Status][build-image]][build-link] ![Apache2/MIT licensed][license-image] ![Rust Version][rustc-image] [![Project Chat][chat-image]][chat-link] Pure Rust implementation of Base64 ([RFC 4648]). Implements multiple Base64 alphabets without data-dependent branches or lookup tables, thereby providing portable "best effort" constant-time operation. Supports `no_std` environments and avoids heap allocations in the core API (but also provides optional `alloc` support for convenience). [Documentation][docs-link] ## About This crate implements several Base64 alphabets in constant-time for sidechannel resistance, aimed at purposes like encoding/decoding the "PEM" format used to store things like cryptographic private keys (i.e. in the [`pem-rfc7468`] crate). The paper [Util::Lookup: Exploiting key decoding in cryptographic libraries][Util::Lookup] demonstrates how the leakage from non-constant-time Base64 parsers can be used to practically extract RSA private keys from SGX enclaves. The padded variants require (`=`) padding. Unpadded variants expressly reject such padding. Whitespace is expressly disallowed, with the exception of the [`Decoder::new_wrapped`] and [`Encoder::new_wrapped`] modes which provide fixed-width line wrapping. ## Supported Base64 variants - Standard Base64: `[A-Z]`, `[a-z]`, `[0-9]`, `+`, `/` - URL-safe Base64: `[A-Z]`, `[a-z]`, `[0-9]`, `-`, `_` - bcrypt Base64: `.`, `/`, `[A-Z]`, `[a-z]`, `[0-9]` - `crypt(3)` Base64: `.`, `-`, `[0-9]`, `[A-Z]`, `[a-z]` ## Minimum Supported Rust Version This crate requires **Rust 1.56** at a minimum. We may change the MSRV in the future, but it will be accompanied by a minor version bump. ## License Licensed under either of: * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) * [MIT license](http://opensource.org/licenses/MIT) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. [//]: # (badges) [crate-image]: https://buildstats.info/crate/base64ct [crate-link]: https://crates.io/crates/base64ct [docs-image]: https://docs.rs/base64ct/badge.svg [docs-link]: https://docs.rs/base64ct/ [build-image]: https://github.com/RustCrypto/formats/actions/workflows/base64ct.yml/badge.svg [build-link]: https://github.com/RustCrypto/formats/actions/workflows/base64ct.yml [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg [rustc-image]: https://img.shields.io/badge/rustc-1.56+-blue.svg [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/300570-formats [//]: # (links) [RustCrypto]: https://github.com/rustcrypto [RFC 4648]: https://tools.ietf.org/html/rfc4648 [`pem-rfc7468`]: https://github.com/RustCrypto/formats/tree/master/pem-rfc7468 [Util::Lookup]: https://arxiv.org/pdf/2108.04600.pdf [`Decoder::new_wrapped`]: https://docs.rs/base64ct/latest/base64ct/struct.Decoder.html#method.new_wrapped [`Encoder::new_wrapped`]: https://docs.rs/base64ct/latest/base64ct/struct.Encoder.html#method.new_wrapped base64ct-1.5.1/src/alphabet/bcrypt.rs000064400000000000000000000014440072674642500155160ustar 00000000000000//! bcrypt Base64 encoding. use super::{Alphabet, DecodeStep, EncodeStep}; /// bcrypt Base64 encoding. /// /// ```text /// ./ [A-Z] [a-z] [0-9] /// 0x2e-0x2f, 0x41-0x5a, 0x61-0x7a, 0x30-0x39 /// ``` #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Base64Bcrypt; impl Alphabet for Base64Bcrypt { const BASE: u8 = b'.'; const DECODER: &'static [DecodeStep] = &[ DecodeStep::Range(b'.'..b'/', -45), DecodeStep::Range(b'A'..b'Z', -62), DecodeStep::Range(b'a'..b'z', -68), DecodeStep::Range(b'0'..b'9', 7), ]; const ENCODER: &'static [EncodeStep] = &[ EncodeStep::Apply(b'/', 17), EncodeStep::Apply(b'Z', 6), EncodeStep::Apply(b'z', -75), ]; const PADDED: bool = false; type Unpadded = Self; } base64ct-1.5.1/src/alphabet/crypt.rs000064400000000000000000000012650072674642500153550ustar 00000000000000//! `crypt(3)` Base64 encoding. use super::{Alphabet, DecodeStep, EncodeStep}; /// `crypt(3)` Base64 encoding. /// /// ```text /// [.-9] [A-Z] [a-z] /// 0x2e-0x39, 0x41-0x5a, 0x61-0x7a /// ``` #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Base64Crypt; impl Alphabet for Base64Crypt { const BASE: u8 = b'.'; const DECODER: &'static [DecodeStep] = &[ DecodeStep::Range(b'.'..b'9', -45), DecodeStep::Range(b'A'..b'Z', -52), DecodeStep::Range(b'a'..b'z', -58), ]; const ENCODER: &'static [EncodeStep] = &[EncodeStep::Apply(b'9', 7), EncodeStep::Apply(b'Z', 6)]; const PADDED: bool = false; type Unpadded = Self; } base64ct-1.5.1/src/alphabet/standard.rs000064400000000000000000000027040072674642500160130ustar 00000000000000//! Standard Base64 encoding. use super::{Alphabet, DecodeStep, EncodeStep}; /// Standard Base64 encoding with `=` padding. /// /// ```text /// [A-Z] [a-z] [0-9] + / /// 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f /// ``` #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Base64; impl Alphabet for Base64 { const BASE: u8 = b'A'; const DECODER: &'static [DecodeStep] = DECODER; const ENCODER: &'static [EncodeStep] = ENCODER; const PADDED: bool = true; type Unpadded = Base64Unpadded; } /// Standard Base64 encoding *without* padding. /// /// ```text /// [A-Z] [a-z] [0-9] + / /// 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f /// ``` #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Base64Unpadded; impl Alphabet for Base64Unpadded { const BASE: u8 = b'A'; const DECODER: &'static [DecodeStep] = DECODER; const ENCODER: &'static [EncodeStep] = ENCODER; const PADDED: bool = false; type Unpadded = Self; } /// Standard Base64 decoder const DECODER: &[DecodeStep] = &[ DecodeStep::Range(b'A'..b'Z', -64), DecodeStep::Range(b'a'..b'z', -70), DecodeStep::Range(b'0'..b'9', 5), DecodeStep::Eq(b'+', 63), DecodeStep::Eq(b'/', 64), ]; /// Standard Base64 encoder const ENCODER: &[EncodeStep] = &[ EncodeStep::Diff(25, 6), EncodeStep::Diff(51, -75), EncodeStep::Diff(61, -(b'+' as i16 - 0x1c)), EncodeStep::Diff(62, b'/' as i16 - b'+' as i16 - 1), ]; base64ct-1.5.1/src/alphabet/url.rs000064400000000000000000000027230072674642500150160ustar 00000000000000//! URL-safe Base64 encoding. use super::{Alphabet, DecodeStep, EncodeStep}; /// URL-safe Base64 encoding with `=` padding. /// /// ```text /// [A-Z] [a-z] [0-9] - _ /// 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2d, 0x5f /// ``` #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Base64Url; impl Alphabet for Base64Url { const BASE: u8 = b'A'; const DECODER: &'static [DecodeStep] = DECODER; const ENCODER: &'static [EncodeStep] = ENCODER; const PADDED: bool = true; type Unpadded = Base64UrlUnpadded; } /// URL-safe Base64 encoding *without* padding. /// /// ```text /// [A-Z] [a-z] [0-9] - _ /// 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2d, 0x5f /// ``` #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Base64UrlUnpadded; impl Alphabet for Base64UrlUnpadded { const BASE: u8 = b'A'; const DECODER: &'static [DecodeStep] = DECODER; const ENCODER: &'static [EncodeStep] = ENCODER; const PADDED: bool = false; type Unpadded = Self; } /// URL-safe Base64 decoder const DECODER: &[DecodeStep] = &[ DecodeStep::Range(b'A'..b'Z', -64), DecodeStep::Range(b'a'..b'z', -70), DecodeStep::Range(b'0'..b'9', 5), DecodeStep::Eq(b'-', 63), DecodeStep::Eq(b'_', 64), ]; /// URL-safe Base64 encoder const ENCODER: &[EncodeStep] = &[ EncodeStep::Diff(25, 6), EncodeStep::Diff(51, -75), EncodeStep::Diff(61, -(b'-' as i16 - 0x20)), EncodeStep::Diff(62, b'_' as i16 - b'-' as i16 - 1), ]; base64ct-1.5.1/src/alphabet.rs000064400000000000000000000072340072674642500142160ustar 00000000000000//! Base64 alphabets. // TODO(tarcieri): explicitly checked/wrapped arithmetic #![allow(clippy::integer_arithmetic)] use core::{fmt::Debug, ops::Range}; pub mod bcrypt; pub mod crypt; pub mod standard; pub mod url; /// Core encoder/decoder functions for a particular Base64 alphabet. pub trait Alphabet: 'static + Copy + Debug + Eq + Send + Sized + Sync { /// First character in this Base64 alphabet. const BASE: u8; /// Decoder passes const DECODER: &'static [DecodeStep]; /// Encoder passes const ENCODER: &'static [EncodeStep]; /// Is this encoding padded? const PADDED: bool; /// Unpadded equivalent of this alphabet. /// /// For alphabets that are unpadded to begin with, this should be `Self`. type Unpadded: Alphabet; /// Decode 3 bytes of a Base64 message. #[inline(always)] fn decode_3bytes(src: &[u8], dst: &mut [u8]) -> i16 { debug_assert_eq!(src.len(), 4); debug_assert!(dst.len() >= 3, "dst too short: {}", dst.len()); let c0 = Self::decode_6bits(src[0]); let c1 = Self::decode_6bits(src[1]); let c2 = Self::decode_6bits(src[2]); let c3 = Self::decode_6bits(src[3]); dst[0] = ((c0 << 2) | (c1 >> 4)) as u8; dst[1] = ((c1 << 4) | (c2 >> 2)) as u8; dst[2] = ((c2 << 6) | c3) as u8; ((c0 | c1 | c2 | c3) >> 8) & 1 } /// Decode 6-bits of a Base64 message. fn decode_6bits(src: u8) -> i16 { let mut ret: i16 = -1; for step in Self::DECODER { ret += match step { DecodeStep::Range(range, offset) => { // Compute exclusive range from inclusive one let start = range.start as i16 - 1; let end = range.end as i16 + 1; (((start - src as i16) & (src as i16 - end)) >> 8) & (src as i16 + *offset) } DecodeStep::Eq(value, offset) => { let start = *value as i16 - 1; let end = *value as i16 + 1; (((start - src as i16) & (src as i16 - end)) >> 8) & *offset } }; } ret } /// Encode 3-bytes of a Base64 message. #[inline(always)] fn encode_3bytes(src: &[u8], dst: &mut [u8]) { debug_assert_eq!(src.len(), 3); debug_assert!(dst.len() >= 4, "dst too short: {}", dst.len()); let b0 = src[0] as i16; let b1 = src[1] as i16; let b2 = src[2] as i16; dst[0] = Self::encode_6bits(b0 >> 2); dst[1] = Self::encode_6bits(((b0 << 4) | (b1 >> 4)) & 63); dst[2] = Self::encode_6bits(((b1 << 2) | (b2 >> 6)) & 63); dst[3] = Self::encode_6bits(b2 & 63); } /// Encode 6-bits of a Base64 message. #[inline(always)] fn encode_6bits(src: i16) -> u8 { let mut diff = src + Self::BASE as i16; for &step in Self::ENCODER { diff += match step { EncodeStep::Apply(threshold, offset) => ((threshold as i16 - diff) >> 8) & offset, EncodeStep::Diff(threshold, offset) => ((threshold as i16 - src) >> 8) & offset, }; } diff as u8 } } /// Constant-time decoder step. #[derive(Debug)] pub enum DecodeStep { /// Match the given range, offsetting the input on match. Range(Range, i16), /// Match the given value, returning the associated offset on match. Eq(u8, i16), } /// Constant-time encoder step. #[derive(Copy, Clone, Debug)] pub enum EncodeStep { /// Apply the given offset to the cumulative result on match. Apply(u8, i16), /// Compute a difference using the given offset on match. Diff(u8, i16), } base64ct-1.5.1/src/decoder.rs000064400000000000000000000472310072674642500140440ustar 00000000000000//! Buffered Base64 decoder. use crate::{ encoding, line_ending::{CHAR_CR, CHAR_LF}, Encoding, Error::{self, InvalidLength}, MIN_LINE_WIDTH, }; use core::{cmp, marker::PhantomData}; #[cfg(feature = "alloc")] use {alloc::vec::Vec, core::iter}; #[cfg(feature = "std")] use std::io; #[cfg(doc)] use crate::{Base64, Base64Unpadded}; /// Stateful Base64 decoder with support for buffered, incremental decoding. /// /// The `E` type parameter can be any type which impls [`Encoding`] such as /// [`Base64`] or [`Base64Unpadded`]. #[derive(Clone)] pub struct Decoder<'i, E: Encoding> { /// Current line being processed. line: Line<'i>, /// Base64 input data reader. line_reader: LineReader<'i>, /// Length of the remaining data after Base64 decoding. remaining_len: usize, /// Block buffer used for non-block-aligned data. block_buffer: BlockBuffer, /// Phantom parameter for the Base64 encoding in use. encoding: PhantomData, } impl<'i, E: Encoding> Decoder<'i, E> { /// Create a new decoder for a byte slice containing contiguous /// (non-newline-delimited) Base64-encoded data. /// /// # Returns /// - `Ok(decoder)` on success. /// - `Err(Error::InvalidLength)` if the input buffer is empty. pub fn new(input: &'i [u8]) -> Result { let line_reader = LineReader::new_unwrapped(input)?; let remaining_len = line_reader.decoded_len::()?; Ok(Self { line: Line::default(), line_reader, remaining_len, block_buffer: BlockBuffer::default(), encoding: PhantomData, }) } /// Create a new decoder for a byte slice containing Base64 which /// line wraps at the given line length. /// /// Trailing newlines are not supported and must be removed in advance. /// /// Newlines are handled according to what are roughly [RFC7468] conventions: /// /// ```text /// [parsers] MUST handle different newline conventions /// ``` /// /// RFC7468 allows any of the following as newlines, and allows a mixture /// of different types of newlines: /// /// ```text /// eol = CRLF / CR / LF /// ``` /// /// # Returns /// - `Ok(decoder)` on success. /// - `Err(Error::InvalidLength)` if the input buffer is empty or the line /// width is zero. /// /// [RFC7468]: https://datatracker.ietf.org/doc/html/rfc7468 pub fn new_wrapped(input: &'i [u8], line_width: usize) -> Result { let line_reader = LineReader::new_wrapped(input, line_width)?; let remaining_len = line_reader.decoded_len::()?; Ok(Self { line: Line::default(), line_reader, remaining_len, block_buffer: BlockBuffer::default(), encoding: PhantomData, }) } /// Fill the provided buffer with data decoded from Base64. /// /// Enough Base64 input data must remain to fill the entire buffer. /// /// # Returns /// - `Ok(bytes)` if the expected amount of data was read /// - `Err(Error::InvalidLength)` if the exact amount of data couldn't be read pub fn decode<'o>(&mut self, out: &'o mut [u8]) -> Result<&'o [u8], Error> { if self.is_finished() { return Err(InvalidLength); } let mut out_pos = 0; while out_pos < out.len() { // If there's data in the block buffer, use it if !self.block_buffer.is_empty() { let out_rem = out.len().checked_sub(out_pos).ok_or(InvalidLength)?; let bytes = self.block_buffer.take(out_rem)?; out[out_pos..][..bytes.len()].copy_from_slice(bytes); out_pos = out_pos.checked_add(bytes.len()).ok_or(InvalidLength)?; } // Advance the line reader if necessary if self.line.is_empty() && !self.line_reader.is_empty() { self.advance_line()?; } // Attempt to decode a stride of block-aligned data let in_blocks = self.line.len() / 4; let out_rem = out.len().checked_sub(out_pos).ok_or(InvalidLength)?; let out_blocks = out_rem / 3; let blocks = cmp::min(in_blocks, out_blocks); let in_aligned = self.line.take(blocks.checked_mul(4).ok_or(InvalidLength)?); if !in_aligned.is_empty() { let out_buf = &mut out[out_pos..][..blocks.checked_mul(3).ok_or(InvalidLength)?]; let decoded_len = self.perform_decode(in_aligned, out_buf)?.len(); out_pos = out_pos.checked_add(decoded_len).ok_or(InvalidLength)?; } if out_pos < out.len() { if self.is_finished() { // If we're out of input then we've been requested to decode // more data than is actually available. return Err(InvalidLength); } else { // If we still have data available but haven't completely // filled the output slice, we're in a situation where // either the input or output isn't block-aligned, so fill // the internal block buffer. self.fill_block_buffer()?; } } } self.remaining_len = self .remaining_len .checked_sub(out.len()) .ok_or(InvalidLength)?; Ok(out) } /// Decode all remaining Base64 data, placing the result into `buf`. /// /// If successful, this function will return the total number of bytes /// decoded into `buf`. #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub fn decode_to_end<'o>(&mut self, buf: &'o mut Vec) -> Result<&'o [u8], Error> { let start_len = buf.len(); let remaining_len = self.remaining_len(); let total_len = start_len.checked_add(remaining_len).ok_or(InvalidLength)?; if total_len > buf.capacity() { buf.reserve(total_len.checked_sub(buf.capacity()).ok_or(InvalidLength)?); } // Append `decoded_len` zeroes to the vector buf.extend(iter::repeat(0).take(remaining_len)); self.decode(&mut buf[start_len..])?; Ok(&buf[start_len..]) } /// Get the length of the remaining data after Base64 decoding. /// /// Decreases every time data is decoded. pub fn remaining_len(&self) -> usize { self.remaining_len } /// Has all of the input data been decoded? pub fn is_finished(&self) -> bool { self.line.is_empty() && self.line_reader.is_empty() && self.block_buffer.is_empty() } /// Fill the block buffer with data. fn fill_block_buffer(&mut self) -> Result<(), Error> { let mut buf = [0u8; BlockBuffer::SIZE]; let decoded = if self.line.len() < 4 && !self.line_reader.is_empty() { // Handle input block which is split across lines let mut tmp = [0u8; 4]; // Copy remaining data in the line into tmp let line_end = self.line.take(4); tmp[..line_end.len()].copy_from_slice(line_end); // Advance the line and attempt to fill tmp self.advance_line()?; let len = 4usize.checked_sub(line_end.len()).ok_or(InvalidLength)?; let line_begin = self.line.take(len); tmp[line_end.len()..][..line_begin.len()].copy_from_slice(line_begin); let tmp_len = line_begin .len() .checked_add(line_end.len()) .ok_or(InvalidLength)?; self.perform_decode(&tmp[..tmp_len], &mut buf) } else { let block = self.line.take(4); self.perform_decode(block, &mut buf) }?; self.block_buffer.fill(decoded) } /// Advance the internal buffer to the next line. fn advance_line(&mut self) -> Result<(), Error> { debug_assert!(self.line.is_empty(), "expected line buffer to be empty"); if let Some(line) = self.line_reader.next().transpose()? { self.line = line; Ok(()) } else { Err(InvalidLength) } } /// Perform Base64 decoding operation. fn perform_decode<'o>(&self, src: &[u8], dst: &'o mut [u8]) -> Result<&'o [u8], Error> { if self.is_finished() { E::decode(src, dst) } else { E::Unpadded::decode(src, dst) } } } #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl<'i, E: Encoding> io::Read for Decoder<'i, E> { fn read(&mut self, buf: &mut [u8]) -> io::Result { let slice = match buf.get_mut(..self.remaining_len()) { Some(bytes) => bytes, None => buf, }; self.decode(slice)?; Ok(slice.len()) } fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { Ok(self.decode_to_end(buf)?.len()) } fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { self.decode(buf)?; Ok(()) } } /// Base64 decode buffer for a 1-block input. /// /// This handles a partially decoded block of data, i.e. data which has been /// decoded but not read. #[derive(Clone, Default, Debug)] struct BlockBuffer { /// 3 decoded bytes from a 4-byte Base64-encoded input. decoded: [u8; Self::SIZE], /// Length of the buffer. length: usize, /// Position within the buffer. position: usize, } impl BlockBuffer { /// Size of the buffer in bytes. const SIZE: usize = 3; /// Fill the buffer by decoding up to 3 bytes of decoded Base64 input. fn fill(&mut self, decoded_input: &[u8]) -> Result<(), Error> { debug_assert!(self.is_empty()); if decoded_input.len() > Self::SIZE { return Err(InvalidLength); } self.position = 0; self.length = decoded_input.len(); self.decoded[..decoded_input.len()].copy_from_slice(decoded_input); Ok(()) } /// Take a specified number of bytes from the buffer. /// /// Returns as many bytes as possible, or an empty slice if the buffer has /// already been read to completion. fn take(&mut self, mut nbytes: usize) -> Result<&[u8], Error> { debug_assert!(self.position <= self.length); let start_pos = self.position; let remaining_len = self.length.checked_sub(start_pos).ok_or(InvalidLength)?; if nbytes > remaining_len { nbytes = remaining_len; } self.position = self.position.checked_add(nbytes).ok_or(InvalidLength)?; Ok(&self.decoded[start_pos..][..nbytes]) } /// Have all of the bytes in this buffer been consumed? fn is_empty(&self) -> bool { self.position == self.length } } /// A single line of linewrapped data, providing a read buffer. #[derive(Clone, Debug)] pub struct Line<'i> { /// Remaining data in the line remaining: &'i [u8], } impl<'i> Default for Line<'i> { fn default() -> Self { Self::new(&[]) } } impl<'i> Line<'i> { /// Create a new line which wraps the given input data. fn new(bytes: &'i [u8]) -> Self { Self { remaining: bytes } } /// Take up to `nbytes` from this line buffer. fn take(&mut self, nbytes: usize) -> &'i [u8] { let (bytes, rest) = if nbytes < self.remaining.len() { self.remaining.split_at(nbytes) } else { (self.remaining, [].as_ref()) }; self.remaining = rest; bytes } /// Slice off a tail of a given length. fn slice_tail(&self, nbytes: usize) -> Result<&'i [u8], Error> { let offset = self.len().checked_sub(nbytes).ok_or(InvalidLength)?; self.remaining.get(offset..).ok_or(InvalidLength) } /// Get the number of bytes remaining in this line. fn len(&self) -> usize { self.remaining.len() } /// Is the buffer for this line empty? fn is_empty(&self) -> bool { self.len() == 0 } /// Trim the newline off the end of this line. fn trim_end(&self) -> Self { Line::new(match self.remaining { [line @ .., CHAR_CR, CHAR_LF] => line, [line @ .., CHAR_CR] => line, [line @ .., CHAR_LF] => line, line => line, }) } } /// Iterator over multi-line Base64 input. #[derive(Clone)] struct LineReader<'i> { /// Remaining linewrapped data to be processed. remaining: &'i [u8], /// Line width. line_width: Option, } impl<'i> LineReader<'i> { /// Create a new reader which operates over continugous unwrapped data. fn new_unwrapped(bytes: &'i [u8]) -> Result { if bytes.is_empty() { Err(InvalidLength) } else { Ok(Self { remaining: bytes, line_width: None, }) } } /// Create a new reader which operates over linewrapped data. fn new_wrapped(bytes: &'i [u8], line_width: usize) -> Result { if line_width < MIN_LINE_WIDTH { return Err(InvalidLength); } let mut reader = Self::new_unwrapped(bytes)?; reader.line_width = Some(line_width); Ok(reader) } /// Is this line reader empty? fn is_empty(&self) -> bool { self.remaining.is_empty() } /// Get the total length of the data decoded from this line reader. fn decoded_len(&self) -> Result { let mut buffer = [0u8; 4]; let mut lines = self.clone(); let mut line = match lines.next().transpose()? { Some(l) => l, None => return Ok(0), }; let mut base64_len = 0usize; loop { base64_len = base64_len.checked_add(line.len()).ok_or(InvalidLength)?; match lines.next().transpose()? { Some(l) => { // Store the end of the line in the buffer so we can // reassemble the last block to determine the real length buffer.copy_from_slice(line.slice_tail(4)?); line = l } // To compute an exact decoded length we need to decode the // last Base64 block and get the decoded length. // // This is what the somewhat complex code below is doing. None => { // Compute number of bytes in the last block (may be unpadded) let base64_last_block_len = match base64_len % 4 { 0 => 4, n => n, }; // Compute decoded length without the last block let decoded_len = encoding::decoded_len( base64_len .checked_sub(base64_last_block_len) .ok_or(InvalidLength)?, ); // Compute the decoded length of the last block let mut out = [0u8; 3]; let last_block_len = if line.len() < base64_last_block_len { let buffered_part_len = base64_last_block_len .checked_sub(line.len()) .ok_or(InvalidLength)?; let offset = 4usize.checked_sub(buffered_part_len).ok_or(InvalidLength)?; for i in 0..buffered_part_len { buffer[i] = buffer[offset.checked_add(i).ok_or(InvalidLength)?]; } buffer[buffered_part_len..][..line.len()].copy_from_slice(line.remaining); let buffer_len = buffered_part_len .checked_add(line.len()) .ok_or(InvalidLength)?; E::decode(&buffer[..buffer_len], &mut out)?.len() } else { let last_block = line.slice_tail(base64_last_block_len)?; E::decode(last_block, &mut out)?.len() }; return decoded_len.checked_add(last_block_len).ok_or(InvalidLength); } } } } } impl<'i> Iterator for LineReader<'i> { type Item = Result, Error>; fn next(&mut self) -> Option, Error>> { if let Some(line_width) = self.line_width { let rest = match self.remaining.get(line_width..) { None | Some([]) => { if self.remaining.is_empty() { return None; } else { let line = Line::new(self.remaining).trim_end(); self.remaining = &[]; return Some(Ok(line)); } } Some([CHAR_CR, CHAR_LF, rest @ ..]) => rest, Some([CHAR_CR, rest @ ..]) => rest, Some([CHAR_LF, rest @ ..]) => rest, _ => { // Expected a leading newline return Some(Err(Error::InvalidEncoding)); } }; let line = Line::new(&self.remaining[..line_width]); self.remaining = rest; Some(Ok(line)) } else if !self.remaining.is_empty() { let line = Line::new(self.remaining).trim_end(); self.remaining = b""; if line.is_empty() { None } else { Some(Ok(line)) } } else { None } } } #[cfg(test)] mod tests { use crate::{alphabet::Alphabet, test_vectors::*, Base64, Base64Unpadded, Decoder}; #[cfg(feature = "std")] use {alloc::vec::Vec, std::io::Read}; #[test] fn decode_padded() { decode_test(PADDED_BIN, || { Decoder::::new(PADDED_BASE64.as_bytes()).unwrap() }) } #[test] fn decode_unpadded() { decode_test(UNPADDED_BIN, || { Decoder::::new(UNPADDED_BASE64.as_bytes()).unwrap() }) } #[test] fn decode_multiline_padded() { decode_test(MULTILINE_PADDED_BIN, || { Decoder::::new_wrapped(MULTILINE_PADDED_BASE64.as_bytes(), 70).unwrap() }) } #[test] fn decode_multiline_unpadded() { decode_test(MULTILINE_UNPADDED_BIN, || { Decoder::::new_wrapped(MULTILINE_UNPADDED_BASE64.as_bytes(), 70) .unwrap() }) } #[cfg(feature = "std")] #[test] fn read_multiline_padded() { let mut decoder = Decoder::::new_wrapped(MULTILINE_PADDED_BASE64.as_bytes(), 70).unwrap(); let mut buf = Vec::new(); let len = decoder.read_to_end(&mut buf).unwrap(); assert_eq!(len, MULTILINE_PADDED_BIN.len()); assert_eq!(buf.as_slice(), MULTILINE_PADDED_BIN); } /// Core functionality of a decoding test fn decode_test<'a, F, V>(expected: &[u8], f: F) where F: Fn() -> Decoder<'a, V>, V: Alphabet, { for chunk_size in 1..expected.len() { let mut decoder = f(); let mut remaining_len = decoder.remaining_len(); let mut buffer = [0u8; 1024]; for chunk in expected.chunks(chunk_size) { assert!(!decoder.is_finished()); let decoded = decoder.decode(&mut buffer[..chunk.len()]).unwrap(); assert_eq!(chunk, decoded); remaining_len -= decoded.len(); assert_eq!(remaining_len, decoder.remaining_len()); } assert!(decoder.is_finished()); assert_eq!(decoder.remaining_len(), 0); } } } base64ct-1.5.1/src/encoder.rs000064400000000000000000000263230072674642500140550ustar 00000000000000//! Buffered Base64 encoder. use crate::{ Encoding, Error::{self, InvalidLength}, LineEnding, MIN_LINE_WIDTH, }; use core::{cmp, marker::PhantomData, str}; #[cfg(feature = "std")] use std::io; #[cfg(doc)] use crate::{Base64, Base64Unpadded}; /// Stateful Base64 encoder with support for buffered, incremental encoding. /// /// The `E` type parameter can be any type which impls [`Encoding`] such as /// [`Base64`] or [`Base64Unpadded`]. pub struct Encoder<'o, E: Encoding> { /// Output buffer. output: &'o mut [u8], /// Cursor within the output buffer. position: usize, /// Block buffer used for non-block-aligned data. block_buffer: BlockBuffer, /// Configuration and state for line-wrapping the output at a specified /// column. line_wrapper: Option, /// Phantom parameter for the Base64 encoding in use. encoding: PhantomData, } impl<'o, E: Encoding> Encoder<'o, E> { /// Create a new encoder which writes output to the given byte slice. /// /// Output constructed using this method is not line-wrapped. pub fn new(output: &'o mut [u8]) -> Result { if output.is_empty() { return Err(InvalidLength); } Ok(Self { output, position: 0, block_buffer: BlockBuffer::default(), line_wrapper: None, encoding: PhantomData, }) } /// Create a new encoder which writes line-wrapped output to the given byte /// slice. /// /// Output will be wrapped at the specified interval, using the provided /// line ending. Use [`LineEnding::default()`] to use the conventional line /// ending for the target OS. /// /// Minimum allowed line width is 4. pub fn new_wrapped( output: &'o mut [u8], width: usize, ending: LineEnding, ) -> Result { let mut encoder = Self::new(output)?; encoder.line_wrapper = Some(LineWrapper::new(width, ending)?); Ok(encoder) } /// Encode the provided buffer as Base64, writing it to the output buffer. /// /// # Returns /// - `Ok(bytes)` if the expected amount of data was read /// - `Err(Error::InvalidLength)` if there is insufficient space in the output buffer pub fn encode(&mut self, mut input: &[u8]) -> Result<(), Error> { // If there's data in the block buffer, fill it if !self.block_buffer.is_empty() { self.process_buffer(&mut input)?; } while !input.is_empty() { // Attempt to encode a stride of block-aligned data let in_blocks = input.len() / 3; let out_blocks = self.remaining().len() / 4; let mut blocks = cmp::min(in_blocks, out_blocks); // When line wrapping, cap the block-aligned stride at near/at line length if let Some(line_wrapper) = &self.line_wrapper { line_wrapper.wrap_blocks(&mut blocks)?; } if blocks > 0 { let len = blocks.checked_mul(3).ok_or(InvalidLength)?; let (in_aligned, in_rem) = input.split_at(len); input = in_rem; self.perform_encode(in_aligned)?; } // If there's remaining non-aligned data, fill the block buffer if !input.is_empty() { self.process_buffer(&mut input)?; } } Ok(()) } /// Get the position inside of the output buffer where the write cursor /// is currently located. pub fn position(&self) -> usize { self.position } /// Finish encoding data, returning the resulting Base64 as a `str`. pub fn finish(self) -> Result<&'o str, Error> { self.finish_with_remaining().map(|(base64, _)| base64) } /// Finish encoding data, returning the resulting Base64 as a `str` /// along with the remaining space in the output buffer. pub fn finish_with_remaining(mut self) -> Result<(&'o str, &'o mut [u8]), Error> { if !self.block_buffer.is_empty() { let buffer_len = self.block_buffer.position; let block = self.block_buffer.bytes; self.perform_encode(&block[..buffer_len])?; } let (base64, remaining) = self.output.split_at_mut(self.position); Ok((str::from_utf8(base64)?, remaining)) } /// Borrow the remaining data in the buffer. fn remaining(&mut self) -> &mut [u8] { &mut self.output[self.position..] } /// Fill the block buffer with data, consuming and encoding it when the /// buffer is full. fn process_buffer(&mut self, input: &mut &[u8]) -> Result<(), Error> { self.block_buffer.fill(input)?; if self.block_buffer.is_full() { let block = self.block_buffer.take(); self.perform_encode(&block)?; } Ok(()) } /// Perform Base64 encoding operation. fn perform_encode(&mut self, input: &[u8]) -> Result { let mut len = E::encode(input, self.remaining())?.as_bytes().len(); // Insert newline characters into the output as needed if let Some(line_wrapper) = &mut self.line_wrapper { line_wrapper.insert_newlines(&mut self.output[self.position..], &mut len)?; } self.position = self.position.checked_add(len).ok_or(InvalidLength)?; Ok(len) } } #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl<'o, E: Encoding> io::Write for Encoder<'o, E> { fn write(&mut self, buf: &[u8]) -> io::Result { self.encode(buf)?; Ok(buf.len()) } fn flush(&mut self) -> io::Result<()> { // TODO(tarcieri): return an error if there's still data remaining in the buffer? Ok(()) } } /// Base64 encode buffer for a 1-block output. /// /// This handles a partial block of data, i.e. data which hasn't been #[derive(Clone, Default, Debug)] struct BlockBuffer { /// 3 decoded bytes to be encoded to a 4-byte Base64-encoded input. bytes: [u8; Self::SIZE], /// Position within the buffer. position: usize, } impl BlockBuffer { /// Size of the buffer in bytes: 3-bytes of unencoded input which /// Base64 encode to 4-bytes of output. const SIZE: usize = 3; /// Fill the remaining space in the buffer with the input data. fn fill(&mut self, input: &mut &[u8]) -> Result<(), Error> { let remaining = Self::SIZE.checked_sub(self.position).ok_or(InvalidLength)?; let len = cmp::min(input.len(), remaining); self.bytes[self.position..][..len].copy_from_slice(&input[..len]); self.position = self.position.checked_add(len).ok_or(InvalidLength)?; *input = &input[len..]; Ok(()) } /// Take the output buffer, resetting the position to 0. fn take(&mut self) -> [u8; Self::SIZE] { debug_assert!(self.is_full()); let result = self.bytes; *self = Default::default(); result } /// Is the buffer empty? fn is_empty(&self) -> bool { self.position == 0 } /// Is the buffer full? fn is_full(&self) -> bool { self.position == Self::SIZE } } /// Helper for wrapping Base64 at a given line width. #[derive(Debug)] struct LineWrapper { /// Number of bytes remaining in the current line. remaining: usize, /// Column at which Base64 should be wrapped. width: usize, /// Newline characters to use at the end of each line. ending: LineEnding, } impl LineWrapper { /// Create a new linewrapper. fn new(width: usize, ending: LineEnding) -> Result { if width < MIN_LINE_WIDTH { return Err(InvalidLength); } Ok(Self { remaining: width, width, ending, }) } /// Wrap the number of blocks to encode near/at EOL. fn wrap_blocks(&self, blocks: &mut usize) -> Result<(), Error> { if blocks.checked_mul(4).ok_or(InvalidLength)? >= self.remaining { *blocks = self.remaining / 4; } Ok(()) } /// Insert newlines into the output buffer as needed. fn insert_newlines(&mut self, mut buffer: &mut [u8], len: &mut usize) -> Result<(), Error> { let mut buffer_len = *len; if buffer_len <= self.remaining { self.remaining = self .remaining .checked_sub(buffer_len) .ok_or(InvalidLength)?; return Ok(()); } buffer = &mut buffer[self.remaining..]; buffer_len = buffer_len .checked_sub(self.remaining) .ok_or(InvalidLength)?; // The `wrap_blocks` function should ensure the buffer is no larger than a Base64 block debug_assert!(buffer_len <= 4, "buffer too long: {}", buffer_len); // Ensure space in buffer to add newlines let buffer_end = buffer_len .checked_add(self.ending.len()) .ok_or(InvalidLength)?; if buffer_end >= buffer.len() { return Err(InvalidLength); } // Shift the buffer contents to make space for the line ending for i in (0..buffer_len).rev() { buffer[i.checked_add(self.ending.len()).ok_or(InvalidLength)?] = buffer[i]; } buffer[..self.ending.len()].copy_from_slice(self.ending.as_bytes()); *len = (*len).checked_add(self.ending.len()).ok_or(InvalidLength)?; self.remaining = self.width.checked_sub(buffer_len).ok_or(InvalidLength)?; Ok(()) } } #[cfg(test)] mod tests { use crate::{alphabet::Alphabet, test_vectors::*, Base64, Base64Unpadded, Encoder, LineEnding}; #[test] fn encode_padded() { encode_test::(PADDED_BIN, PADDED_BASE64, None); } #[test] fn encode_unpadded() { encode_test::(UNPADDED_BIN, UNPADDED_BASE64, None); } #[test] fn encode_multiline_padded() { encode_test::(MULTILINE_PADDED_BIN, MULTILINE_PADDED_BASE64, Some(70)); } #[test] fn encode_multiline_unpadded() { encode_test::(MULTILINE_UNPADDED_BIN, MULTILINE_UNPADDED_BASE64, Some(70)); } #[test] fn no_trailing_newline_when_aligned() { let mut buffer = [0u8; 64]; let mut encoder = Encoder::::new_wrapped(&mut buffer, 64, LineEnding::LF).unwrap(); encoder.encode(&[0u8; 48]).unwrap(); // Ensure no newline character is present in this case assert_eq!( "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", encoder.finish().unwrap() ); } /// Core functionality of an encoding test. fn encode_test(input: &[u8], expected: &str, wrapped: Option) { let mut buffer = [0u8; 1024]; for chunk_size in 1..input.len() { let mut encoder = match wrapped { Some(line_width) => { Encoder::::new_wrapped(&mut buffer, line_width, LineEnding::LF) } None => Encoder::::new(&mut buffer), } .unwrap(); for chunk in input.chunks(chunk_size) { encoder.encode(chunk).unwrap(); } assert_eq!(expected, encoder.finish().unwrap()); } } } base64ct-1.5.1/src/encoding.rs000064400000000000000000000307360072674642500142270ustar 00000000000000//! Base64 encodings use crate::{ alphabet::Alphabet, errors::{Error, InvalidEncodingError, InvalidLengthError}, }; use core::str; #[cfg(feature = "alloc")] use alloc::{string::String, vec::Vec}; #[cfg(doc)] use crate::{Base64, Base64Bcrypt, Base64Crypt, Base64Unpadded, Base64Url, Base64UrlUnpadded}; /// Padding character const PAD: u8 = b'='; /// Base64 encoding trait. /// /// This trait must be imported to make use of any Base64 alphabet defined /// in this crate. /// /// The following encoding types impl this trait: /// /// - [`Base64`]: standard Base64 encoding with `=` padding. /// - [`Base64Bcrypt`]: bcrypt Base64 encoding. /// - [`Base64Crypt`]: `crypt(3)` Base64 encoding. /// - [`Base64Unpadded`]: standard Base64 encoding *without* padding. /// - [`Base64Url`]: URL-safe Base64 encoding with `=` padding. /// - [`Base64UrlUnpadded`]: URL-safe Base64 encoding *without* padding. pub trait Encoding: Alphabet { /// Decode a Base64 string into the provided destination buffer. fn decode(src: impl AsRef<[u8]>, dst: &mut [u8]) -> Result<&[u8], Error>; /// Decode a Base64 string in-place. /// /// NOTE: this method does not (yet) validate that padding is well-formed, /// if the given Base64 encoding is padded. fn decode_in_place(buf: &mut [u8]) -> Result<&[u8], InvalidEncodingError>; /// Decode a Base64 string into a byte vector. #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] fn decode_vec(input: &str) -> Result, Error>; /// Encode the input byte slice as Base64. /// /// Writes the result into the provided destination slice, returning an /// ASCII-encoded Base64 string value. fn encode<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a str, InvalidLengthError>; /// Encode input byte slice into a [`String`] containing Base64. /// /// # Panics /// If `input` length is greater than `usize::MAX/4`. #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] fn encode_string(input: &[u8]) -> String; /// Get the length of Base64 produced by encoding the given bytes. /// /// WARNING: this function will return `0` for lengths greater than `usize::MAX/4`! fn encoded_len(bytes: &[u8]) -> usize; } impl Encoding for T { fn decode(src: impl AsRef<[u8]>, dst: &mut [u8]) -> Result<&[u8], Error> { let (src_unpadded, mut err) = if T::PADDED { let (unpadded_len, e) = decode_padding(src.as_ref())?; (&src.as_ref()[..unpadded_len], e) } else { (src.as_ref(), 0) }; let dlen = decoded_len(src_unpadded.len()); if dlen > dst.len() { return Err(Error::InvalidLength); } let dst = &mut dst[..dlen]; let mut src_chunks = src_unpadded.chunks_exact(4); let mut dst_chunks = dst.chunks_exact_mut(3); for (s, d) in (&mut src_chunks).zip(&mut dst_chunks) { err |= Self::decode_3bytes(s, d); } let src_rem = src_chunks.remainder(); let dst_rem = dst_chunks.into_remainder(); err |= !(src_rem.is_empty() || src_rem.len() >= 2) as i16; let mut tmp_out = [0u8; 3]; let mut tmp_in = [b'A'; 4]; tmp_in[..src_rem.len()].copy_from_slice(src_rem); err |= Self::decode_3bytes(&tmp_in, &mut tmp_out); dst_rem.copy_from_slice(&tmp_out[..dst_rem.len()]); if err == 0 { validate_last_block::(src.as_ref(), dst)?; Ok(dst) } else { Err(Error::InvalidEncoding) } } // TODO(tarcieri): explicitly checked/wrapped arithmetic #[allow(clippy::integer_arithmetic)] fn decode_in_place(mut buf: &mut [u8]) -> Result<&[u8], InvalidEncodingError> { // TODO: eliminate unsafe code when LLVM12 is stable // See: https://github.com/rust-lang/rust/issues/80963 let mut err = if T::PADDED { let (unpadded_len, e) = decode_padding(buf)?; buf = &mut buf[..unpadded_len]; e } else { 0 }; let dlen = decoded_len(buf.len()); let full_chunks = buf.len() / 4; for chunk in 0..full_chunks { // SAFETY: `p3` and `p4` point inside `buf`, while they may overlap, // read and write are clearly separated from each other and done via // raw pointers. #[allow(unsafe_code)] unsafe { debug_assert!(3 * chunk + 3 <= buf.len()); debug_assert!(4 * chunk + 4 <= buf.len()); let p3 = buf.as_mut_ptr().add(3 * chunk) as *mut [u8; 3]; let p4 = buf.as_ptr().add(4 * chunk) as *const [u8; 4]; let mut tmp_out = [0u8; 3]; err |= Self::decode_3bytes(&*p4, &mut tmp_out); *p3 = tmp_out; } } let src_rem_pos = 4 * full_chunks; let src_rem_len = buf.len() - src_rem_pos; let dst_rem_pos = 3 * full_chunks; let dst_rem_len = dlen - dst_rem_pos; err |= !(src_rem_len == 0 || src_rem_len >= 2) as i16; let mut tmp_in = [b'A'; 4]; tmp_in[..src_rem_len].copy_from_slice(&buf[src_rem_pos..]); let mut tmp_out = [0u8; 3]; err |= Self::decode_3bytes(&tmp_in, &mut tmp_out); if err == 0 { // SAFETY: `dst_rem_len` is always smaller than 4, so we don't // read outside of `tmp_out`, write and the final slicing never go // outside of `buf`. #[allow(unsafe_code)] unsafe { debug_assert!(dst_rem_pos + dst_rem_len <= buf.len()); debug_assert!(dst_rem_len <= tmp_out.len()); debug_assert!(dlen <= buf.len()); core::ptr::copy_nonoverlapping( tmp_out.as_ptr(), buf.as_mut_ptr().add(dst_rem_pos), dst_rem_len, ); Ok(buf.get_unchecked(..dlen)) } } else { Err(InvalidEncodingError) } } #[cfg(feature = "alloc")] fn decode_vec(input: &str) -> Result, Error> { let mut output = vec![0u8; decoded_len(input.len())]; let len = Self::decode(input, &mut output)?.len(); if len <= output.len() { output.truncate(len); Ok(output) } else { Err(Error::InvalidLength) } } fn encode<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a str, InvalidLengthError> { let elen = match encoded_len_inner(src.len(), T::PADDED) { Some(v) => v, None => return Err(InvalidLengthError), }; if elen > dst.len() { return Err(InvalidLengthError); } let dst = &mut dst[..elen]; let mut src_chunks = src.chunks_exact(3); let mut dst_chunks = dst.chunks_exact_mut(4); for (s, d) in (&mut src_chunks).zip(&mut dst_chunks) { Self::encode_3bytes(s, d); } let src_rem = src_chunks.remainder(); if T::PADDED { if let Some(dst_rem) = dst_chunks.next() { let mut tmp = [0u8; 3]; tmp[..src_rem.len()].copy_from_slice(src_rem); Self::encode_3bytes(&tmp, dst_rem); let flag = src_rem.len() == 1; let mask = (flag as u8).wrapping_sub(1); dst_rem[2] = (dst_rem[2] & mask) | (PAD & !mask); dst_rem[3] = PAD; } } else { let dst_rem = dst_chunks.into_remainder(); let mut tmp_in = [0u8; 3]; let mut tmp_out = [0u8; 4]; tmp_in[..src_rem.len()].copy_from_slice(src_rem); Self::encode_3bytes(&tmp_in, &mut tmp_out); dst_rem.copy_from_slice(&tmp_out[..dst_rem.len()]); } debug_assert!(str::from_utf8(dst).is_ok()); // SAFETY: values written by `encode_3bytes` are valid one-byte UTF-8 chars #[allow(unsafe_code)] Ok(unsafe { str::from_utf8_unchecked(dst) }) } #[cfg(feature = "alloc")] fn encode_string(input: &[u8]) -> String { let elen = encoded_len_inner(input.len(), T::PADDED).expect("input is too big"); let mut dst = vec![0u8; elen]; let res = Self::encode(input, &mut dst).expect("encoding error"); debug_assert_eq!(elen, res.len()); debug_assert!(str::from_utf8(&dst).is_ok()); // SAFETY: `dst` is fully written and contains only valid one-byte UTF-8 chars #[allow(unsafe_code)] unsafe { String::from_utf8_unchecked(dst) } } fn encoded_len(bytes: &[u8]) -> usize { encoded_len_inner(bytes.len(), T::PADDED).unwrap_or(0) } } /// Validate padding is of the expected length compute unpadded length. /// /// Note that this method does not explicitly check that the padded data /// is valid in and of itself: that is performed by `validate_last_block` as a /// final step. /// /// Returns length-related errors eagerly as a [`Result`], and data-dependent /// errors (i.e. malformed padding bytes) as `i16` to be combined with other /// encoding-related errors prior to branching. #[inline(always)] pub(crate) fn decode_padding(input: &[u8]) -> Result<(usize, i16), InvalidEncodingError> { if input.len() % 4 != 0 { return Err(InvalidEncodingError); } let unpadded_len = match *input { [.., b0, b1] => is_pad_ct(b0) .checked_add(is_pad_ct(b1)) .and_then(|len| len.try_into().ok()) .and_then(|len| input.len().checked_sub(len)) .ok_or(InvalidEncodingError)?, _ => input.len(), }; let padding_len = input .len() .checked_sub(unpadded_len) .ok_or(InvalidEncodingError)?; let err = match *input { [.., b0] if padding_len == 1 => is_pad_ct(b0) ^ 1, [.., b0, b1] if padding_len == 2 => (is_pad_ct(b0) & is_pad_ct(b1)) ^ 1, _ => { if padding_len == 0 { 0 } else { return Err(InvalidEncodingError); } } }; Ok((unpadded_len, err)) } /// Validate that the last block of the decoded data round-trips back to the /// encoded data. fn validate_last_block(encoded: &[u8], decoded: &[u8]) -> Result<(), Error> { if encoded.is_empty() && decoded.is_empty() { return Ok(()); } // TODO(tarcieri): explicitly checked/wrapped arithmetic #[allow(clippy::integer_arithmetic)] fn last_block_start(bytes: &[u8], block_size: usize) -> usize { (bytes.len().saturating_sub(1) / block_size) * block_size } let enc_block = encoded .get(last_block_start(encoded, 4)..) .ok_or(Error::InvalidEncoding)?; let dec_block = decoded .get(last_block_start(decoded, 3)..) .ok_or(Error::InvalidEncoding)?; // Round-trip encode the decoded block let mut buf = [0u8; 4]; let block = T::encode(dec_block, &mut buf)?; // Non-short-circuiting comparison of padding // TODO(tarcieri): better constant-time mechanisms (e.g. `subtle`)? if block .as_bytes() .iter() .zip(enc_block.iter()) .fold(0, |acc, (a, b)| acc | (a ^ b)) == 0 { Ok(()) } else { Err(Error::InvalidEncoding) } } /// Get the length of the output from decoding the provided *unpadded* /// Base64-encoded input. /// /// Note that this function does not fully validate the Base64 is well-formed /// and may return incorrect results for malformed Base64. // TODO(tarcieri): explicitly checked/wrapped arithmetic #[allow(clippy::integer_arithmetic)] #[inline(always)] pub(crate) fn decoded_len(input_len: usize) -> usize { // overflow-proof computation of `(3*n)/4` let k = input_len / 4; let l = input_len - 4 * k; 3 * k + (3 * l) / 4 } /// Branchless match that a given byte is the `PAD` character // TODO(tarcieri): explicitly checked/wrapped arithmetic #[allow(clippy::integer_arithmetic)] #[inline(always)] fn is_pad_ct(input: u8) -> i16 { ((((PAD as i16 - 1) - input as i16) & (input as i16 - (PAD as i16 + 1))) >> 8) & 1 } // TODO(tarcieri): explicitly checked/wrapped arithmetic #[allow(clippy::integer_arithmetic)] #[inline(always)] const fn encoded_len_inner(n: usize, padded: bool) -> Option { match n.checked_mul(4) { Some(q) => { if padded { Some(((q / 3) + 3) & !3) } else { Some((q / 3) + (q % 3 != 0) as usize) } } None => None, } } base64ct-1.5.1/src/errors.rs000064400000000000000000000043470072674642500137540ustar 00000000000000//! Error types use core::fmt; const INVALID_ENCODING_MSG: &str = "invalid Base64 encoding"; const INVALID_LENGTH_MSG: &str = "invalid Base64 length"; /// Insufficient output buffer length. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct InvalidLengthError; impl fmt::Display for InvalidLengthError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { f.write_str(INVALID_LENGTH_MSG) } } #[cfg(feature = "std")] impl std::error::Error for InvalidLengthError {} /// Invalid encoding of provided Base64 string. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct InvalidEncodingError; impl fmt::Display for InvalidEncodingError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { f.write_str(INVALID_ENCODING_MSG) } } #[cfg(feature = "std")] impl std::error::Error for InvalidEncodingError {} /// Generic error, union of [`InvalidLengthError`] and [`InvalidEncodingError`]. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Error { /// Invalid encoding of provided Base64 string. InvalidEncoding, /// Insufficient output buffer length. InvalidLength, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { let s = match self { Self::InvalidEncoding => INVALID_ENCODING_MSG, Self::InvalidLength => INVALID_LENGTH_MSG, }; f.write_str(s) } } impl From for Error { #[inline] fn from(_: InvalidEncodingError) -> Error { Error::InvalidEncoding } } impl From for Error { #[inline] fn from(_: InvalidLengthError) -> Error { Error::InvalidLength } } impl From for Error { #[inline] fn from(_: core::str::Utf8Error) -> Error { Error::InvalidEncoding } } #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl From for std::io::Error { fn from(err: Error) -> std::io::Error { // TODO(tarcieri): better customize `ErrorKind`? std::io::Error::new(std::io::ErrorKind::InvalidData, err) } } #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl std::error::Error for Error {} base64ct-1.5.1/src/lib.rs000064400000000000000000000052470072674642500132060ustar 00000000000000#![no_std] #![cfg_attr(docsrs, feature(doc_cfg))] #![doc( html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", html_root_url = "https://docs.rs/base64ct/1.4.1" )] #![doc = include_str!("../README.md")] #![warn( clippy::integer_arithmetic, clippy::panic, clippy::panic_in_result_fn, clippy::unwrap_used, missing_docs, rust_2018_idioms, unsafe_code, unused_lifetimes, unused_qualifications )] //! # Usage //! //! ## Allocating (enable `alloc` crate feature) //! //! ``` //! # #[cfg(feature = "alloc")] //! # { //! use base64ct::{Base64, Encoding}; //! //! let bytes = b"example bytestring!"; //! let encoded = Base64::encode_string(bytes); //! assert_eq!(encoded, "ZXhhbXBsZSBieXRlc3RyaW5nIQ=="); //! //! let decoded = Base64::decode_vec(&encoded).unwrap(); //! assert_eq!(decoded, bytes); //! # } //! ``` //! //! ## Heapless `no_std` usage //! //! ``` //! use base64ct::{Base64, Encoding}; //! //! const BUF_SIZE: usize = 128; //! //! let bytes = b"example bytestring!"; //! assert!(Base64::encoded_len(bytes) <= BUF_SIZE); //! //! let mut enc_buf = [0u8; BUF_SIZE]; //! let encoded = Base64::encode(bytes, &mut enc_buf).unwrap(); //! assert_eq!(encoded, "ZXhhbXBsZSBieXRlc3RyaW5nIQ=="); //! //! let mut dec_buf = [0u8; BUF_SIZE]; //! let decoded = Base64::decode(encoded, &mut dec_buf).unwrap(); //! assert_eq!(decoded, bytes); //! ``` //! //! # Implementation //! //! Implemented using integer arithmetic alone without any lookup tables or //! data-dependent branches, thereby providing portable "best effort" //! constant-time operation. //! //! Not constant-time with respect to message length (only data). //! //! Adapted from the following constant-time C++ implementation of Base64: //! //! //! //! Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com). //! Derived code is dual licensed MIT + Apache 2 (with permission from Sc00bz). #[cfg(feature = "alloc")] #[macro_use] extern crate alloc; #[cfg(feature = "std")] extern crate std; mod alphabet; mod decoder; mod encoder; mod encoding; mod errors; mod line_ending; #[cfg(test)] mod test_vectors; pub use crate::{ alphabet::{ bcrypt::Base64Bcrypt, crypt::Base64Crypt, standard::{Base64, Base64Unpadded}, url::{Base64Url, Base64UrlUnpadded}, }, decoder::Decoder, encoder::Encoder, encoding::Encoding, errors::{Error, InvalidEncodingError, InvalidLengthError}, line_ending::LineEnding, }; /// Minimum supported line width. const MIN_LINE_WIDTH: usize = 4; base64ct-1.5.1/src/line_ending.rs000064400000000000000000000024700072674642500147060ustar 00000000000000//! Line endings. /// Carriage return pub(crate) const CHAR_CR: u8 = 0x0d; /// Line feed pub(crate) const CHAR_LF: u8 = 0x0a; /// Line endings: variants of newline characters that can be used with Base64. /// /// Use [`LineEnding::default`] to get an appropriate line ending for the /// current operating system. #[allow(clippy::upper_case_acronyms)] #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub enum LineEnding { /// Carriage return: `\r` (Pre-OS X Macintosh) CR, /// Line feed: `\n` (Unix OSes) LF, /// Carriage return + line feed: `\r\n` (Windows) CRLF, } impl Default for LineEnding { // Default line ending matches conventions for target OS #[cfg(windows)] fn default() -> LineEnding { LineEnding::CRLF } #[cfg(not(windows))] fn default() -> LineEnding { LineEnding::LF } } #[allow(clippy::len_without_is_empty)] impl LineEnding { /// Get the byte serialization of this [`LineEnding`]. pub fn as_bytes(self) -> &'static [u8] { match self { LineEnding::CR => &[CHAR_CR], LineEnding::LF => &[CHAR_LF], LineEnding::CRLF => &[CHAR_CR, CHAR_LF], } } /// Get the encoded length of this [`LineEnding`]. pub fn len(self) -> usize { self.as_bytes().len() } } base64ct-1.5.1/src/test_vectors.rs000064400000000000000000000117100072674642500151540ustar 00000000000000//! Base64 test vectors. /// Padded Base64-encoded example pub(crate) const PADDED_BASE64: &str = "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHwf2HMM5TRXvo2SQJjsNkiDD5KqiiNjrGVv3UUh+mMT5RHxiRtOnlqvjhQtBq0VpmpCV/PwUdhOig4vkbqAcEc="; pub(crate) const PADDED_BIN: &[u8] = &[ 0, 0, 0, 19, 101, 99, 100, 115, 97, 45, 115, 104, 97, 50, 45, 110, 105, 115, 116, 112, 50, 53, 54, 0, 0, 0, 8, 110, 105, 115, 116, 112, 50, 53, 54, 0, 0, 0, 65, 4, 124, 31, 216, 115, 12, 229, 52, 87, 190, 141, 146, 64, 152, 236, 54, 72, 131, 15, 146, 170, 138, 35, 99, 172, 101, 111, 221, 69, 33, 250, 99, 19, 229, 17, 241, 137, 27, 78, 158, 90, 175, 142, 20, 45, 6, 173, 21, 166, 106, 66, 87, 243, 240, 81, 216, 78, 138, 14, 47, 145, 186, 128, 112, 71, ]; /// Unpadded Base64-encoded example pub(crate) const UNPADDED_BASE64: &str = "AAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqti"; pub(crate) const UNPADDED_BIN: &[u8] = &[ 0, 0, 0, 11, 115, 115, 104, 45, 101, 100, 50, 53, 53, 49, 57, 0, 0, 0, 32, 179, 62, 174, 243, 126, 162, 223, 124, 170, 1, 13, 239, 222, 163, 78, 36, 31, 101, 241, 181, 41, 164, 244, 62, 209, 67, 39, 245, 197, 74, 171, 98, ]; /// Padded multi-line Base64 example (from the `ssh-key` crate's `id_ed25519`) pub(crate) const MULTILINE_PADDED_BASE64: &str = "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n\ QyNTUxOQAAACCzPq7zfqLffKoBDe/eo04kH2XxtSmk9D7RQyf1xUqrYgAAAJgAIAxdACAM\n\ XQAAAAtzc2gtZWQyNTUxOQAAACCzPq7zfqLffKoBDe/eo04kH2XxtSmk9D7RQyf1xUqrYg\n\ AAAEC2BsIi0QwW2uFscKTUUXNHLsYX4FxlaSDSblbAj7WR7bM+rvN+ot98qgEN796jTiQf\n\ ZfG1KaT0PtFDJ/XFSqtiAAAAEHVzZXJAZXhhbXBsZS5jb20BAgMEBQ=="; pub(crate) const MULTILINE_PADDED_BIN: &[u8] = &[ 111, 112, 101, 110, 115, 115, 104, 45, 107, 101, 121, 45, 118, 49, 0, 0, 0, 0, 4, 110, 111, 110, 101, 0, 0, 0, 4, 110, 111, 110, 101, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 51, 0, 0, 0, 11, 115, 115, 104, 45, 101, 100, 50, 53, 53, 49, 57, 0, 0, 0, 32, 179, 62, 174, 243, 126, 162, 223, 124, 170, 1, 13, 239, 222, 163, 78, 36, 31, 101, 241, 181, 41, 164, 244, 62, 209, 67, 39, 245, 197, 74, 171, 98, 0, 0, 0, 152, 0, 32, 12, 93, 0, 32, 12, 93, 0, 0, 0, 11, 115, 115, 104, 45, 101, 100, 50, 53, 53, 49, 57, 0, 0, 0, 32, 179, 62, 174, 243, 126, 162, 223, 124, 170, 1, 13, 239, 222, 163, 78, 36, 31, 101, 241, 181, 41, 164, 244, 62, 209, 67, 39, 245, 197, 74, 171, 98, 0, 0, 0, 64, 182, 6, 194, 34, 209, 12, 22, 218, 225, 108, 112, 164, 212, 81, 115, 71, 46, 198, 23, 224, 92, 101, 105, 32, 210, 110, 86, 192, 143, 181, 145, 237, 179, 62, 174, 243, 126, 162, 223, 124, 170, 1, 13, 239, 222, 163, 78, 36, 31, 101, 241, 181, 41, 164, 244, 62, 209, 67, 39, 245, 197, 74, 171, 98, 0, 0, 0, 16, 117, 115, 101, 114, 64, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 1, 2, 3, 4, 5, ]; /// Unpadded multi-line Base64 example (from the `ssh-key` crate's `id_ecdsa_p256`). pub(crate) const MULTILINE_UNPADDED_BASE64: &str = "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS\n\ 1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQR8H9hzDOU0V76NkkCY7DZIgw+Sqooj\n\ Y6xlb91FIfpjE+UR8YkbTp5ar44ULQatFaZqQlfz8FHYTooOL5G6gHBHAAAAsB8RBhUfEQ\n\ YVAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHwf2HMM5TRXvo2S\n\ QJjsNkiDD5KqiiNjrGVv3UUh+mMT5RHxiRtOnlqvjhQtBq0VpmpCV/PwUdhOig4vkbqAcE\n\ cAAAAhAMp4pkd0v643EjIkk38DmJYBiXB6ygqGRc60NZxCO6B5AAAAEHVzZXJAZXhhbXBs\n\ ZS5jb20BAgMEBQYH"; pub(crate) const MULTILINE_UNPADDED_BIN: &[u8] = &[ 111, 112, 101, 110, 115, 115, 104, 45, 107, 101, 121, 45, 118, 49, 0, 0, 0, 0, 4, 110, 111, 110, 101, 0, 0, 0, 4, 110, 111, 110, 101, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 104, 0, 0, 0, 19, 101, 99, 100, 115, 97, 45, 115, 104, 97, 50, 45, 110, 105, 115, 116, 112, 50, 53, 54, 0, 0, 0, 8, 110, 105, 115, 116, 112, 50, 53, 54, 0, 0, 0, 65, 4, 124, 31, 216, 115, 12, 229, 52, 87, 190, 141, 146, 64, 152, 236, 54, 72, 131, 15, 146, 170, 138, 35, 99, 172, 101, 111, 221, 69, 33, 250, 99, 19, 229, 17, 241, 137, 27, 78, 158, 90, 175, 142, 20, 45, 6, 173, 21, 166, 106, 66, 87, 243, 240, 81, 216, 78, 138, 14, 47, 145, 186, 128, 112, 71, 0, 0, 0, 176, 31, 17, 6, 21, 31, 17, 6, 21, 0, 0, 0, 19, 101, 99, 100, 115, 97, 45, 115, 104, 97, 50, 45, 110, 105, 115, 116, 112, 50, 53, 54, 0, 0, 0, 8, 110, 105, 115, 116, 112, 50, 53, 54, 0, 0, 0, 65, 4, 124, 31, 216, 115, 12, 229, 52, 87, 190, 141, 146, 64, 152, 236, 54, 72, 131, 15, 146, 170, 138, 35, 99, 172, 101, 111, 221, 69, 33, 250, 99, 19, 229, 17, 241, 137, 27, 78, 158, 90, 175, 142, 20, 45, 6, 173, 21, 166, 106, 66, 87, 243, 240, 81, 216, 78, 138, 14, 47, 145, 186, 128, 112, 71, 0, 0, 0, 33, 0, 202, 120, 166, 71, 116, 191, 174, 55, 18, 50, 36, 147, 127, 3, 152, 150, 1, 137, 112, 122, 202, 10, 134, 69, 206, 180, 53, 156, 66, 59, 160, 121, 0, 0, 0, 16, 117, 115, 101, 114, 64, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 1, 2, 3, 4, 5, 6, 7, ]; base64ct-1.5.1/tests/bcrypt.rs000064400000000000000000000032660072674642500143150ustar 00000000000000//! bcrypt Base64 tests #[macro_use] mod common; use crate::common::*; use base64ct::Base64Bcrypt; const TEST_VECTORS: &[TestVector] = &[ TestVector { raw: b"", b64: "" }, TestVector { raw: b"\0", b64: "..", }, TestVector { raw: b"***", b64: "Igmo", }, TestVector { raw: b"\x01\x02\x03\x04", b64: ".OGB/.", }, TestVector { raw: b"\xAD\xAD\xAD\xAD\xAD", b64: "pY0rpYy", }, TestVector { raw: b"\xFF\xEF\xFE\xFF\xEF\xFE", b64: "98989898", }, TestVector { raw: b"\xFF\xFF\xFF\xFF\xFF", b64: "9999996", }, TestVector { raw: b"\x40\xC1\x3F\xBD\x05\x4C\x72\x2A\xA3\xC2\xF2\x11\x73\xC0\x69\xEA\ \x49\x7D\x35\x29\x6B\xCC\x24\x65\xF6\xF9\xD0\x41\x08\x7B\xD7\xA9", b64: "OKC9tOTKagohutGPa6/n4ij7LQjpxAPj7tlOOOf5z4i", }, TestVector { raw: b"\x00\x10\x83\x10Q\x87 \x92\x8B0\xD3\x8FA\x14\x93QU\x97a\x96\x9Bq\ \xD7\x9F\x82\x18\xA3\x92Y\xA7\xA2\x9A\xAB\xB2\xDB\xAF\xC3\x1C\xB3\ \xFB\xF0\x00", b64: "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwx89..", }, ]; impl_tests!(Base64Bcrypt); #[test] fn reject_trailing_whitespace() { let input = "OKC9tOTKagohutGPa6/n4ij7LQjpxAPj7tlOOOf5z4i\n"; let mut buf = [0u8; 1024]; assert_eq!( Base64Bcrypt::decode(input, &mut buf), Err(Error::InvalidEncoding) ); } #[test] fn unpadded_reject_trailing_equals() { let input = "OKC9tOTKagohutGPa6/n4ij7LQjpxAPj7tlOOOf5z4i="; let mut buf = [0u8; 1024]; assert_eq!( Base64Bcrypt::decode(input, &mut buf), Err(Error::InvalidEncoding) ); } base64ct-1.5.1/tests/common/mod.rs000064400000000000000000000051400072674642500150520ustar 00000000000000//! Common testing functionality /// Base64 test vector pub struct TestVector { pub raw: &'static [u8], pub b64: &'static str, } /// Generate test suite for a particular Base64 flavor #[macro_export] macro_rules! impl_tests { ($encoding:ty) => { use base64ct::{Encoding, Error}; #[test] fn encode_test_vectors() { let mut buf = [0u8; 1024]; for vector in TEST_VECTORS { let out = <$encoding>::encode(vector.raw, &mut buf).unwrap(); assert_eq!(<$encoding>::encoded_len(vector.raw), vector.b64.len()); assert_eq!(vector.b64, &out[..]); #[cfg(feature = "alloc")] { let out = <$encoding>::encode_string(vector.raw); assert_eq!(vector.b64, &out[..]); } } } #[test] fn decode_test_vectors() { let mut buf = [0u8; 1024]; for vector in TEST_VECTORS { let out = <$encoding>::decode(vector.b64, &mut buf).unwrap(); assert_eq!(vector.raw, &out[..]); let n = vector.b64.len(); buf[..n].copy_from_slice(vector.b64.as_bytes()); let out = <$encoding>::decode_in_place(&mut buf[..n]).unwrap(); assert_eq!(vector.raw, out); #[cfg(feature = "alloc")] { let out = <$encoding>::decode_vec(vector.b64).unwrap(); assert_eq!(vector.raw, &out[..]); } } } #[test] fn encode_and_decode_various_lengths() { let data = [b'X'; 64]; let mut inbuf = [0u8; 1024]; let mut outbuf = [0u8; 1024]; for i in 0..data.len() { let encoded = <$encoding>::encode(&data[..i], &mut inbuf).unwrap(); // Make sure it round trips let decoded = <$encoding>::decode(encoded, &mut outbuf).unwrap(); assert_eq!(decoded, &data[..i]); let elen = <$encoding>::encode(&data[..i], &mut inbuf).unwrap().len(); let buf = &mut inbuf[..elen]; let decoded = <$encoding>::decode_in_place(buf).unwrap(); assert_eq!(decoded, &data[..i]); #[cfg(feature = "alloc")] { let encoded = <$encoding>::encode_string(&data[..i]); let decoded = <$encoding>::decode_vec(&encoded).unwrap(); assert_eq!(decoded, &data[..i]); } } } }; } base64ct-1.5.1/tests/crypt.rs000064400000000000000000000032660072674642500141530ustar 00000000000000//! `crypt(3)` Base64 tests #[macro_use] mod common; use crate::common::*; use base64ct::Base64Crypt; const TEST_VECTORS: &[TestVector] = &[ TestVector { raw: b"", b64: "" }, TestVector { raw: b"\0", b64: "..", }, TestVector { raw: b"***", b64: "8Wce", }, TestVector { raw: b"\x01\x02\x03\x04", b64: ".E61/.", }, TestVector { raw: b"\xAD\xAD\xAD\xAD\xAD", b64: "fOqhfOo", }, TestVector { raw: b"\xFF\xEF\xFE\xFF\xEF\xFE", b64: "zyzyzyzy", }, TestVector { raw: b"\xFF\xFF\xFF\xFF\xFF", b64: "zzzzzzw", }, TestVector { raw: b"\x40\xC1\x3F\xBD\x05\x4C\x72\x2A\xA3\xC2\xF2\x11\x73\xC0\x69\xEA\ \x49\x7D\x35\x29\x6B\xCC\x24\x65\xF6\xF9\xD0\x41\x08\x7B\xD7\xA9", b64: "EA2zjEJAQWeXkj6FQw/duYZxBGZfn0FZxjbEEEVvpuY", }, TestVector { raw: b"\x00\x10\x83\x10Q\x87 \x92\x8B0\xD3\x8FA\x14\x93QU\x97a\x96\x9Bq\ \xD7\x9F\x82\x18\xA3\x92Y\xA7\xA2\x9A\xAB\xB2\xDB\xAF\xC3\x1C\xB3\ \xFB\xF0\x00", b64: "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnyz..", }, ]; impl_tests!(Base64Crypt); #[test] fn reject_trailing_whitespace() { let input = "OKC9tOTKagohutGPa6/n4ij7LQjpxAPj7tlOOOf5z4i\n"; let mut buf = [0u8; 1024]; assert_eq!( Base64Crypt::decode(input, &mut buf), Err(Error::InvalidEncoding) ); } #[test] fn unpadded_reject_trailing_equals() { let input = "OKC9tOTKagohutGPa6/n4ij7LQjpxAPj7tlOOOf5z4i="; let mut buf = [0u8; 1024]; assert_eq!( Base64Crypt::decode(input, &mut buf), Err(Error::InvalidEncoding) ); } base64ct-1.5.1/tests/proptests.proptest-regressions000064400000000000000000000122610072674642500206450ustar 00000000000000# Seeds for failure cases proptest has generated in the past. It is # automatically read and these particular cases re-run before any # novel cases are generated. # # It is recommended to check this file in to source control so that # everyone who runs the test benefits from these saved cases. cc ea4af6a6a3c5feddd17be51d3bb3d863881547acf50b553e76da3f34f8b755d4 # shrinks to base64ish = "" cc 348d4acf2c3d1e8db3772f5645179e24b50178747469da9709e60800175eef80 # shrinks to bytes = [240, 144, 128, 128, 240, 144, 128, 128, 32, 32, 32, 194, 161, 48, 97, 97, 65, 194, 161, 32, 97, 194, 161, 32, 240, 144, 128, 128, 194, 161, 48, 32, 97, 194, 161, 240, 144, 128, 128, 32, 224, 160, 128, 97, 224, 160, 128, 48, 48, 194, 161, 32, 240, 144, 128, 128, 11, 65, 97, 48, 65, 65, 97, 11, 240, 144, 128, 128, 240, 144, 128, 128, 48, 224, 160, 128, 194, 161, 32, 32, 194, 161, 32, 48, 97, 240, 144, 128, 128, 224, 160, 128, 240, 144, 128, 128, 0, 224, 160, 128, 32, 240, 144, 128, 128, 0, 32, 32, 97, 240, 144, 128, 128, 240, 144, 128, 128, 240, 144, 128, 128, 240, 144, 128, 128, 0, 0, 240, 144, 128, 128, 32, 240, 144, 128, 128, 32, 48, 65, 11, 32, 65, 48, 48, 65, 65, 194, 161, 32, 224, 160, 128, 240, 144, 128, 128, 224, 160, 128, 0, 65, 0, 65, 32, 194, 161, 240, 144, 128, 128, 32, 65, 32, 0, 97, 32, 97, 11, 11, 48, 97, 97, 240, 144, 128, 128, 65, 240, 144, 128, 128, 194, 161], line_width = 10, chunk_size = 163 cc 0c0ee7f6a60d24431333f5c39c506b818a6c21022e39288619c8f78f29d30b1c # shrinks to bytes = [240, 144, 128, 128, 194, 161, 194, 161, 240, 144, 128, 128, 194, 161, 240, 144, 128, 128, 65, 224, 160, 128, 97, 224, 160, 128, 32, 97, 32, 65, 224, 160, 128, 0, 97, 0, 240, 144, 128, 128, 97, 194, 161, 32, 240, 144, 128, 128, 11, 48, 32, 65, 32, 240, 144, 128, 128, 97, 194, 161, 48, 48, 240, 144, 128, 128, 194, 161, 194, 161, 32, 194, 161, 48, 0, 32, 48, 224, 160, 128, 65, 240, 144, 128, 128, 11, 65, 11, 240, 144, 128, 128, 32, 32, 194, 161, 240, 144, 128, 128, 224, 160, 128, 240, 144, 128, 128, 194, 161, 224, 160, 128, 65, 32, 240, 144, 128, 128, 32, 240, 144, 128, 128, 48, 240, 144, 128, 128, 0, 48, 240, 144, 128, 128, 48, 65, 65, 11, 0, 65, 240, 144, 128, 128, 240, 144, 128, 128, 32, 65, 240, 144, 128, 128, 112, 75, 46, 232, 143, 132, 240, 159, 149, 180, 101, 92, 11, 42, 98, 244, 142, 150, 136, 83, 13, 243, 189, 168, 131, 194, 154, 9, 243, 129, 165, 130, 241, 138, 188, 150, 39, 241, 170, 133, 154, 39, 61, 244, 136, 146, 157, 46, 91, 108, 34, 66, 0, 239, 187, 191, 34, 240, 158, 187, 152, 241, 187, 172, 188, 46, 239, 191, 189, 244, 143, 139, 131, 13, 13, 226, 128, 174, 60, 200, 186, 194, 151, 27, 105, 43, 226, 128, 174, 70, 0, 38, 127, 194, 133, 195, 177, 123, 127, 121, 241, 128, 141, 141, 244, 137, 146, 189, 55, 54, 9, 240, 159, 149, 180, 2, 209, 168, 239, 187, 191, 11, 34, 123, 32, 42, 242, 171, 149, 149, 102, 241, 174, 190, 188, 242, 144, 186, 145, 1, 84, 34, 56, 7, 0, 194, 188, 43, 117, 48, 96, 11, 60, 242, 190, 170, 187, 47, 99, 37, 241, 175, 142, 186, 240, 178, 162, 136, 46, 2, 241, 176, 162, 162, 37, 242, 148, 135, 179, 11, 36, 104, 244, 130, 136, 177], line_width = 24, chunk_size = 240 cc b6d81102accbff17f00786b06c6040fc59fee8aa087033c9b5604d2a3f246afd # shrinks to bytes = [32, 65, 11, 97, 97, 32, 240, 144, 128, 128, 97, 32, 65, 0, 0, 32, 240, 144, 128, 128, 97, 65, 97, 97, 240, 144, 128, 128, 240, 144, 128, 128, 65, 48, 240, 144, 128, 128, 240, 144, 128, 128, 32, 0, 97, 97, 240, 144, 128, 128, 65, 32, 194, 161, 65, 0, 32, 11, 97, 32, 32, 11, 32, 240, 144, 128, 128, 240, 144, 128, 128, 194, 128, 32, 48, 65, 32, 240, 144, 128, 128, 240, 144, 128, 128, 240, 144, 128, 128, 194, 161, 32, 194, 161, 48, 224, 160, 128, 240, 144, 128, 128, 97, 32, 0, 48, 240, 144, 128, 128, 0, 11, 240, 144, 128, 128, 97, 240, 144, 128, 128, 11, 32, 0, 32, 0, 194, 161, 194, 161, 56, 242, 150, 180, 168, 243, 187, 153, 181, 46, 36, 121, 70, 8, 226, 128, 174, 242, 135, 172, 189, 0, 194, 169, 244, 130, 145, 146, 240, 159, 149, 180, 63, 240, 184, 155, 139, 27, 243, 185, 138, 139, 194, 162, 46, 242, 148, 129, 171, 195, 143, 56, 241, 147, 151, 173, 240, 159, 149, 180, 33, 89, 36, 37, 240, 159, 149, 180, 200, 186, 117, 194, 165, 77, 241, 171, 180, 143, 60, 96, 242, 175, 134, 177, 27, 1, 42, 242, 145, 189, 151, 92, 39, 96, 38, 243, 181, 148, 171, 243, 164, 185, 188, 47, 195, 181, 0, 226, 128, 174, 13, 233, 136, 141, 57, 200, 186, 243, 129, 145, 159, 242, 137, 177, 176, 122, 61, 243, 140, 180, 151, 239, 191, 189, 80, 194, 144, 121, 42, 239, 191, 189, 231, 173, 145, 75, 91, 0, 123, 238, 154, 139, 58, 240, 179, 187, 172, 107, 13, 13, 123, 241, 152, 132, 160, 242, 130, 149, 190, 92, 239, 187, 191, 117, 241, 182, 130, 165, 241, 165, 155, 168, 39, 60, 0, 0, 13, 200, 186, 83, 37, 243, 174, 183, 166, 11, 0, 237, 134, 157, 39, 58, 113, 44, 243, 135, 142, 174, 9, 9, 195, 184, 74, 241, 146, 132, 133, 34, 58, 92, 123, 239, 187, 191, 37, 58, 239, 187, 191, 77, 9, 243, 183, 143, 189, 243, 159, 143, 171, 243, 162, 128, 179, 241, 137, 158, 163, 127, 60, 195, 159, 106, 47, 242, 135, 154, 161, 51, 243, 160, 136, 149, 91, 241, 175, 181, 149, 96, 58, 46, 11, 37, 107, 32, 52, 237, 136, 144, 77, 194, 156, 42, 13, 39, 61, 2, 59, 48, 58, 240, 159, 149, 180, 4, 96, 127, 230, 166, 145, 58, 239, 187, 191, 242, 135, 132, 146, 241, 178, 129, 185, 36], line_width = 118, chunk_size = 147 base64ct-1.5.1/tests/proptests.rs000064400000000000000000000121160072674642500150470ustar 00000000000000//! Equivalence tests between `base64` crate and `base64ct`. #![cfg(feature = "std")] use base64ct::{Base64 as Base64ct, Encoding}; use proptest::{prelude::*, string::*}; use std::iter; /// Incremental Base64 decoder. type Decoder<'a> = base64ct::Decoder<'a, Base64ct>; /// Incremental Base64 encoder. type Encoder<'a> = base64ct::Encoder<'a, Base64ct>; proptest! { /// Ensure `base64ct` decodes data encoded by `base64` ref crate #[test] fn decode_equiv(bytes in bytes_regex(".{0,256}").unwrap()) { let encoded = base64::encode(&bytes); let decoded = Base64ct::decode_vec(&encoded); prop_assert_eq!(Ok(bytes), decoded); } /// Ensure that `base64ct`'s incremental decoder is able to decode randomly /// generated inputs encoded by the `base64` ref crate #[test] fn decode_incremental(bytes in bytes_regex(".{1,256}").unwrap(), chunk_size in 1..256usize) { let encoded = base64::encode(&bytes); let chunk_size = match chunk_size % bytes.len() { 0 => 1, n => n }; let mut buffer = [0u8; 384]; let mut decoder = Decoder::new(encoded.as_bytes()).unwrap(); let mut remaining_len = decoder.remaining_len(); for chunk in bytes.chunks(chunk_size) { prop_assert!(!decoder.is_finished()); let decoded = decoder.decode(&mut buffer[..chunk.len()]); prop_assert_eq!(Ok(chunk), decoded); remaining_len -= decoded.unwrap().len(); prop_assert_eq!(remaining_len, decoder.remaining_len()); } prop_assert!(decoder.is_finished()); prop_assert_eq!(decoder.remaining_len(), 0); } #[test] fn decode_incremental_wrapped( bytes in bytes_regex(".{1,256}").unwrap(), line_width in 4..128usize, chunk_size in 1..256usize ) { for line_ending in ["\r", "\n", "\r\n"] { let encoded = base64::encode(&bytes); let mut encoded_wrapped = Vec::new(); let mut lines = encoded.as_bytes().chunks_exact(line_width); for line in &mut lines { encoded_wrapped.extend_from_slice(line); encoded_wrapped.extend_from_slice(line_ending.as_bytes()); } let last = lines.remainder(); if last.is_empty() { encoded_wrapped.truncate(encoded_wrapped.len() - line_ending.len()); } else { encoded_wrapped.extend_from_slice(last); } let chunk_size = match chunk_size % bytes.len() { 0 => 1, n => n }; let mut buffer = [0u8; 384]; let mut decoder = Decoder::new_wrapped(&encoded_wrapped, line_width).unwrap(); let mut remaining_len = decoder.remaining_len(); for chunk in bytes.chunks(chunk_size) { prop_assert!(!decoder.is_finished()); let decoded = decoder.decode(&mut buffer[..chunk.len()]); prop_assert_eq!(Ok(chunk), decoded); remaining_len -= decoded.unwrap().len(); prop_assert_eq!(remaining_len, decoder.remaining_len()); } prop_assert!(decoder.is_finished()); prop_assert_eq!(decoder.remaining_len(), 0); } } /// Ensure `base64ct` and `base64` ref crate decode randomly generated /// inputs equivalently. /// /// Inputs are selected to be valid characters in the standard Base64 /// padded alphabet, but are not necessarily valid Base64. #[test] fn decode_random(base64ish in string_regex("[A-Za-z0-9+/]{0,256}").unwrap()) { let base64ish_padded = match base64ish.len() % 4 { 0 => base64ish, n => { let padding_len = 4 - n; base64ish + &iter::repeat("=").take(padding_len).collect::() } }; let decoded_ct = Base64ct::decode_vec(&base64ish_padded).ok(); let decoded_ref = base64::decode(&base64ish_padded).ok(); prop_assert_eq!(decoded_ct, decoded_ref); } /// Ensure `base64ct` and the `base64` ref crate encode randomly generated /// inputs equivalently. #[test] fn encode_equiv(bytes in bytes_regex(".{0,256}").unwrap()) { let encoded_ct = Base64ct::encode_string(&bytes); let encoded_ref = base64::encode(&bytes); prop_assert_eq!(encoded_ct, encoded_ref); } /// Ensure that `base64ct`'s incremental encoder is able to encode randomly /// generated inputs which match what's encoded by the `base64` ref crate #[test] fn encode_incremental(bytes in bytes_regex(".{1,256}").unwrap(), chunk_size in 1..256usize) { let expected = base64::encode(&bytes); let chunk_size = match chunk_size % bytes.len() { 0 => 1, n => n }; let mut buffer = [0u8; 1024]; let mut encoder = Encoder::new(&mut buffer).unwrap(); for chunk in bytes.chunks(chunk_size) { encoder.encode(chunk).unwrap(); } prop_assert_eq!(expected, encoder.finish().unwrap()); } } base64ct-1.5.1/tests/standard.rs000064400000000000000000000077630072674642500146200ustar 00000000000000//! Standard Base64 tests #[macro_use] mod common; /// Standard Base64 with `=` padding mod padded { use crate::common::*; use base64ct::Base64; const TEST_VECTORS: &[TestVector] = &[ TestVector { raw: b"", b64: "" }, TestVector { raw: b"\0", b64: "AA==", }, TestVector { raw: b"***", b64: "Kioq", }, TestVector { raw: b"\x01\x02\x03\x04", b64: "AQIDBA==", }, TestVector { raw: b"\xAD\xAD\xAD\xAD\xAD", b64: "ra2tra0=", }, TestVector { raw: b"\xFF\xEF\xFE\xFF\xEF\xFE", b64: "/+/+/+/+", }, TestVector { raw: b"\xFF\xFF\xFF\xFF\xFF", b64: "//////8=", }, TestVector { raw: b"\x40\xC1\x3F\xBD\x05\x4C\x72\x2A\xA3\xC2\xF2\x11\x73\xC0\x69\xEA\ \x49\x7D\x35\x29\x6B\xCC\x24\x65\xF6\xF9\xD0\x41\x08\x7B\xD7\xA9", b64: "QME/vQVMciqjwvIRc8Bp6kl9NSlrzCRl9vnQQQh716k=", }, TestVector { raw: b"\x00\x10\x83\x10Q\x87 \x92\x8B0\xD3\x8FA\x14\x93QU\x97a\x96\x9Bq\ \xD7\x9F\x82\x18\xA3\x92Y\xA7\xA2\x9A\xAB\xB2\xDB\xAF\xC3\x1C\xB3\ \xFB\xF0\x00", b64: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/AA", }, ]; impl_tests!(Base64); #[test] fn reject_trailing_whitespace() { let input = "QME/vQVMciqjwvIRc8Bp6kl9NSlrzCRl9vnQQQh716k\n"; let mut buf = [0u8; 1024]; assert_eq!(Base64::decode(input, &mut buf), Err(Error::InvalidEncoding)); } #[test] fn reject_invalid_padding() { let input = "AA/="; let mut buf = [0u8; 1024]; assert_eq!(Base64::decode(input, &mut buf), Err(Error::InvalidEncoding)); } } /// Standard Base64 *without* padding mod unpadded { use crate::common::*; use base64ct::Base64Unpadded; const TEST_VECTORS: &[TestVector] = &[ TestVector { raw: b"", b64: "" }, TestVector { raw: b"\0", b64: "AA", }, TestVector { raw: b"***", b64: "Kioq", }, TestVector { raw: b"\x01\x02\x03\x04", b64: "AQIDBA", }, TestVector { raw: b"\xAD\xAD\xAD\xAD\xAD", b64: "ra2tra0", }, TestVector { raw: b"\xFF\xEF\xFE\xFF\xEF\xFE", b64: "/+/+/+/+", }, TestVector { raw: b"\xFF\xFF\xFF\xFF\xFF", b64: "//////8", }, TestVector { raw: b"\x40\xC1\x3F\xBD\x05\x4C\x72\x2A\xA3\xC2\xF2\x11\x73\xC0\x69\xEA\ \x49\x7D\x35\x29\x6B\xCC\x24\x65\xF6\xF9\xD0\x41\x08\x7B\xD7\xA9", b64: "QME/vQVMciqjwvIRc8Bp6kl9NSlrzCRl9vnQQQh716k", }, TestVector { raw: b"\x00\x10\x83\x10Q\x87 \x92\x8B0\xD3\x8FA\x14\x93QU\x97a\x96\x9Bq\ \xD7\x9F\x82\x18\xA3\x92Y\xA7\xA2\x9A\xAB\xB2\xDB\xAF\xC3\x1C\xB3\ \xFB\xF0\x00", b64: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/AA", }, ]; impl_tests!(Base64Unpadded); #[test] fn reject_trailing_whitespace() { let input = "EA2zjEJAQWeXkj6FQw/duYZxBGZfn0FZxjbEEEVvpuY\n"; let mut buf = [0u8; 1024]; assert_eq!( Base64Unpadded::decode(input, &mut buf), Err(Error::InvalidEncoding) ); } #[test] fn unpadded_reject_trailing_equals() { let input = "EA2zjEJAQWeXkj6FQw/duYZxBGZfn0FZxjbEEEVvpuY="; let mut buf = [0u8; 1024]; assert_eq!( Base64Unpadded::decode(input, &mut buf), Err(Error::InvalidEncoding) ); } #[test] fn reject_non_canonical_encoding() { let input = "Mi"; let mut buf = [0u8; 8]; assert_eq!( Base64Unpadded::decode(input, &mut buf), Err(Error::InvalidEncoding) ); } } base64ct-1.5.1/tests/url.rs000064400000000000000000000071620072674642500136130ustar 00000000000000//! URL-safe Base64 tests #[macro_use] mod common; /// URL-safe Base64 with `=` padding mod padded { use crate::common::*; use base64ct::Base64Url; const TEST_VECTORS: &[TestVector] = &[ TestVector { raw: b"", b64: "" }, TestVector { raw: b"\0", b64: "AA==", }, TestVector { raw: b"***", b64: "Kioq", }, TestVector { raw: b"\x01\x02\x03\x04", b64: "AQIDBA==", }, TestVector { raw: b"\xAD\xAD\xAD\xAD\xAD", b64: "ra2tra0=", }, TestVector { raw: b"\xFF\xEF\xFE\xFF\xEF\xFE", b64: "_-_-_-_-", }, TestVector { raw: b"\xFF\xFF\xFF\xFF\xFF", b64: "______8=", }, TestVector { raw: b"\x40\xC1\x3F\xBD\x05\x4C\x72\x2A\xA3\xC2\xF2\x11\x73\xC0\x69\xEA\ \x49\x7D\x35\x29\x6B\xCC\x24\x65\xF6\xF9\xD0\x41\x08\x7B\xD7\xA9", b64: "QME_vQVMciqjwvIRc8Bp6kl9NSlrzCRl9vnQQQh716k=", }, TestVector { raw: b"\x00\x10\x83\x10Q\x87 \x92\x8B0\xD3\x8FA\x14\x93QU\x97a\x96\x9Bq\ \xD7\x9F\x82\x18\xA3\x92Y\xA7\xA2\x9A\xAB\xB2\xDB\xAF\xC3\x1C\xB3\ \xFB\xF0\x00", b64: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_AA", }, ]; impl_tests!(Base64Url); #[test] fn reject_trailing_whitespace() { let input = "QME/vQVMciqjwvIRc8Bp6kl9NSlrzCRl9vnQQQh716k\n"; let mut buf = [0u8; 1024]; assert_eq!( Base64Url::decode(input, &mut buf), Err(Error::InvalidEncoding) ); } } /// URL-safe Base64 *without* padding mod unpadded { use crate::common::*; use base64ct::Base64UrlUnpadded; const TEST_VECTORS: &[TestVector] = &[ TestVector { raw: b"", b64: "" }, TestVector { raw: b"\0", b64: "AA", }, TestVector { raw: b"***", b64: "Kioq", }, TestVector { raw: b"\x01\x02\x03\x04", b64: "AQIDBA", }, TestVector { raw: b"\xAD\xAD\xAD\xAD\xAD", b64: "ra2tra0", }, TestVector { raw: b"\xFF\xEF\xFE\xFF\xEF\xFE", b64: "_-_-_-_-", }, TestVector { raw: b"\xFF\xFF\xFF\xFF\xFF", b64: "______8", }, TestVector { raw: b"\x40\xC1\x3F\xBD\x05\x4C\x72\x2A\xA3\xC2\xF2\x11\x73\xC0\x69\xEA\ \x49\x7D\x35\x29\x6B\xCC\x24\x65\xF6\xF9\xD0\x41\x08\x7B\xD7\xA9", b64: "QME_vQVMciqjwvIRc8Bp6kl9NSlrzCRl9vnQQQh716k", }, TestVector { raw: b"\x00\x10\x83\x10Q\x87 \x92\x8B0\xD3\x8FA\x14\x93QU\x97a\x96\x9Bq\ \xD7\x9F\x82\x18\xA3\x92Y\xA7\xA2\x9A\xAB\xB2\xDB\xAF\xC3\x1C\xB3\ \xFB\xF0\x00", b64: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_AA", }, ]; impl_tests!(Base64UrlUnpadded); #[test] fn reject_trailing_whitespace() { let input = "QME/vQVMciqjwvIRc8Bp6kl9NSlrzCRl9vnQQQh716k\n"; let mut buf = [0u8; 1024]; assert_eq!( Base64UrlUnpadded::decode(input, &mut buf), Err(Error::InvalidEncoding) ); } #[test] fn unpadded_reject_trailing_equals() { let input = "QME_vQVMciqjwvIRc8Bp6kl9NSlrzCRl9vnQQQh716k="; let mut buf = [0u8; 1024]; assert_eq!( Base64UrlUnpadded::decode(input, &mut buf), Err(Error::InvalidEncoding) ); } }