├── .github ├── dependabot.yml └── workflows │ ├── ci-version.yml │ └── ci.yml ├── tests ├── encode.rs ├── decode.rs ├── escape.rs └── unescape.rs ├── Cargo.toml ├── LICENSE ├── src ├── decode.rs ├── lib.rs ├── encode.rs ├── escape.rs └── unescape.rs ├── README.md ├── rustfmt.toml └── .gitignore /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" -------------------------------------------------------------------------------- /tests/encode.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn encode() { 3 | assert_eq!("aHR0cHM6Ly9tYWdpY2xlbi5vcmc", base64_url::encode("https://magiclen.org")); 4 | } 5 | 6 | #[test] 7 | fn encode_to_string() { 8 | let mut url = "https://magiclen.org/".to_string(); 9 | 10 | assert_eq!("YXJ0aWNsZXM", base64_url::encode_to_string("articles", &mut url)); 11 | 12 | assert_eq!("https://magiclen.org/YXJ0aWNsZXM", url); 13 | } 14 | -------------------------------------------------------------------------------- /tests/decode.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn decode() { 3 | assert_eq!( 4 | b"https://magiclen.org".to_vec(), 5 | base64_url::decode("aHR0cHM6Ly9tYWdpY2xlbi5vcmc").unwrap() 6 | ); 7 | } 8 | 9 | #[test] 10 | fn decode_to_vec() { 11 | let mut url = b"https://magiclen.org/".to_vec(); 12 | 13 | assert_eq!(b"articles", base64_url::decode_to_vec("YXJ0aWNsZXM", &mut url).unwrap()); 14 | 15 | assert_eq!(b"https://magiclen.org/articles", url.as_slice()); 16 | } 17 | -------------------------------------------------------------------------------- /tests/escape.rs: -------------------------------------------------------------------------------- 1 | use base64::Engine; 2 | use base64_url::base64; 3 | 4 | #[test] 5 | fn escape() { 6 | assert_eq!( 7 | "aHR0cHM6Ly9tYWdpY2xlbi5vcmc", 8 | base64_url::escape( 9 | base64::engine::general_purpose::STANDARD.encode("https://magiclen.org").as_str() 10 | ) 11 | ); 12 | } 13 | 14 | #[test] 15 | fn escape_in_place() { 16 | let mut base64_string = 17 | base64::engine::general_purpose::STANDARD.encode("https://magiclen.org"); 18 | 19 | base64_url::escape_in_place(&mut base64_string); 20 | 21 | assert_eq!("aHR0cHM6Ly9tYWdpY2xlbi5vcmc", base64_string); 22 | } 23 | -------------------------------------------------------------------------------- /tests/unescape.rs: -------------------------------------------------------------------------------- 1 | use base64::Engine; 2 | use base64_url::base64; 3 | 4 | #[test] 5 | fn unescape() { 6 | assert_eq!( 7 | b"https://magiclen.org", 8 | base64::engine::general_purpose::STANDARD 9 | .decode(base64_url::unescape("aHR0cHM6Ly9tYWdpY2xlbi5vcmc").as_bytes()) 10 | .unwrap() 11 | .as_slice() 12 | ); 13 | } 14 | 15 | #[test] 16 | fn unescape_in_place() { 17 | let mut base64_url_string = String::from("aHR0cHM6Ly9tYWdpY2xlbi5vcmc"); 18 | 19 | base64_url::unescape_in_place(&mut base64_url_string); 20 | 21 | assert_eq!( 22 | b"https://magiclen.org", 23 | base64::engine::general_purpose::STANDARD.decode(base64_url_string).unwrap().as_slice() 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "base64-url" 3 | version = "3.0.2" 4 | authors = ["Magic Len "] 5 | edition = "2021" 6 | rust-version = "1.57" 7 | repository = "https://github.com/magiclen/base64-url" 8 | homepage = "https://magiclen.org/base64-url" 9 | keywords = ["decode", "base64", "encode", "utf8", "url"] 10 | categories = ["no-std", "encoding"] 11 | description = "Base64 encode, decode, escape and unescape for URL applications." 12 | license = "MIT" 13 | include = ["src/**/*", "Cargo.toml", "README.md", "LICENSE"] 14 | 15 | [dependencies] 16 | base64 = { version = "0.22", default-features = false, features = ["alloc"] } 17 | 18 | [features] 19 | default = [] 20 | std = ["base64/std"] # Only for the `base64` crate itself. This crate doesn't use the `std` feature. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 magiclen.org (Ron Li) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/ci-version.yml: -------------------------------------------------------------------------------- 1 | name: CI-version 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | tests: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | os: 17 | - ubuntu-latest 18 | - macos-latest 19 | - windows-latest 20 | toolchain: 21 | - stable 22 | - nightly 23 | features: 24 | - 25 | - --features std 26 | name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) 27 | runs-on: ${{ matrix.os }} 28 | steps: 29 | - uses: actions/checkout@v6 30 | - uses: actions-rust-lang/setup-rust-toolchain@v1 31 | with: 32 | toolchain: ${{ matrix.toolchain }} 33 | - run: cargo test --release ${{ matrix.features }} 34 | - run: cargo doc --release ${{ matrix.features }} 35 | 36 | MSRV: 37 | strategy: 38 | fail-fast: false 39 | matrix: 40 | os: 41 | - ubuntu-latest 42 | - macos-latest 43 | - windows-latest 44 | toolchain: 45 | - 1.57 46 | features: 47 | - 48 | - --features std 49 | name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) 50 | runs-on: ${{ matrix.os }} 51 | steps: 52 | - uses: actions/checkout@v6 53 | - uses: actions-rust-lang/setup-rust-toolchain@v1 54 | with: 55 | toolchain: ${{ matrix.toolchain }} 56 | - run: cargo test --release --lib --bins ${{ matrix.features }} -------------------------------------------------------------------------------- /src/decode.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | 3 | use base64::Engine; 4 | 5 | /// Decode a Base64-URL string to data. 6 | #[inline] 7 | pub fn decode>(input: &T) -> Result, base64::DecodeError> { 8 | base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(input) 9 | } 10 | 11 | /// Decode a Base64-URL string to data and directly store into a mutable `Vec` reference by concatenating them and return the slice of the decoded data. 12 | #[inline] 13 | pub fn decode_to_vec<'a, T: ?Sized + AsRef<[u8]>>( 14 | input: &T, 15 | output: &'a mut Vec, 16 | ) -> Result<&'a [u8], base64::DecodeSliceError> { 17 | let bytes = input.as_ref(); 18 | 19 | let current_length = output.len(); 20 | 21 | let original_max_length = ((bytes.len() + 3) >> 2) * 3; 22 | 23 | output.reserve(original_max_length); 24 | 25 | #[allow(clippy::uninit_vec)] 26 | unsafe { 27 | output.set_len(current_length + original_max_length); 28 | } 29 | 30 | let original_length = decode_to_slice(bytes, &mut output[current_length..])?.len(); 31 | 32 | unsafe { 33 | output.set_len(current_length + original_length); 34 | } 35 | 36 | Ok(&output[current_length..]) 37 | } 38 | 39 | /// Decode a Base64-URL string to data into a slice and return the slice with a valid length. 40 | #[inline] 41 | pub fn decode_to_slice<'a, T: ?Sized + AsRef<[u8]>>( 42 | input: &T, 43 | output: &'a mut [u8], 44 | ) -> Result<&'a [u8], base64::DecodeSliceError> { 45 | let length = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode_slice(input, output)?; 46 | 47 | Ok(&output[..length]) 48 | } 49 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | # Base64 URL 3 | 4 | Base64 encode, decode, escape and unescape for URL applications. 5 | 6 | ## Examples 7 | 8 | Encode data to a Base64-URL string. 9 | 10 | ```rust 11 | assert_eq!("SGVsbG8sIHdvcmxkIQ", base64_url::encode("Hello, world!")); 12 | ``` 13 | 14 | Decode a Base64-URL string to data. 15 | 16 | ```rust 17 | assert_eq!(b"Hello, world!", base64_url::decode("SGVsbG8sIHdvcmxkIQ").unwrap().as_slice()); 18 | ``` 19 | 20 | Escape a Base64 string to a Base64-URL string. The conversion is not concerning with Base64 decoding. You need to make sure the input string is a correct Base64 string by yourself. 21 | 22 | ```rust 23 | assert_eq!("SGVsbG8sIHdvcmxkIQ", base64_url::escape("SGVsbG8sIHdvcmxkIQ==")); 24 | ``` 25 | 26 | Unescape a Base64-URL string to a Base64-URL string. The conversion is not concerning with Base64 decoding. You need to make sure the input string is a correct Base64-URL string by yourself. 27 | 28 | ```rust 29 | assert_eq!("SGVsbG8sIHdvcmxkIQ==", base64_url::unescape("SGVsbG8sIHdvcmxkIQ")); 30 | ``` 31 | 32 | Besides, you can also use other `encode_*`, `decode_*`, `escape_*`, `unescape_*` associated functions to deal with more specific cases. For example, 33 | 34 | ```rust 35 | let hash = &[1, 2, 3, 4, 5, 6, 7, 8, 9]; 36 | let mut url = String::from("https://example.com/?hash="); 37 | 38 | assert_eq!("AQIDBAUGBwgJ", base64_url::encode_to_string(hash, &mut url)); 39 | assert_eq!("https://example.com/?hash=AQIDBAUGBwgJ", url); 40 | ``` 41 | 42 | ## Std 43 | 44 | Enable the `std` features to compile this crate with std. 45 | 46 | ```toml 47 | [dependencies.base64-url] 48 | version = "*" 49 | features = ["std"] 50 | ``` 51 | */ 52 | 53 | #![cfg_attr(not(feature = "std"), no_std)] 54 | 55 | extern crate alloc; 56 | 57 | pub extern crate base64; 58 | 59 | mod decode; 60 | mod encode; 61 | mod escape; 62 | mod unescape; 63 | 64 | pub use decode::*; 65 | pub use encode::*; 66 | pub use escape::*; 67 | pub use unescape::*; 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Base64 URL 2 | ==================== 3 | 4 | [![CI](https://github.com/magiclen/base64-url/actions/workflows/ci.yml/badge.svg)](https://github.com/magiclen/base64-url/actions/workflows/ci.yml) 5 | 6 | Base64 encode, decode, escape and unescape for URL applications. 7 | 8 | ## Examples 9 | 10 | Encode data to a Base64-URL string. 11 | 12 | ```rust 13 | assert_eq!("SGVsbG8sIHdvcmxkIQ", base64_url::encode("Hello, world!")); 14 | ``` 15 | 16 | Decode a Base64-URL string to data. 17 | 18 | ```rust 19 | assert_eq!(b"Hello, world!", base64_url::decode("SGVsbG8sIHdvcmxkIQ").unwrap().as_slice()); 20 | ``` 21 | 22 | Escape a Base64 string to a Base64-URL string. The conversion is not concerning with Base64 decoding. You need to make sure the input string is a correct Base64 string by yourself. 23 | 24 | ```rust 25 | assert_eq!("SGVsbG8sIHdvcmxkIQ", base64_url::escape("SGVsbG8sIHdvcmxkIQ==")); 26 | ``` 27 | 28 | Unescape a Base64-URL string to a Base64-URL string. The conversion is not concerning with Base64 decoding. You need to make sure the input string is a correct Base64-URL string by yourself. 29 | 30 | ```rust 31 | assert_eq!("SGVsbG8sIHdvcmxkIQ==", base64_url::unescape("SGVsbG8sIHdvcmxkIQ")); 32 | ``` 33 | 34 | Besides, you can also use other `encode_*`, `decode_*`, `escape_*`, `unescape_*` associated functions to deal with more specific cases. For example, 35 | 36 | ```rust 37 | let hash = &[1, 2, 3, 4, 5, 6, 7, 8, 9]; 38 | let mut url = String::from("https://example.com/?hash="); 39 | 40 | assert_eq!("AQIDBAUGBwgJ", base64_url::encode_to_string(hash, &mut url)); 41 | assert_eq!("https://example.com/?hash=AQIDBAUGBwgJ", url); 42 | ``` 43 | 44 | ## Std 45 | 46 | Enable the `std` features to compile this crate with std. 47 | 48 | ```toml 49 | [dependencies.base64-url] 50 | version = "*" 51 | features = ["std"] 52 | ``` 53 | 54 | ## Crates.io 55 | 56 | https://crates.io/crates/base64-url 57 | 58 | ## Documentation 59 | 60 | https://docs.rs/base64-url 61 | 62 | ## License 63 | 64 | [MIT](LICENSE) -------------------------------------------------------------------------------- /src/encode.rs: -------------------------------------------------------------------------------- 1 | use alloc::{string::String, vec::Vec}; 2 | use core::str::from_utf8_unchecked; 3 | 4 | use base64::Engine; 5 | 6 | /// Encode data to a Base64-URL string. 7 | #[inline] 8 | pub fn encode>(input: &T) -> String { 9 | base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(input) 10 | } 11 | 12 | /// Encode data to a Base64-URL string and directly store to a mutable `String` reference by concatenating them and return the slice of the Base64-URL string. It is usually for generating a URL. 13 | #[inline] 14 | pub fn encode_to_string<'a, T: ?Sized + AsRef<[u8]>>(input: &T, output: &'a mut String) -> &'a str { 15 | let base64_url = encode_to_vec(input, unsafe { output.as_mut_vec() }); 16 | 17 | unsafe { from_utf8_unchecked(base64_url) } 18 | } 19 | 20 | /// Encode data to Base64-URL data and directly store to a mutable `Vec` reference by concatenating them and return the slice of the Base64-URL data. It is usually for generating a URL. 21 | #[inline] 22 | pub fn encode_to_vec<'a, T: ?Sized + AsRef<[u8]>>(input: &T, output: &'a mut Vec) -> &'a [u8] { 23 | let bytes = input.as_ref(); 24 | 25 | let current_length = output.len(); 26 | 27 | let base64_length = ((bytes.len() << 2) + 2) / 3; 28 | 29 | output.reserve(base64_length); 30 | 31 | #[allow(clippy::uninit_vec)] 32 | unsafe { 33 | output.set_len(current_length + base64_length); 34 | } 35 | 36 | let base64_length = encode_to_slice(bytes, &mut output[current_length..]).unwrap().len(); 37 | 38 | unsafe { 39 | output.set_len(current_length + base64_length); 40 | } 41 | 42 | &output[current_length..] 43 | } 44 | 45 | /// Encode data to a Base64-URL string to a slice and return the slice with a valid length. 46 | #[inline] 47 | pub fn encode_to_slice<'a, T: ?Sized + AsRef<[u8]>>( 48 | input: &T, 49 | output: &'a mut [u8], 50 | ) -> Result<&'a [u8], base64::EncodeSliceError> { 51 | let length = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode_slice(input, output)?; 52 | 53 | Ok(&output[..length]) 54 | } 55 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # array_width = 60 2 | # attr_fn_like_width = 70 3 | binop_separator = "Front" 4 | blank_lines_lower_bound = 0 5 | blank_lines_upper_bound = 1 6 | brace_style = "PreferSameLine" 7 | # chain_width = 60 8 | color = "Auto" 9 | # comment_width = 100 10 | condense_wildcard_suffixes = true 11 | control_brace_style = "AlwaysSameLine" 12 | empty_item_single_line = true 13 | enum_discrim_align_threshold = 80 14 | error_on_line_overflow = false 15 | error_on_unformatted = false 16 | # fn_call_width = 60 17 | fn_params_layout = "Tall" 18 | fn_single_line = false 19 | force_explicit_abi = true 20 | force_multiline_blocks = false 21 | format_code_in_doc_comments = true 22 | doc_comment_code_block_width = 80 23 | format_generated_files = true 24 | format_macro_matchers = true 25 | format_macro_bodies = true 26 | skip_macro_invocations = [] 27 | format_strings = true 28 | hard_tabs = false 29 | hex_literal_case = "Upper" 30 | imports_indent = "Block" 31 | imports_layout = "Mixed" 32 | indent_style = "Block" 33 | inline_attribute_width = 0 34 | match_arm_blocks = true 35 | match_arm_leading_pipes = "Never" 36 | match_block_trailing_comma = true 37 | max_width = 100 38 | merge_derives = true 39 | imports_granularity = "Crate" 40 | newline_style = "Unix" 41 | normalize_comments = false 42 | normalize_doc_attributes = true 43 | overflow_delimited_expr = true 44 | remove_nested_parens = true 45 | reorder_impl_items = true 46 | reorder_imports = true 47 | group_imports = "StdExternalCrate" 48 | reorder_modules = true 49 | short_array_element_width_threshold = 10 50 | # single_line_if_else_max_width = 50 51 | space_after_colon = true 52 | space_before_colon = false 53 | spaces_around_ranges = false 54 | struct_field_align_threshold = 80 55 | struct_lit_single_line = false 56 | # struct_lit_width = 18 57 | # struct_variant_width = 35 58 | tab_spaces = 4 59 | trailing_comma = "Vertical" 60 | trailing_semicolon = true 61 | type_punctuation_density = "Wide" 62 | use_field_init_shorthand = true 63 | use_small_heuristics = "Max" 64 | use_try_shorthand = true 65 | where_single_line = false 66 | wrap_comments = false -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [ push, pull_request ] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | rustfmt: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v6 13 | - uses: actions-rust-lang/setup-rust-toolchain@v1 14 | with: 15 | toolchain: nightly 16 | components: rustfmt 17 | - uses: actions-rust-lang/rustfmt@v1 18 | 19 | clippy: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v6 23 | - uses: actions-rust-lang/setup-rust-toolchain@v1 24 | with: 25 | components: clippy 26 | - run: cargo clippy --all-targets --all-features -- -D warnings 27 | 28 | tests: 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | os: 33 | - ubuntu-latest 34 | - macos-latest 35 | - windows-latest 36 | toolchain: 37 | - stable 38 | - nightly 39 | features: 40 | - 41 | - --features std 42 | name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) 43 | runs-on: ${{ matrix.os }} 44 | steps: 45 | - uses: actions/checkout@v6 46 | - uses: actions-rust-lang/setup-rust-toolchain@v1 47 | with: 48 | toolchain: ${{ matrix.toolchain }} 49 | - run: cargo test ${{ matrix.features }} 50 | - run: cargo doc ${{ matrix.features }} 51 | 52 | MSRV: 53 | strategy: 54 | fail-fast: false 55 | matrix: 56 | os: 57 | - ubuntu-latest 58 | - macos-latest 59 | - windows-latest 60 | toolchain: 61 | - 1.57 62 | features: 63 | - 64 | - --features std 65 | name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) 66 | runs-on: ${{ matrix.os }} 67 | steps: 68 | - uses: actions/checkout@v6 69 | - uses: actions-rust-lang/setup-rust-toolchain@v1 70 | with: 71 | toolchain: ${{ matrix.toolchain }} 72 | - run: cargo test --lib --bins ${{ matrix.features }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Intellij+all ### 2 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 3 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 4 | 5 | # User-specific stuff 6 | .idea/**/workspace.xml 7 | .idea/**/tasks.xml 8 | .idea/**/usage.statistics.xml 9 | .idea/**/dictionaries 10 | .idea/**/shelf 11 | 12 | # AWS User-specific 13 | .idea/**/aws.xml 14 | 15 | # Generated files 16 | .idea/**/contentModel.xml 17 | 18 | # Sensitive or high-churn files 19 | .idea/**/dataSources/ 20 | .idea/**/dataSources.ids 21 | .idea/**/dataSources.local.xml 22 | .idea/**/sqlDataSources.xml 23 | .idea/**/dynamic.xml 24 | .idea/**/uiDesigner.xml 25 | .idea/**/dbnavigator.xml 26 | 27 | # Gradle 28 | .idea/**/gradle.xml 29 | .idea/**/libraries 30 | 31 | # Gradle and Maven with auto-import 32 | # When using Gradle or Maven with auto-import, you should exclude module files, 33 | # since they will be recreated, and may cause churn. Uncomment if using 34 | # auto-import. 35 | # .idea/artifacts 36 | # .idea/compiler.xml 37 | # .idea/jarRepositories.xml 38 | # .idea/modules.xml 39 | # .idea/*.iml 40 | # .idea/modules 41 | # *.iml 42 | # *.ipr 43 | 44 | # CMake 45 | cmake-build-*/ 46 | 47 | # Mongo Explorer plugin 48 | .idea/**/mongoSettings.xml 49 | 50 | # File-based project format 51 | *.iws 52 | 53 | # IntelliJ 54 | out/ 55 | 56 | # mpeltonen/sbt-idea plugin 57 | .idea_modules/ 58 | 59 | # JIRA plugin 60 | atlassian-ide-plugin.xml 61 | 62 | # Cursive Clojure plugin 63 | .idea/replstate.xml 64 | 65 | # SonarLint plugin 66 | .idea/sonarlint/ 67 | 68 | # Crashlytics plugin (for Android Studio and IntelliJ) 69 | com_crashlytics_export_strings.xml 70 | crashlytics.properties 71 | crashlytics-build.properties 72 | fabric.properties 73 | 74 | # Editor-based Rest Client 75 | .idea/httpRequests 76 | 77 | # Android studio 3.1+ serialized cache file 78 | .idea/caches/build_file_checksums.ser 79 | 80 | ### Intellij+all Patch ### 81 | # Ignore everything but code style settings and run configurations 82 | # that are supposed to be shared within teams. 83 | 84 | .idea/* 85 | 86 | !.idea/codeStyles 87 | !.idea/runConfigurations 88 | 89 | ### Rust ### 90 | # Generated by Cargo 91 | # will have compiled files and executables 92 | debug/ 93 | target/ 94 | 95 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 96 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 97 | Cargo.lock 98 | 99 | # These are backup files generated by rustfmt 100 | **/*.rs.bk 101 | 102 | # MSVC Windows builds of rustc generate these, which store debugging information 103 | *.pdb 104 | 105 | ### Vim ### 106 | # Swap 107 | [._]*.s[a-v][a-z] 108 | !*.svg # comment out if you don't need vector files 109 | [._]*.sw[a-p] 110 | [._]s[a-rt-v][a-z] 111 | [._]ss[a-gi-z] 112 | [._]sw[a-p] 113 | 114 | # Session 115 | Session.vim 116 | Sessionx.vim 117 | 118 | # Temporary 119 | .netrwhist 120 | *~ 121 | # Auto-generated tag files 122 | tags 123 | # Persistent undo 124 | [._]*.un~ 125 | 126 | ### VisualStudioCode ### 127 | .vscode/* 128 | !.vscode/settings.json 129 | !.vscode/tasks.json 130 | !.vscode/launch.json 131 | !.vscode/extensions.json 132 | !.vscode/*.code-snippets 133 | 134 | # Local History for Visual Studio Code 135 | .history/ 136 | 137 | # Built Visual Studio Code Extensions 138 | *.vsix 139 | 140 | ### VisualStudioCode Patch ### 141 | # Ignore all local history of files 142 | .history 143 | .ionide -------------------------------------------------------------------------------- /src/escape.rs: -------------------------------------------------------------------------------- 1 | use alloc::{borrow::Cow, string::String, vec::Vec}; 2 | use core::str::from_utf8_unchecked; 3 | 4 | /// Escape a Base64 string to a Base64-URL string. The conversion is not concerning with Base64 decoding. You need to make sure the input string is a correct Base64 string by yourself. 5 | #[inline] 6 | pub fn escape>(base64: &S) -> Cow<'_, str> { 7 | let base64 = base64.as_ref(); 8 | 9 | match escape_u8_slice(base64) { 10 | Cow::Owned(base64_url) => { 11 | let base64_url = unsafe { String::from_utf8_unchecked(base64_url) }; 12 | 13 | Cow::from(base64_url) 14 | }, 15 | Cow::Borrowed(base64_url) => { 16 | let base64_url = unsafe { from_utf8_unchecked(base64_url) }; 17 | 18 | Cow::from(base64_url) 19 | }, 20 | } 21 | } 22 | 23 | /// Escape Base64 data to Base64-URL data. The conversion is not concerning with Base64 decoding. You need to make sure the input Base64 data is correct by yourself. 24 | #[inline] 25 | pub fn escape_u8_slice>(base64: &S) -> Cow<'_, [u8]> { 26 | let base64 = base64.as_ref(); 27 | 28 | let length = base64.len(); 29 | 30 | let mut p = 0; 31 | 32 | let first = loop { 33 | if p == length { 34 | return Cow::from(base64); 35 | } 36 | 37 | let e = base64[p]; 38 | 39 | match e { 40 | 43 => { 41 | break 45; 42 | }, 43 | 47 => { 44 | break 95; 45 | }, 46 | 61 => { 47 | return Cow::from(&base64[..p]); 48 | }, 49 | _ => (), 50 | } 51 | 52 | p += 1; 53 | }; 54 | 55 | let mut base64_url = Vec::with_capacity(base64.len()); 56 | 57 | base64_url.extend_from_slice(&base64[..p]); 58 | base64_url.push(first); 59 | 60 | p += 1; 61 | 62 | let mut start = p; 63 | 64 | loop { 65 | if p == length { 66 | break; 67 | } 68 | 69 | let e = base64[p]; 70 | 71 | match e { 72 | 43 => { 73 | base64_url.extend_from_slice(&base64[start..p]); 74 | start = p + 1; 75 | 76 | base64_url.push(45); 77 | }, 78 | 47 => { 79 | base64_url.extend_from_slice(&base64[start..p]); 80 | start = p + 1; 81 | 82 | base64_url.push(95); 83 | }, 84 | 61 => break, 85 | _ => (), 86 | } 87 | 88 | p += 1; 89 | } 90 | 91 | base64_url.extend_from_slice(&base64[start..p]); 92 | 93 | Cow::from(base64_url) 94 | } 95 | 96 | /// In-place escape a Base64 string to a Base64-URL string. The conversion is not concerning with Base64 decoding. You need to make sure the input string is a correct Base64 string by yourself. 97 | #[inline] 98 | pub fn escape_in_place(base64: &mut String) -> &str { 99 | let v = unsafe { base64.as_mut_vec() }; 100 | 101 | escape_vec_in_place(v); 102 | 103 | base64.as_str() 104 | } 105 | 106 | /// In-place escape Base64 data to Base64-URL data. The conversion is not concerning with Base64 decoding. You need to make sure the input Base64 data is correct by yourself. 107 | #[inline] 108 | pub fn escape_vec_in_place(base64: &mut Vec) -> &[u8] { 109 | let length = escape_u8_slice_in_place(base64.as_mut_slice()).len(); 110 | 111 | unsafe { 112 | base64.set_len(length); 113 | } 114 | 115 | base64.as_slice() 116 | } 117 | 118 | /// In-place escape Base64 data to Base64-URL data. The conversion is not concerning with Base64 decoding. You need to make sure the input Base64 data is correct by yourself. 119 | #[inline] 120 | pub fn escape_u8_slice_in_place>(base64: &mut S) -> &[u8] { 121 | let base64 = base64.as_mut(); 122 | 123 | let mut len = base64.len(); 124 | 125 | for (index, n) in base64.iter_mut().enumerate() { 126 | match *n { 127 | 43 => *n = 45, 128 | 47 => *n = 95, 129 | 61 => { 130 | len = index; 131 | break; 132 | }, 133 | _ => (), 134 | } 135 | } 136 | 137 | &base64[..len] 138 | } 139 | 140 | #[deprecated(since = "1.3.0", note = "Please use the `escape_in_place` function instead")] 141 | /// Escape a Base64 string to a Base64-URL string. It is unsafe because the conversion is not concerning with Base64 decoding. You need to make sure the input string is a correct Base64 string by yourself. 142 | #[inline] 143 | pub fn unsafe_escape>(base64: S) -> String { 144 | let mut base64 = base64.into(); 145 | 146 | escape_in_place(&mut base64); 147 | 148 | base64 149 | } 150 | 151 | #[deprecated(since = "1.3.0", note = "Please use the `escape_in_place` function instead")] 152 | /// In-place escape a Base64 string to a Base64-URL string. It is unsafe because the conversion is not concerning with Base64 decoding. You need to make sure the input string is a correct Base64 string by yourself. 153 | #[inline] 154 | pub fn unsafe_escape_string(base64: &mut String) { 155 | escape_in_place(base64); 156 | } 157 | 158 | #[deprecated(since = "1.3.0", note = "Please use the `escape_vec_in_place` function instead")] 159 | /// In-place escape Base64 data to Base64-URL data. It is unsafe because the conversion is not concerning with Base64 decoding. You need to make sure the input Base64 data is correct by yourself. 160 | #[inline] 161 | pub fn unsafe_escape_vec(base64: &mut Vec) { 162 | escape_vec_in_place(base64); 163 | } 164 | 165 | #[deprecated(since = "1.3.0", note = "Please use the `escape_u8_slice_in_place` function instead")] 166 | /// In-place escape Base64 data to Base64-URL data. It is unsafe because the conversion is not concerning with Base64 decoding. You need to make sure the input Base64 data is correct by yourself. 167 | #[inline] 168 | pub fn unsafe_escape_u8_slice>(base64: &mut S) -> &[u8] { 169 | escape_u8_slice_in_place(base64) 170 | } 171 | -------------------------------------------------------------------------------- /src/unescape.rs: -------------------------------------------------------------------------------- 1 | use alloc::{borrow::Cow, string::String, vec::Vec}; 2 | use core::{mem::swap, str::from_utf8_unchecked}; 3 | 4 | /// Unescape a Base64-URL string to a Base64 string. The conversion is not concerning with Base64 decoding. You need to make sure the input string is a correct Base64-URL string by yourself. 5 | #[inline] 6 | pub fn unescape>(base64_url: &S) -> Cow<'_, str> { 7 | let base64_url = base64_url.as_ref(); 8 | 9 | match unescape_u8_slice(base64_url) { 10 | Cow::Owned(base64) => { 11 | let base64 = unsafe { String::from_utf8_unchecked(base64) }; 12 | 13 | Cow::from(base64) 14 | }, 15 | Cow::Borrowed(base64) => { 16 | let base64 = unsafe { from_utf8_unchecked(base64) }; 17 | 18 | Cow::from(base64) 19 | }, 20 | } 21 | } 22 | 23 | /// Unescape Base64-URL data to Base64 data. The conversion is not concerning with Base64 decoding. You need to make sure the input Base64-URL data is correct by yourself. 24 | pub fn unescape_u8_slice>(base64_url: &S) -> Cow<'_, [u8]> { 25 | let base64_url = base64_url.as_ref(); 26 | let length = base64_url.len(); 27 | 28 | let padding = length & 0b11; 29 | 30 | if padding > 0 { 31 | let new_size = base64_url.len() + (4 - padding); 32 | 33 | let mut base64 = Vec::with_capacity(new_size); 34 | 35 | let mut p = 0; 36 | let mut start = 0; 37 | 38 | loop { 39 | if p == length { 40 | break; 41 | } 42 | 43 | let e = base64_url[p]; 44 | 45 | match e { 46 | 45 => { 47 | base64.extend_from_slice(&base64_url[start..p]); 48 | start = p + 1; 49 | 50 | base64.push(43); 51 | }, 52 | 95 => { 53 | base64.extend_from_slice(&base64_url[start..p]); 54 | start = p + 1; 55 | 56 | base64.push(47); 57 | }, 58 | _ => (), 59 | } 60 | 61 | p += 1; 62 | } 63 | 64 | base64.extend_from_slice(&base64_url[start..p]); 65 | 66 | base64.resize(new_size, 61); 67 | 68 | Cow::from(base64) 69 | } else { 70 | let mut p = 0; 71 | 72 | let first = loop { 73 | if p == length { 74 | return Cow::from(base64_url); 75 | } 76 | 77 | let e = base64_url[p]; 78 | 79 | match e { 80 | 45 => { 81 | break 43; 82 | }, 83 | 95 => { 84 | break 47; 85 | }, 86 | _ => (), 87 | } 88 | 89 | p += 1; 90 | }; 91 | 92 | let mut base64 = Vec::with_capacity(base64_url.len()); 93 | 94 | base64.extend_from_slice(&base64_url[..p]); 95 | base64.push(first); 96 | 97 | p += 1; 98 | 99 | let mut start = p; 100 | 101 | loop { 102 | if p == length { 103 | break; 104 | } 105 | 106 | let e = base64_url[p]; 107 | 108 | match e { 109 | 45 => { 110 | base64.extend_from_slice(&base64_url[start..p]); 111 | start = p + 1; 112 | 113 | base64.push(43); 114 | }, 115 | 95 => { 116 | base64.extend_from_slice(&base64_url[start..p]); 117 | start = p + 1; 118 | 119 | base64.push(47); 120 | }, 121 | _ => (), 122 | } 123 | 124 | p += 1; 125 | } 126 | 127 | base64.extend_from_slice(&base64_url[start..p]); 128 | 129 | Cow::from(base64) 130 | } 131 | } 132 | 133 | /// In-place unescape a Base64-URL string to a Base64 string. It is unsafe because the conversion is not concerning with Base64 decoding. You need to make sure the input string is a correct Base64-URL string by yourself. 134 | #[inline] 135 | pub fn unescape_in_place(base64_url: &mut String) -> &str { 136 | let v = unsafe { base64_url.as_mut_vec() }; 137 | 138 | unescape_vec_in_place(v); 139 | 140 | base64_url.as_str() 141 | } 142 | 143 | /// In-place unescape Base64-URL data to Base64 data. It is unsafe because the conversion is not concerning with Base64 decoding. You need to make sure the input Base64-URL data is correct by yourself. 144 | #[inline] 145 | pub fn unescape_vec_in_place(base64_url: &mut Vec) -> &[u8] { 146 | let base64 = unescape_u8_slice_try_in_place(base64_url.as_mut_slice()); 147 | 148 | if let Cow::Owned(mut base64) = base64 { 149 | swap(base64_url, &mut base64); 150 | } 151 | 152 | base64_url.as_slice() 153 | } 154 | 155 | /// Unescape Base64-URL data to Base64 data and try to do it in-place. It is unsafe because the conversion is not concerning with Base64 decoding. You need to make sure the input Base64-URL data is correct by yourself. 156 | #[inline] 157 | pub fn unescape_u8_slice_try_in_place>( 158 | base64_url: &mut S, 159 | ) -> Cow<'_, [u8]> { 160 | let base64_url = base64_url.as_mut(); 161 | 162 | for n in base64_url.iter_mut() { 163 | match *n { 164 | 45 => *n = 43, 165 | 95 => *n = 47, 166 | _ => (), 167 | } 168 | } 169 | 170 | let padding = base64_url.len() & 0b11; 171 | 172 | if padding > 0 { 173 | let mut base64_url_vec = base64_url.to_vec(); 174 | 175 | let new_size = base64_url.len() + (4 - padding); 176 | 177 | base64_url_vec.resize(new_size, 61); 178 | 179 | Cow::from(base64_url_vec) 180 | } else { 181 | Cow::from(base64_url as &[u8]) 182 | } 183 | } 184 | 185 | #[deprecated(since = "1.3.0", note = "Please use the `unescape_in_place` function instead")] 186 | /// Unescape a Base64-URL string to a Base64 string. It is unsafe because the conversion is not concerning with Base64 decoding. You need to make sure the input string is a correct Base64-URL string by yourself. 187 | #[inline] 188 | pub fn unsafe_unescape>(base64_url: S) -> String { 189 | let mut base64_url = base64_url.into(); 190 | 191 | unescape_in_place(&mut base64_url); 192 | 193 | base64_url 194 | } 195 | 196 | #[deprecated(since = "1.3.0", note = "Please use the `unescape_in_place` function instead")] 197 | /// In-place unescape a Base64-URL string to a Base64 string. It is unsafe because the conversion is not concerning with Base64 decoding. You need to make sure the input string is a correct Base64-URL string by yourself. 198 | #[inline] 199 | pub fn unsafe_unescape_string(base64_url: &mut String) { 200 | unescape_in_place(base64_url); 201 | } 202 | 203 | #[deprecated(since = "1.3.0", note = "Please use the `unescape_vec_in_place` function instead")] 204 | /// In-place unescape Base64-URL data to Base64 data. It is unsafe because the conversion is not concerning with Base64 decoding. You need to make sure the input Base64-URL data is correct by yourself. 205 | pub fn unsafe_unescape_vec(base64_url: &mut Vec) { 206 | unescape_vec_in_place(base64_url); 207 | } 208 | 209 | #[deprecated( 210 | since = "1.3.0", 211 | note = "Please use the `unescape_u8_slice_try_in_place` function instead" 212 | )] 213 | /// Unescape Base64-URL data to Base64 data. It is unsafe because the conversion is not concerning with Base64 decoding. You need to make sure the input Base64-URL data is correct by yourself. 214 | pub fn unsafe_unescape_u8_slice>(base64_url: &mut S) -> Cow<'_, [u8]> { 215 | unescape_u8_slice_try_in_place(base64_url) 216 | } 217 | --------------------------------------------------------------------------------