├── .Rbuildignore ├── .github ├── .gitignore └── workflows │ ├── R-CMD-check.yaml │ ├── cran-checks.yaml │ ├── msrv.yaml │ └── pkgdown.yaml ├── .gitignore ├── .vscode └── settings.json ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── alphabet.R ├── b64-package.R ├── config.R ├── encode.R ├── engine.R └── extendr-wrappers.R ├── README.Rmd ├── README.md ├── _pkgdown.yml ├── configure ├── configure.win ├── cran-comments.md ├── man ├── alphabet.Rd ├── b64-package.Rd ├── encode.Rd ├── engine.Rd ├── figures │ └── logo.svg ├── new_config.Rd └── utils.Rd ├── pkgdown └── favicon │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-180x180.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-76x76.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ └── favicon.ico ├── src ├── .gitignore ├── Makevars.in ├── Makevars.ucrt ├── Makevars.win.in ├── b64-win.def ├── entrypoint.c └── rust │ ├── Cargo.lock │ ├── Cargo.toml │ ├── src │ └── lib.rs │ ├── vendor-config.toml │ └── vendor.tar.xz ├── tests ├── testthat.R └── testthat │ ├── test-roundtrip-alphabet.R │ └── test-roundtrip.R └── tools ├── config.R └── msrv.R /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^b64\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^src/\.cargo$ 4 | ^README\.Rmd$ 5 | ^LICENSE\.md$ 6 | ^src/rust/vendor$ 7 | ^\.github$ 8 | ^cran-comments\.md$ 9 | ^CRAN-SUBMISSION$ 10 | ^_pkgdown\.yml$ 11 | ^docs$ 12 | ^pkgdown$ 13 | ^src/rust/target$ 14 | ^src/Makevars$ 15 | ^src/Makevars\.win$ 16 | README.Rmd 17 | README.md 18 | ^\.vscode$ 19 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: R-CMD-check 10 | 11 | jobs: 12 | R-CMD-check: 13 | runs-on: ${{ matrix.config.os }} 14 | 15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | config: 21 | - {os: macos-13, r: 'devel'} 22 | - {os: macos-latest, r: 'release'} 23 | - {os: windows-latest, r: 'release'} 24 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 25 | - {os: ubuntu-latest, r: 'release'} 26 | - {os: ubuntu-latest, r: 'oldrel-1'} 27 | 28 | env: 29 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 30 | R_KEEP_PKG_SOURCE: yes 31 | NOT_CRAN: false 32 | 33 | steps: 34 | - uses: actions/checkout@v3 35 | 36 | - uses: r-lib/actions/setup-pandoc@v2 37 | 38 | - uses: r-lib/actions/setup-r@v2 39 | with: 40 | r-version: ${{ matrix.config.r }} 41 | http-user-agent: ${{ matrix.config.http-user-agent }} 42 | use-public-rspm: true 43 | 44 | - uses: r-lib/actions/setup-r-dependencies@v2 45 | with: 46 | extra-packages: any::rcmdcheck 47 | needs: check 48 | 49 | - uses: r-lib/actions/check-r-package@v2 50 | with: 51 | upload-snapshots: true 52 | -------------------------------------------------------------------------------- /.github/workflows/cran-checks.yaml: -------------------------------------------------------------------------------- 1 | name: Check CRAN status 2 | 3 | on: 4 | schedule: 5 | # Runs daily at 4:00 PM UTC (9:00 AM PST) 6 | - cron: '0 16 * * *' 7 | # allows for manually running of the check 8 | workflow_dispatch: 9 | 10 | jobs: 11 | check_cran_status: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Get CRAN checks 16 | uses: ricochet-rs/cran-checks/check-pkg@main 17 | with: 18 | pkg: b64 19 | -------------------------------------------------------------------------------- /.github/workflows/msrv.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | push: 4 | branches: [main, master] 5 | pull_request: 6 | branches: [main, master] 7 | 8 | name: check MSRV 9 | 10 | jobs: 11 | check-msrv: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: taiki-e/install-action@v2 16 | with: 17 | tool: cargo-msrv 18 | - name: run cargo-msrv 19 | run: cargo msrv verify --path ./src/rust/ 20 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | release: 9 | types: [published] 10 | workflow_dispatch: 11 | 12 | name: pkgdown 13 | 14 | jobs: 15 | pkgdown: 16 | runs-on: ubuntu-latest 17 | # Only restrict concurrency for non-PR jobs 18 | concurrency: 19 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 20 | env: 21 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 22 | permissions: 23 | contents: write 24 | steps: 25 | - uses: actions/checkout@v3 26 | 27 | - uses: r-lib/actions/setup-pandoc@v2 28 | 29 | - uses: r-lib/actions/setup-r@v2 30 | with: 31 | use-public-rspm: true 32 | 33 | - uses: r-lib/actions/setup-r-dependencies@v2 34 | with: 35 | extra-packages: any::pkgdown, local::. 36 | needs: website 37 | 38 | - name: Build site 39 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 40 | shell: Rscript {0} 41 | 42 | - name: Deploy to GitHub pages 🚀 43 | if: github.event_name != 'pull_request' 44 | uses: JamesIves/github-pages-deploy-action@v4.4.1 45 | with: 46 | clean: false 47 | branch: gh-pages 48 | folder: docs 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | CRAN-SUBMISSION 2 | .Rproj.user 3 | src/rust/vendor 4 | src/vendor 5 | docs 6 | src/Makevars 7 | src/Makevars.win 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.linkedProjects": [ 3 | "${workspaceFolder}/src/rust/Cargo.toml" 4 | ], 5 | "files.associations": { 6 | "Makevars.in": "makefile", 7 | "Makevars.win": "makefile", 8 | "configure": "shellscript", 9 | "configure.win": "shellscript", 10 | "cleanup": "shellscript", 11 | "cleanup.win": "shellscript" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: b64 2 | Title: Fast and Vectorized Base 64 Engine 3 | Version: 0.1.6 4 | Authors@R: c( 5 | person("Josiah", "Parry", , "josiah.parry@gmail.com", role = c("aut", "cre"), 6 | comment = c(ORCID = "0000-0001-9910-865X")), 7 | person("Etienne", "Bacher", email = "etienne.bacher@protonmail.com", role = "ctb", 8 | comment = c(ORCID = "0000-0002-9271-5075"))) 9 | Description: Provides a fast, lightweight, and vectorized base 64 engine 10 | to encode and decode character and raw vectors as well as files stored 11 | on disk. Common base 64 alphabets are supported out of the box 12 | including the standard, URL-safe, bcrypt, crypt, 'BinHex', and 13 | IMAP-modified UTF-7 alphabets. Custom engines can be created to 14 | support unique base 64 encoding and decoding needs. 15 | License: MIT + file LICENSE 16 | Encoding: UTF-8 17 | Language: en 18 | Roxygen: list(markdown = TRUE) 19 | RoxygenNote: 7.3.2 20 | Config/rextendr/version: 0.4.0.9000 21 | SystemRequirements: Cargo (Rust's package manager), rustc 22 | Suggests: 23 | blob, 24 | testthat (>= 3.0.0) 25 | Config/testthat/edition: 3 26 | URL: https://extendr.github.io/b64/, https://github.com/extendr/b64 27 | BugReports: https://github.com/extendr/b64/issues 28 | Depends: 29 | R (>= 4.2) 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2024 2 | COPYRIGHT HOLDER: b64 authors 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2024 b64 authors 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 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(as.character,alphabet) 4 | S3method(print,alphabet) 5 | S3method(print,engine) 6 | S3method(print,engine_config) 7 | export(alphabet) 8 | export(b64_chunk) 9 | export(b64_wrap) 10 | export(decode) 11 | export(decode_as_string) 12 | export(decode_file) 13 | export(encode) 14 | export(encode_file) 15 | export(engine) 16 | export(new_alphabet) 17 | export(new_config) 18 | export(new_engine) 19 | useDynLib(b64, .registration = TRUE) 20 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # b64 0.1.6 2 | 3 | * Enables building in offline environments 4 | * Improves documentation 5 | * Adds new utility function `decode_as_string()` to decode base64 strings directly into a string. 6 | 7 | # b64 0.1.3 8 | 9 | * Addresses non API changes 10 | * All hard dependencies were removed (#2, @etiennebacher). 11 | 12 | # b64 0.1.0 13 | 14 | * Initial CRAN submission. 15 | -------------------------------------------------------------------------------- /R/alphabet.R: -------------------------------------------------------------------------------- 1 | #' Standard base64 alphabets 2 | #' 3 | #' Create an alphabet from a set of standard base64 alphabets, or use your own. 4 | #' 5 | #' @param which default `"standard"`. Which base64 alphabet to use. 6 | #' See details for other values. 7 | #' @param chars a character scalar contains 64 unique characters. 8 | #' 9 | #' @details 10 | #' 11 | #' - `"bcrypt"`: bcrypt alphabet 12 | #' - `"bin_hex"`: alphabet used in BinHex 4.0 files 13 | #' - `"crypt"`: crypt(3) alphabet (with . and / as the first two characters) 14 | #' - `"imap_mutf7"`: alphabet used in IMAP-modified UTF-7 (with + and ,) 15 | #' - `"standard"`: standard alphabet (with + and /) specified in RFC 4648 16 | #' - `"url_safe"`: URL-safe alphabet (with - and _) specified in RFC 4648 17 | #' 18 | #' See [base64 crate](https://docs.rs/base64/latest/base64/alphabet/index.html#constants) 19 | #' from where these definitions come. 20 | #' 21 | #' @export 22 | #' @examples 23 | #' alphabet("standard") 24 | #' alphabet("bcrypt") 25 | #' alphabet("bin_hex") 26 | #' alphabet("crypt") 27 | #' alphabet("imap_mutf7") 28 | #' alphabet("url_safe") 29 | #' 30 | #' new_alphabet("qwertyuiop[]asdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890") 31 | #' @returns an object of class `alphabet` 32 | alphabet <- function(which = "standard") { 33 | match.arg( 34 | which, 35 | choices = c("standard", "bcrypt", "bin_hex", "crypt", "imap_mutf7", "url_safe") 36 | ) 37 | structure(alphabet_(which), class = "alphabet") 38 | } 39 | 40 | #' @export 41 | #' @rdname alphabet 42 | new_alphabet <- function(chars) { 43 | n <- nchar(chars) 44 | if (nchar(chars) != 64) { 45 | stop( 46 | paste( 47 | "`chars` must contain 64 unique characters. Only", n, "characters were provided." 48 | ) 49 | ) 50 | } 51 | 52 | structure(new_alphabet_(chars), class = "alphabet") 53 | } 54 | 55 | 56 | #' @export 57 | print.alphabet <- function(x, ...) { 58 | cat("\n") 59 | cat(get_alphabet_(x)) 60 | invisible(x) 61 | } 62 | 63 | 64 | #' @export 65 | as.character.alphabet <- function(x, ...) { 66 | get_alphabet_(x) 67 | } 68 | -------------------------------------------------------------------------------- /R/b64-package.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | "_PACKAGE" 3 | 4 | ## usethis namespace: start 5 | ## usethis namespace: end 6 | NULL 7 | -------------------------------------------------------------------------------- /R/config.R: -------------------------------------------------------------------------------- 1 | #' Create a custom encoding engine 2 | #' 3 | #' @details 4 | #' 5 | #' See [base64 crate](https://docs.rs/base64/latest/base64/engine/general_purpose/struct.GeneralPurposeConfig.html#method.with_encode_padding) for more details. 6 | #' 7 | #' ## Decode Padding Modes 8 | #' 9 | #' There are three modes that can be used for `decode_padding_mode` argument. 10 | #' 11 | #' - `"canonical"`: padding must consist of 0, 1, or 2 `=` characters 12 | #' - `"none"`: there must be no padding characters present 13 | #' - `"indifferent"`: canonical padding is used, but omitted padding 14 | #' characters are also permitted 15 | #' 16 | #' @param encode_padding default `TRUE` add 1-2 trailing `=` to pad results 17 | #' @param decode_padding_trailing_bits default `FALSE`. "If invalid trailing bits are present and this is true, those bits will be silently ignored." (See details for reference). 18 | #' @param decode_padding_mode default `"canonical"`. Other values are `"indifferent"` and `"none"`. See details for more. 19 | #' @export 20 | #' @return an object of class `engine_config` 21 | #' @examples 22 | #' # create a new nonsensicle config 23 | #' new_config(FALSE, TRUE, "none") 24 | new_config <- function( 25 | encode_padding = TRUE, 26 | decode_padding_trailing_bits = FALSE, 27 | decode_padding_mode = c("canonical", "indifferent", "none") 28 | ) { 29 | 30 | padding_mode <- match.arg( 31 | decode_padding_mode, 32 | choices = c("canonical", "indifferent", "none") 33 | ) 34 | 35 | res <- new_config_( 36 | encode_padding, 37 | decode_padding_trailing_bits, 38 | padding_mode 39 | ) 40 | 41 | structure(res, class = "engine_config") 42 | } 43 | 44 | #' @export 45 | print.engine_config <- function(x, ...) { 46 | y <- print_config_(x) 47 | cat("\n") 48 | invisible(x) 49 | } 50 | 51 | 52 | -------------------------------------------------------------------------------- /R/encode.R: -------------------------------------------------------------------------------- 1 | #' Encode and decode using base64 2 | #' 3 | #' @details 4 | #' 5 | #' ## Encoding 6 | #' 7 | #' - `encode()` takes a character vector, list of raw vectors (or blob class), or a raw vector and encodes them into base64 strings. 8 | #' - `encode_file()` takes a path to a file and encodes it as a base64 string. 9 | #' 10 | #' ## Decoding 11 | #' 12 | #' - `decode()` will decode either a base64 encoded character scalar, a raw vector, or a list of raw vectors (see blob package). 13 | #' - `decode_file()` will decode a base64 encoded file into a raw vector. 14 | #' - `decode_as_string()` is designed to decode a base64 encoded string to a utf-8 string. By default, it will decode a chunked base64 encoded strings using `\n` as the separator. Use the `newline` argument to determine how to split the input string prior to decoding. 15 | #' 16 | #' 17 | #' @param what a character, raw, or blob vector 18 | #' @param eng a base64 engine. See [engine()] for details. 19 | #' @param path a path to a base64 encoded file. 20 | #' @param newline a character sequence to split in the input base64 encoded string on before decoding. 21 | #' 22 | #' @return 23 | #' Both `encode()` and `decode()` are vectorized. They will return a character 24 | #' and blob vector the same length as `what`, respectively. 25 | #' 26 | #' `decode_as_string()` returns a character scalar. 27 | #' @export 28 | #' @name encode 29 | #' @examples 30 | #' # encode hello world 31 | #' encoded <- encode("Hello world") 32 | #' encoded 33 | #' 34 | #' # decode to a blob 35 | #' decoded <- decode(encoded) 36 | #' decoded 37 | #' 38 | #' # convert back to a character 39 | #' rawToChar(decoded[[1]]) 40 | encode <- function(what, eng = engine()) { 41 | n <- length(what) 42 | if (inherits(what, "raw") || (n == 1 & inherits(what, "character"))) { 43 | encode_(what, eng) 44 | } else { 45 | encode_vectorized_(what, eng) 46 | } 47 | } 48 | 49 | #' @export 50 | #' @rdname encode 51 | decode <- function(what, eng = engine()) { 52 | n <- length(what) 53 | if (inherits(what, "raw") || (n == 1 & inherits(what, "character"))) { 54 | decode_(what, eng) 55 | } else { 56 | decode_vectorized_(what, eng) 57 | } 58 | } 59 | 60 | #' @export 61 | #' @rdname encode 62 | decode_as_string <- function(what, newline = "\n", eng = engine()) { 63 | if (!inherits(what, "character") || length(what) != 1) { 64 | stop("`what` must be a scalar character vector") 65 | } 66 | 67 | if (!inherits(newline, "character") || length(newline) != 1) { 68 | stop("`newline` must be a scalar character vector") 69 | } 70 | 71 | decode_as_string_(what, newline, eng) 72 | } 73 | 74 | 75 | #' @export 76 | #' @name encode 77 | encode_file <- function(path, eng = engine()) { 78 | if (!file.exists(path)) { 79 | stop(paste0("`", path, "` does not exist")) 80 | } 81 | 82 | encode_file_(path, eng) 83 | } 84 | 85 | 86 | #' @export 87 | #' @rdname encode 88 | decode_file <- function(path, eng = engine()) { 89 | if (!file.exists(path)) { 90 | stop(paste0("`", path, "` does not exist")) 91 | } 92 | 93 | decode_file_(path, eng) 94 | } 95 | -------------------------------------------------------------------------------- /R/engine.R: -------------------------------------------------------------------------------- 1 | #' Create an encoding engine 2 | #' 3 | #' @param which default `"standard"`. The base64 encoding engine to be used. 4 | #' See details for more. 5 | #' @param .alphabet an object of class `alphabet` as created with 6 | #' [`alphabet()`] or [`new_alphabet()`] 7 | #' @param .config an object of class `engine_config` as created with 8 | #' [new_config()] 9 | #' @details 10 | #' 11 | #' ## Engines 12 | #' 13 | #' By default, the "standard" base64 engine is used which is specified in 14 | #' [RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648#section-4). 15 | #' 16 | #' Additional pre-configured base64 engines are provided these are: 17 | #' 18 | #' - `"standard_no_pad"`: uses the standard engine without padding 19 | #' - `"url_safe"`: uses a url-safe alphabet with padding 20 | #' - `"url_safe_no_pad"`: uses a url-safe alphabet without padding 21 | #' 22 | #' See [base64 crate](https://docs.rs/base64/latest/base64/engine/general_purpose/index.html#constants) for more. 23 | #' 24 | #' @return an object of class `engine`. 25 | #' @export 26 | #' @examples 27 | #' engine() 28 | #' new_engine(alphabet("bcrypt"), new_config()) 29 | engine <- function(which = "standard") { 30 | provided <- c("standard", "standard_no_pad", "url_safe", "url_safe_no_pad") 31 | match.arg(which, choices = provided) 32 | structure(engine_(which), class = "engine") 33 | } 34 | 35 | #' @export 36 | #' @rdname engine 37 | new_engine <- function(.alphabet = alphabet(), .config = new_config()) { 38 | 39 | if (!inherits(.alphabet, "alphabet")) { 40 | stop( 41 | paste( 42 | "`.alphabet` is not an object of class 'alphabet'.\n" , 43 | "Use `alphabet()` for a standard base64 alphabet." 44 | ) 45 | ) 46 | } else if (!inherits(.config, "engine_config")) { 47 | stop( 48 | paste( 49 | "`.config` is not an object of class 'engine_config'.\n" , 50 | "Create one with `new_config()`." 51 | ) 52 | ) 53 | } 54 | 55 | res <- new_engine_(.alphabet, .config) 56 | structure(res, class = "engine") 57 | } 58 | 59 | #' @export 60 | print.engine <- function(x, ...) { 61 | cat("") 62 | invisible(x) 63 | } 64 | -------------------------------------------------------------------------------- /R/extendr-wrappers.R: -------------------------------------------------------------------------------- 1 | # Generated by extendr: Do not edit by hand 2 | 3 | # nolint start 4 | 5 | # 6 | # This file was created with the following call: 7 | # .Call("wrap__make_b64_wrappers", use_symbols = TRUE, package_name = "b64") 8 | 9 | #' @usage NULL 10 | #' @useDynLib b64, .registration = TRUE 11 | NULL 12 | 13 | encode_ <- function(what, engine) .Call(wrap__encode_, what, engine) 14 | 15 | encode_file_ <- function(path, engine) .Call(wrap__encode_file_, path, engine) 16 | 17 | encode_vectorized_ <- function(what, engine) .Call(wrap__encode_vectorized_, what, engine) 18 | 19 | decode_ <- function(input, engine) .Call(wrap__decode_, input, engine) 20 | 21 | decode_file_ <- function(path, engine) .Call(wrap__decode_file_, path, engine) 22 | 23 | decode_vectorized_ <- function(what, engine) .Call(wrap__decode_vectorized_, what, engine) 24 | 25 | decode_as_string_ <- function(what, split, engine) .Call(wrap__decode_as_string_, what, split, engine) 26 | 27 | alphabet_ <- function(which) .Call(wrap__alphabet_, which) 28 | 29 | new_alphabet_ <- function(chars) .Call(wrap__new_alphabet_, chars) 30 | 31 | get_alphabet_ <- function(alphabet) .Call(wrap__get_alphabet_, alphabet) 32 | 33 | new_engine_ <- function(alphabet, config) .Call(wrap__new_engine_, alphabet, config) 34 | 35 | engine_ <- function(which) .Call(wrap__engine_, which) 36 | 37 | print_engine_ <- function(engine) .Call(wrap__print_engine_, engine) 38 | 39 | new_config_ <- function(encode_padding, decode_padding_trailing_bits, decode_padding_mode) .Call(wrap__new_config_, encode_padding, decode_padding_trailing_bits, decode_padding_mode) 40 | 41 | print_config_ <- function(config) .Call(wrap__print_config_, config) 42 | 43 | #' Utility Functions 44 | #' 45 | #' Functions to perform common tasks when working with base64 encoded strings. 46 | #' 47 | #' @details 48 | #' 49 | #' `b64_chunk()` splits a character vector of base64 encoded strings into chunks of a 50 | #' specified width. 51 | #' 52 | #' `b64_wrap()` wraps a character vector of base64 encoded strings with a newline character. 53 | #' 54 | #' @returns 55 | #' 56 | #' - `b64_chunk()` returns a list of character vectors. 57 | #' - `b64_wrap()` returns a scalar character vector. 58 | #' 59 | #' @examples 60 | #' encoded <- encode("Hello, world!") 61 | #' chunked <- b64_chunk(encoded, 4) 62 | #' chunked 63 | #' 64 | #' b64_wrap(chunked, "\n") 65 | #' @param width a numeric scalar defining the width of the chunks. Must be divisible by 4. 66 | #' @param encoded a character vector of base64 encoded strings. 67 | #' @export 68 | #' @rdname utils 69 | b64_chunk <- function(encoded, width) .Call(wrap__b64_chunk, encoded, width) 70 | 71 | #' @param chunks a character vector of base64 encoded strings. 72 | #' @param newline a character scalar defining the newline character. 73 | #' @export 74 | #' @rdname utils 75 | b64_wrap <- function(chunks, newline) .Call(wrap__b64_wrap, chunks, newline) 76 | 77 | 78 | # nolint end 79 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | fig.path = "man/figures/README-", 12 | out.width = "100%" 13 | ) 14 | ``` 15 | 16 | # b64 17 | 18 | 19 | [![R-CMD-check](https://github.com/extendr/b64/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/extendr/b64/actions/workflows/R-CMD-check.yaml) 20 | 21 | 22 | b64 is a dependency free, fast, lightweight, and vectorized base64 encoder and decoder. 23 | 24 | ## Installation 25 | 26 | You can install b64 from CRAN with 27 | 28 | ```r 29 | install.packages("b64") 30 | ``` 31 | 32 | ## Example 33 | 34 | Encode to base64 using `encode()`. 35 | 36 | ```{r example} 37 | library(b64) 38 | 39 | hello <- encode("Hello, from extendr") 40 | hello 41 | ``` 42 | 43 | Decode using `decode()`. Note that the returned object will always have the `"blob"` class. To achieve 0 dependencies, the `blob` package is only listed as a suggested dependency but if you attach it, its print method will be used. 44 | 45 | ```{r} 46 | library(blob) 47 | decoded <- decode(hello) 48 | decoded 49 | ``` 50 | 51 | We can convert the decoded base64 to characters and see how it worked. 52 | 53 | ```{r} 54 | rawToChar(decoded[[1]]) 55 | ``` 56 | 57 | 58 | ### Vectorized 59 | 60 | Both `encode()` and `decode()` are vectorized. 61 | 62 | ```{r} 63 | lorem <- unlist(lorem::ipsum(5, 1, 5)) 64 | lorem 65 | 66 | encoded <- encode(lorem) 67 | encoded 68 | ``` 69 | 70 | We can decode all of these using `decode()` as well. 71 | 72 | ```{r} 73 | decode(encoded) 74 | ``` 75 | 76 | 77 | ## Encoding and decoding files 78 | 79 | `b64` shines when encoding and decoding files. `encode_file()` and `decode_file()` both work by reading a file as a stream making it far faster than the alternative. 80 | 81 | ```{r message = FALSE, warn = FALSE} 82 | tmp <- tempfile() 83 | fp <- "https://github.com/extendr/b64/blob/main/src/rust/vendor.tar.xz" 84 | 85 | download.file(fp, tmp) 86 | 87 | bench::mark( 88 | b64 = encode_file(tmp), 89 | base64enc = base64enc::base64encode(tmp) 90 | ) 91 | ``` 92 | 93 | While the encoding is very impressive, better yet is the decoding performance. 94 | 95 | ```{r} 96 | # create a temp file 97 | tmp2 <- tempfile() 98 | 99 | # encode it and write to tmep file 100 | encode_file(tmp) |> 101 | charToRaw() |> 102 | writeBin(tmp2) 103 | 104 | bench::mark( 105 | b64 = decode_file(tmp2), 106 | base64enc = base64enc::base64decode(file(tmp2)) 107 | ) 108 | ``` 109 | 110 | ## Alternative engines 111 | 112 | Out of the box, `b64` provides a number of pre-configured engines that can be used. The function `engine()` allows you to choose one of these different engines For example, `engine("url_safe")` provides a standard engine that uses a url-safe alphabet with padding. 113 | 114 | 115 | ```{r} 116 | url_engine <- engine("url_safe") 117 | url_safe_encoded <- encode("\xfa\xec U", url_engine) 118 | url_safe_encoded 119 | ``` 120 | 121 | If we try to decode this using the standard engine, we will encounter an error. 122 | 123 | ```{r error=TRUE} 124 | decode(url_safe_encoded) 125 | ``` 126 | 127 | We can use our new engine to decode it. 128 | 129 | ```{r} 130 | decode(url_safe_encoded, url_engine) 131 | ``` 132 | 133 | ### Custom Engines 134 | 135 | We can create custom engines with `new_engine()`. This allows us to provide our on alphabet and configuration. 136 | 137 | We can use one of the many predefined alphabets or create one our selves with `new_alphabet()`. We can also specify our engine config using `new_config()` which lets us choose whether or not to pad and how to handle decoding. 138 | 139 | ```{r} 140 | my_eng <- new_engine( 141 | alphabet("crypt"), 142 | new_config(TRUE, TRUE, "none") 143 | ) 144 | ``` 145 | 146 | This engine can be used to encode or decode text. 147 | 148 | ```{r} 149 | txt <- "lorem ipsum sit dolor amet" 150 | 151 | encode(txt, my_eng) 152 | ``` 153 | 154 | Compare this to the standard encoder: 155 | 156 | ```{r} 157 | encode(txt) 158 | ``` 159 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # b64 5 | 6 | 7 | 8 | [![R-CMD-check](https://github.com/extendr/b64/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/extendr/b64/actions/workflows/R-CMD-check.yaml) 9 | 10 | 11 | b64 is a dependency free, fast, lightweight, and vectorized base64 12 | encoder and decoder. 13 | 14 | ## Installation 15 | 16 | You can install b64 from CRAN with 17 | 18 | ``` r 19 | install.packages("b64") 20 | ``` 21 | 22 | ## Example 23 | 24 | Encode to base64 using `encode()`. 25 | 26 | ``` r 27 | library(b64) 28 | 29 | hello <- encode("Hello, from extendr") 30 | hello 31 | #> [1] "SGVsbG8sIGZyb20gZXh0ZW5kcg==" 32 | ``` 33 | 34 | Decode using `decode()`. Note that the returned object will always have 35 | the `"blob"` class. To achieve 0 dependencies, the `blob` package is 36 | only listed as a suggested dependency but if you attach it, its print 37 | method will be used. 38 | 39 | ``` r 40 | library(blob) 41 | decoded <- decode(hello) 42 | decoded 43 | #> 44 | #> [1] blob[19 B] 45 | ``` 46 | 47 | We can convert the decoded base64 to characters and see how it worked. 48 | 49 | ``` r 50 | rawToChar(decoded[[1]]) 51 | #> [1] "Hello, from extendr" 52 | ``` 53 | 54 | ### Vectorized 55 | 56 | Both `encode()` and `decode()` are vectorized. 57 | 58 | ``` r 59 | lorem <- unlist(lorem::ipsum(5, 1, 5)) 60 | lorem 61 | #> [1] "Consectetur in sapien interdum diam lobortis eros?" 62 | #> [2] "Lorem sed ligula fames?" 63 | #> [3] "Adipiscing suscipit magna sapien varius." 64 | #> [4] "Elit tellus taciti turpis hendrerit sagittis." 65 | #> [5] "Sit suspendisse ultrices augue class parturient ultricies venenatis." 66 | 67 | encoded <- encode(lorem) 68 | encoded 69 | #> [1] "Q29uc2VjdGV0dXIgaW4gc2FwaWVuIGludGVyZHVtIGRpYW0gbG9ib3J0aXMgZXJvcz8=" 70 | #> [2] "TG9yZW0gc2VkIGxpZ3VsYSBmYW1lcz8=" 71 | #> [3] "QWRpcGlzY2luZyBzdXNjaXBpdCBtYWduYSBzYXBpZW4gdmFyaXVzLg==" 72 | #> [4] "RWxpdCB0ZWxsdXMgdGFjaXRpIHR1cnBpcyBoZW5kcmVyaXQgc2FnaXR0aXMu" 73 | #> [5] "U2l0IHN1c3BlbmRpc3NlIHVsdHJpY2VzIGF1Z3VlIGNsYXNzIHBhcnR1cmllbnQgdWx0cmljaWVzIHZlbmVuYXRpcy4=" 74 | ``` 75 | 76 | We can decode all of these using `decode()` as well. 77 | 78 | ``` r 79 | decode(encoded) 80 | #> 81 | #> [1] blob[50 B] blob[23 B] blob[40 B] blob[45 B] blob[68 B] 82 | ``` 83 | 84 | ## Encoding and decoding files 85 | 86 | `b64` shines when encoding and decoding files. `encode_file()` and 87 | `decode_file()` both work by reading a file as a stream making it far 88 | faster than the alternative. 89 | 90 | ``` r 91 | tmp <- tempfile() 92 | fp <- "https://github.com/extendr/b64/blob/main/src/rust/vendor.tar.xz" 93 | 94 | download.file(fp, tmp) 95 | 96 | bench::mark( 97 | b64 = encode_file(tmp), 98 | base64enc = base64enc::base64encode(tmp) 99 | ) 100 | #> # A tibble: 2 × 6 101 | #> expression min median `itr/sec` mem_alloc `gc/sec` 102 | #> 103 | #> 1 b64 334µs 340µs 2810. 218KB 0 104 | #> 2 base64enc 905µs 938µs 1043. 729KB 4.22 105 | ``` 106 | 107 | While the encoding is very impressive, better yet is the decoding 108 | performance. 109 | 110 | ``` r 111 | # create a temp file 112 | tmp2 <- tempfile() 113 | 114 | # encode it and write to tmep file 115 | encode_file(tmp) |> 116 | charToRaw() |> 117 | writeBin(tmp2) 118 | 119 | bench::mark( 120 | b64 = decode_file(tmp2), 121 | base64enc = base64enc::base64decode(file(tmp2)) 122 | ) 123 | #> # A tibble: 2 × 6 124 | #> expression min median `itr/sec` mem_alloc `gc/sec` 125 | #> 126 | #> 1 b64 107.09µs 122.6µs 7414. 164KB 8.77 127 | #> 2 base64enc 1.58ms 1.6ms 602. 172KB 3.70 128 | ``` 129 | 130 | ## Alternative engines 131 | 132 | Out of the box, `b64` provides a number of pre-configured engines that 133 | can be used. The function `engine()` allows you to choose one of these 134 | different engines For example, `engine("url_safe")` provides a standard 135 | engine that uses a url-safe alphabet with padding. 136 | 137 | ``` r 138 | url_engine <- engine("url_safe") 139 | url_safe_encoded <- encode("\xfa\xec U", url_engine) 140 | url_safe_encoded 141 | #> [1] "-uwgVQ==" 142 | ``` 143 | 144 | If we try to decode this using the standard engine, we will encounter an 145 | error. 146 | 147 | ``` r 148 | decode(url_safe_encoded) 149 | #> Error in decode_(what, eng): Invalid byte 45, offset 0. 150 | ``` 151 | 152 | We can use our new engine to decode it. 153 | 154 | ``` r 155 | decode(url_safe_encoded, url_engine) 156 | #> 157 | #> [1] blob[4 B] 158 | ``` 159 | 160 | ### Custom Engines 161 | 162 | We can create custom engines with `new_engine()`. This allows us to 163 | provide our on alphabet and configuration. 164 | 165 | We can use one of the many predefined alphabets or create one our selves 166 | with `new_alphabet()`. We can also specify our engine config using 167 | `new_config()` which lets us choose whether or not to pad and how to 168 | handle decoding. 169 | 170 | ``` r 171 | my_eng <- new_engine( 172 | alphabet("crypt"), 173 | new_config(TRUE, TRUE, "none") 174 | ) 175 | ``` 176 | 177 | This engine can be used to encode or decode text. 178 | 179 | ``` r 180 | txt <- "lorem ipsum sit dolor amet" 181 | 182 | encode(txt, my_eng) 183 | #> [1] "P4xmNKoUOL/nRKoUQqZo64FjP4xm643hNLE=" 184 | ``` 185 | 186 | Compare this to the standard encoder: 187 | 188 | ``` r 189 | encode(txt) 190 | #> [1] "bG9yZW0gaXBzdW0gc2l0IGRvbG9yIGFtZXQ=" 191 | ``` 192 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: ~ 2 | template: 3 | bootstrap: 5 4 | 5 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | : "${R_HOME=`R RHOME`}" 3 | "${R_HOME}/bin/Rscript" tools/config.R 4 | -------------------------------------------------------------------------------- /configure.win: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | "${R_HOME}/bin${R_ARCH_BIN}/Rscript.exe" tools/config.R 3 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## R CMD check results 2 | 3 | This is a maitenance release to ensure that the package can compile in offline environments 4 | 5 | 0 errors | 0 warnings | 1 note 6 | 7 | * This is a new release. 8 | 9 | -------------------------------------------------------------------------------- /man/alphabet.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/alphabet.R 3 | \name{alphabet} 4 | \alias{alphabet} 5 | \alias{new_alphabet} 6 | \title{Standard base64 alphabets} 7 | \usage{ 8 | alphabet(which = "standard") 9 | 10 | new_alphabet(chars) 11 | } 12 | \arguments{ 13 | \item{which}{default \code{"standard"}. Which base64 alphabet to use. 14 | See details for other values.} 15 | 16 | \item{chars}{a character scalar contains 64 unique characters.} 17 | } 18 | \value{ 19 | an object of class \code{alphabet} 20 | } 21 | \description{ 22 | Create an alphabet from a set of standard base64 alphabets, or use your own. 23 | } 24 | \details{ 25 | \itemize{ 26 | \item \code{"bcrypt"}: bcrypt alphabet 27 | \item \code{"bin_hex"}: alphabet used in BinHex 4.0 files 28 | \item \code{"crypt"}: crypt(3) alphabet (with . and / as the first two characters) 29 | \item \code{"imap_mutf7"}: alphabet used in IMAP-modified UTF-7 (with + and ,) 30 | \item \code{"standard"}: standard alphabet (with + and /) specified in RFC 4648 31 | \item \code{"url_safe"}: URL-safe alphabet (with - and _) specified in RFC 4648 32 | } 33 | 34 | See \href{https://docs.rs/base64/latest/base64/alphabet/index.html#constants}{base64 crate} 35 | from where these definitions come. 36 | } 37 | \examples{ 38 | alphabet("standard") 39 | alphabet("bcrypt") 40 | alphabet("bin_hex") 41 | alphabet("crypt") 42 | alphabet("imap_mutf7") 43 | alphabet("url_safe") 44 | 45 | new_alphabet("qwertyuiop[]asdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890") 46 | } 47 | -------------------------------------------------------------------------------- /man/b64-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/b64-package.R 3 | \docType{package} 4 | \name{b64-package} 5 | \alias{b64} 6 | \alias{b64-package} 7 | \title{b64: Fast and Vectorized Base 64 Engine} 8 | \description{ 9 | Provides a fast, lightweight, and vectorized base 64 engine to encode and decode character and raw vectors as well as files stored on disk. Common base 64 alphabets are supported out of the box including the standard, URL-safe, bcrypt, crypt, 'BinHex', and IMAP-modified UTF-7 alphabets. Custom engines can be created to support unique base 64 encoding and decoding needs. 10 | } 11 | \seealso{ 12 | Useful links: 13 | \itemize{ 14 | \item \url{https://extendr.github.io/b64/} 15 | \item \url{https://github.com/extendr/b64} 16 | \item Report bugs at \url{https://github.com/extendr/b64/issues} 17 | } 18 | 19 | } 20 | \author{ 21 | \strong{Maintainer}: Josiah Parry \email{josiah.parry@gmail.com} (\href{https://orcid.org/0000-0001-9910-865X}{ORCID}) 22 | 23 | Other contributors: 24 | \itemize{ 25 | \item Etienne Bacher \email{etienne.bacher@protonmail.com} (\href{https://orcid.org/0000-0002-9271-5075}{ORCID}) [contributor] 26 | } 27 | 28 | } 29 | \keyword{internal} 30 | -------------------------------------------------------------------------------- /man/encode.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/encode.R 3 | \name{encode} 4 | \alias{encode} 5 | \alias{decode} 6 | \alias{decode_as_string} 7 | \alias{encode_file} 8 | \alias{decode_file} 9 | \title{Encode and decode using base64} 10 | \usage{ 11 | encode(what, eng = engine()) 12 | 13 | decode(what, eng = engine()) 14 | 15 | decode_as_string(what, newline = "\\n", eng = engine()) 16 | 17 | encode_file(path, eng = engine()) 18 | 19 | decode_file(path, eng = engine()) 20 | } 21 | \arguments{ 22 | \item{what}{a character, raw, or blob vector} 23 | 24 | \item{eng}{a base64 engine. See \code{\link[=engine]{engine()}} for details.} 25 | 26 | \item{newline}{a character sequence to split in the input base64 encoded string on before decoding.} 27 | 28 | \item{path}{a path to a base64 encoded file.} 29 | } 30 | \value{ 31 | Both \code{encode()} and \code{decode()} are vectorized. They will return a character 32 | and blob vector the same length as \code{what}, respectively. 33 | 34 | \code{decode_as_string()} returns a character scalar. 35 | } 36 | \description{ 37 | Encode and decode using base64 38 | } 39 | \details{ 40 | \subsection{Encoding}{ 41 | \itemize{ 42 | \item \code{encode()} takes a character vector, list of raw vectors (or blob class), or a raw vector and encodes them into base64 strings. 43 | \item \code{encode_file()} takes a path to a file and encodes it as a base64 string. 44 | } 45 | } 46 | 47 | \subsection{Decoding}{ 48 | \itemize{ 49 | \item \code{decode()} will decode either a base64 encoded character scalar, a raw vector, or a list of raw vectors (see blob package). 50 | \item \code{decode_file()} will decode a base64 encoded file into a raw vector. 51 | \item \code{decode_as_string()} is designed to decode a base64 encoded string to a utf-8 string. By default, it will decode a chunked base64 encoded strings using \verb{\\n} as the separator. Use the \code{newline} argument to determine how to split the input string prior to decoding. 52 | } 53 | } 54 | } 55 | \examples{ 56 | # encode hello world 57 | encoded <- encode("Hello world") 58 | encoded 59 | 60 | # decode to a blob 61 | decoded <- decode(encoded) 62 | decoded 63 | 64 | # convert back to a character 65 | rawToChar(decoded[[1]]) 66 | } 67 | -------------------------------------------------------------------------------- /man/engine.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/engine.R 3 | \name{engine} 4 | \alias{engine} 5 | \alias{new_engine} 6 | \title{Create an encoding engine} 7 | \usage{ 8 | engine(which = "standard") 9 | 10 | new_engine(.alphabet = alphabet(), .config = new_config()) 11 | } 12 | \arguments{ 13 | \item{which}{default \code{"standard"}. The base64 encoding engine to be used. 14 | See details for more.} 15 | 16 | \item{.alphabet}{an object of class \code{alphabet} as created with 17 | \code{\link[=alphabet]{alphabet()}} or \code{\link[=new_alphabet]{new_alphabet()}}} 18 | 19 | \item{.config}{an object of class \code{engine_config} as created with 20 | \code{\link[=new_config]{new_config()}}} 21 | } 22 | \value{ 23 | an object of class \code{engine}. 24 | } 25 | \description{ 26 | Create an encoding engine 27 | } 28 | \details{ 29 | \subsection{Engines}{ 30 | 31 | By default, the "standard" base64 engine is used which is specified in 32 | \href{https://datatracker.ietf.org/doc/html/rfc4648#section-4}{RFC 4648}. 33 | 34 | Additional pre-configured base64 engines are provided these are: 35 | \itemize{ 36 | \item \code{"standard_no_pad"}: uses the standard engine without padding 37 | \item \code{"url_safe"}: uses a url-safe alphabet with padding 38 | \item \code{"url_safe_no_pad"}: uses a url-safe alphabet without padding 39 | } 40 | 41 | See \href{https://docs.rs/base64/latest/base64/engine/general_purpose/index.html#constants}{base64 crate} for more. 42 | } 43 | } 44 | \examples{ 45 | engine() 46 | new_engine(alphabet("bcrypt"), new_config()) 47 | } 48 | -------------------------------------------------------------------------------- /man/new_config.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/config.R 3 | \name{new_config} 4 | \alias{new_config} 5 | \title{Create a custom encoding engine} 6 | \usage{ 7 | new_config( 8 | encode_padding = TRUE, 9 | decode_padding_trailing_bits = FALSE, 10 | decode_padding_mode = c("canonical", "indifferent", "none") 11 | ) 12 | } 13 | \arguments{ 14 | \item{encode_padding}{default \code{TRUE} add 1-2 trailing \code{=} to pad results} 15 | 16 | \item{decode_padding_trailing_bits}{default \code{FALSE}. "If invalid trailing bits are present and this is true, those bits will be silently ignored." (See details for reference).} 17 | 18 | \item{decode_padding_mode}{default \code{"canonical"}. Other values are \code{"indifferent"} and \code{"none"}. See details for more.} 19 | } 20 | \value{ 21 | an object of class \code{engine_config} 22 | } 23 | \description{ 24 | Create a custom encoding engine 25 | } 26 | \details{ 27 | See \href{https://docs.rs/base64/latest/base64/engine/general_purpose/struct.GeneralPurposeConfig.html#method.with_encode_padding}{base64 crate} for more details. 28 | \subsection{Decode Padding Modes}{ 29 | 30 | There are three modes that can be used for \code{decode_padding_mode} argument. 31 | \itemize{ 32 | \item \code{"canonical"}: padding must consist of 0, 1, or 2 \code{=} characters 33 | \item \code{"none"}: there must be no padding characters present 34 | \item \code{"indifferent"}: canonical padding is used, but omitted padding 35 | characters are also permitted 36 | } 37 | } 38 | } 39 | \examples{ 40 | # create a new nonsensicle config 41 | new_config(FALSE, TRUE, "none") 42 | } 43 | -------------------------------------------------------------------------------- /man/utils.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/extendr-wrappers.R 3 | \name{b64_chunk} 4 | \alias{b64_chunk} 5 | \alias{b64_wrap} 6 | \title{Utility Functions} 7 | \usage{ 8 | b64_chunk(encoded, width) 9 | 10 | b64_wrap(chunks, newline) 11 | } 12 | \arguments{ 13 | \item{encoded}{a character vector of base64 encoded strings.} 14 | 15 | \item{width}{a numeric scalar defining the width of the chunks. Must be divisible by 4.} 16 | 17 | \item{chunks}{a character vector of base64 encoded strings.} 18 | 19 | \item{newline}{a character scalar defining the newline character.} 20 | } 21 | \value{ 22 | \itemize{ 23 | \item \code{b64_chunk()} returns a list of character vectors. 24 | \item \code{b64_wrap()} returns a scalar character vector. 25 | } 26 | } 27 | \description{ 28 | Functions to perform common tasks when working with base64 encoded strings. 29 | } 30 | \details{ 31 | \code{b64_chunk()} splits a character vector of base64 encoded strings into chunks of a 32 | specified width. 33 | 34 | \code{b64_wrap()} wraps a character vector of base64 encoded strings with a newline character. 35 | } 36 | \examples{ 37 | encoded <- encode("Hello, world!") 38 | chunked <- b64_chunk(encoded, 4) 39 | chunked 40 | 41 | b64_wrap(chunked, "\n") 42 | } 43 | -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/extendr/b64/b456c3cc81d11f2d3c956cd27aef0f76b42720fa/pkgdown/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/extendr/b64/b456c3cc81d11f2d3c956cd27aef0f76b42720fa/pkgdown/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/extendr/b64/b456c3cc81d11f2d3c956cd27aef0f76b42720fa/pkgdown/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/extendr/b64/b456c3cc81d11f2d3c956cd27aef0f76b42720fa/pkgdown/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/extendr/b64/b456c3cc81d11f2d3c956cd27aef0f76b42720fa/pkgdown/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/extendr/b64/b456c3cc81d11f2d3c956cd27aef0f76b42720fa/pkgdown/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/extendr/b64/b456c3cc81d11f2d3c956cd27aef0f76b42720fa/pkgdown/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/extendr/b64/b456c3cc81d11f2d3c956cd27aef0f76b42720fa/pkgdown/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/extendr/b64/b456c3cc81d11f2d3c956cd27aef0f76b42720fa/pkgdown/favicon/favicon.ico -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | *.dll 4 | target 5 | .cargo 6 | -------------------------------------------------------------------------------- /src/Makevars.in: -------------------------------------------------------------------------------- 1 | TARGET_DIR = ./rust/target 2 | LIBDIR = $(TARGET_DIR)/@LIBDIR@ 3 | STATLIB = $(LIBDIR)/libb64.a 4 | PKG_LIBS = -L$(LIBDIR) -lb64 5 | 6 | all: $(SHLIB) rust_clean 7 | 8 | .PHONY: $(STATLIB) 9 | 10 | $(SHLIB): $(STATLIB) 11 | 12 | CARGOTMP = $(CURDIR)/.cargo 13 | VENDOR_DIR = $(CURDIR)/vendor 14 | 15 | 16 | # RUSTFLAGS appends --print=native-static-libs to ensure that 17 | # the correct linkers are used. Use this for debugging if need. 18 | # 19 | # CRAN note: Cargo and Rustc versions are reported during 20 | # configure via tools/msrv.R. 21 | # 22 | # vendor.tar.xz, if present, is unzipped and used for offline compilation. 23 | $(STATLIB): 24 | 25 | if [ -f ./rust/vendor.tar.xz ]; then \ 26 | tar xf rust/vendor.tar.xz && \ 27 | mkdir -p $(CARGOTMP) && \ 28 | cp rust/vendor-config.toml $(CARGOTMP)/config.toml; \ 29 | fi 30 | 31 | export CARGO_HOME=$(CARGOTMP) && \ 32 | export PATH="$(PATH):$(HOME)/.cargo/bin" && \ 33 | RUSTFLAGS="$(RUSTFLAGS) --print=native-static-libs" cargo build @CRAN_FLAGS@ --lib @PROFILE@ --manifest-path=./rust/Cargo.toml --target-dir $(TARGET_DIR) @TARGET@ 34 | 35 | # Always clean up CARGOTMP 36 | rm -Rf $(CARGOTMP); 37 | 38 | rust_clean: $(SHLIB) 39 | rm -Rf $(CARGOTMP) $(VENDOR_DIR) @CLEAN_TARGET@ 40 | 41 | clean: 42 | rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) $(TARGET_DIR) 43 | -------------------------------------------------------------------------------- /src/Makevars.ucrt: -------------------------------------------------------------------------------- 1 | # Rtools42 doesn't have the linker in the location that cargo expects, so we 2 | # need to overwrite it via configuration. 3 | CARGO_LINKER = x86_64-w64-mingw32.static.posix-gcc.exe 4 | 5 | include Makevars.win 6 | -------------------------------------------------------------------------------- /src/Makevars.win.in: -------------------------------------------------------------------------------- 1 | TARGET = $(subst 64,x86_64,$(subst 32,i686,$(WIN)))-pc-windows-gnu 2 | 3 | TARGET_DIR = ./rust/target 4 | LIBDIR = $(TARGET_DIR)/$(TARGET)/@LIBDIR@ 5 | STATLIB = $(LIBDIR)/libb64.a 6 | PKG_LIBS = -L$(LIBDIR) -lb64 -lws2_32 -ladvapi32 -luserenv -lbcrypt -lntdll 7 | 8 | all: $(SHLIB) rust_clean 9 | 10 | .PHONY: $(STATLIB) 11 | 12 | $(SHLIB): $(STATLIB) 13 | 14 | CARGOTMP = $(CURDIR)/.cargo 15 | VENDOR_DIR = vendor 16 | 17 | $(STATLIB): 18 | mkdir -p $(TARGET_DIR)/libgcc_mock 19 | touch $(TARGET_DIR)/libgcc_mock/libgcc_eh.a 20 | 21 | if [ -f ./rust/vendor.tar.xz ]; then \ 22 | tar xf rust/vendor.tar.xz && \ 23 | mkdir -p $(CARGOTMP) && \ 24 | cp rust/vendor-config.toml $(CARGOTMP)/config.toml; \ 25 | fi 26 | 27 | # Build the project using Cargo with additional flags 28 | export CARGO_HOME=$(CARGOTMP) && \ 29 | export LIBRARY_PATH="$(LIBRARY_PATH);$(CURDIR)/$(TARGET_DIR)/libgcc_mock" && \ 30 | RUSTFLAGS="$(RUSTFLAGS) --print=native-static-libs" cargo build @CRAN_FLAGS@ --target=$(TARGET) --lib @PROFILE@ --manifest-path=rust/Cargo.toml --target-dir=$(TARGET_DIR) 31 | 32 | # Always clean up CARGOTMP 33 | rm -Rf $(CARGOTMP); 34 | 35 | rust_clean: $(SHLIB) 36 | rm -Rf $(CARGOTMP) $(VENDOR_DIR) @CLEAN_TARGET@ 37 | 38 | clean: 39 | rm -Rf $(SHLIB) $(STATLIB) $(OBJECTS) $(TARGET_DIR) 40 | -------------------------------------------------------------------------------- /src/b64-win.def: -------------------------------------------------------------------------------- 1 | EXPORTS 2 | R_init_b64 3 | -------------------------------------------------------------------------------- /src/entrypoint.c: -------------------------------------------------------------------------------- 1 | // We need to forward routine registration from C to Rust 2 | // to avoid the linker removing the static library. 3 | 4 | void R_init_b64_extendr(void *dll); 5 | 6 | void R_init_b64(void *dll) { 7 | R_init_b64_extendr(dll); 8 | } 9 | -------------------------------------------------------------------------------- /src/rust/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "b64" 7 | version = "0.1.0" 8 | dependencies = [ 9 | "base64", 10 | "extendr-api", 11 | "itertools", 12 | ] 13 | 14 | [[package]] 15 | name = "base64" 16 | version = "0.21.7" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 19 | 20 | [[package]] 21 | name = "build-print" 22 | version = "0.1.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "4a2128d00b7061b82b72844a351e80acd29e05afc60e9261e2ac90dca9ecc2ac" 25 | 26 | [[package]] 27 | name = "either" 28 | version = "1.15.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 31 | 32 | [[package]] 33 | name = "extendr-api" 34 | version = "0.8.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "cae12193370e4f00f4a54b64f40dabc753ad755f15229c367e4b5851ed206954" 37 | dependencies = [ 38 | "either", 39 | "extendr-ffi", 40 | "extendr-macros", 41 | "once_cell", 42 | "paste", 43 | ] 44 | 45 | [[package]] 46 | name = "extendr-ffi" 47 | version = "0.8.0" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "b48f96b7a4a2ff009ad9087f22a6de2312731a4096b520e3eb1c483df476ae95" 50 | dependencies = [ 51 | "build-print", 52 | ] 53 | 54 | [[package]] 55 | name = "extendr-macros" 56 | version = "0.8.0" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "fdbbac9afddafddb4dabd10aefa8082e3f057ec5bfa519c7b44af114e7ebf1a5" 59 | dependencies = [ 60 | "proc-macro2", 61 | "quote", 62 | "syn", 63 | ] 64 | 65 | [[package]] 66 | name = "itertools" 67 | version = "0.12.1" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 70 | dependencies = [ 71 | "either", 72 | ] 73 | 74 | [[package]] 75 | name = "once_cell" 76 | version = "1.21.3" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 79 | 80 | [[package]] 81 | name = "paste" 82 | version = "1.0.15" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 85 | 86 | [[package]] 87 | name = "proc-macro2" 88 | version = "1.0.95" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 91 | dependencies = [ 92 | "unicode-ident", 93 | ] 94 | 95 | [[package]] 96 | name = "quote" 97 | version = "1.0.40" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 100 | dependencies = [ 101 | "proc-macro2", 102 | ] 103 | 104 | [[package]] 105 | name = "syn" 106 | version = "2.0.101" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" 109 | dependencies = [ 110 | "proc-macro2", 111 | "quote", 112 | "unicode-ident", 113 | ] 114 | 115 | [[package]] 116 | name = "unicode-ident" 117 | version = "1.0.18" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 120 | -------------------------------------------------------------------------------- /src/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "b64" 3 | publish = false 4 | version = "0.1.0" 5 | edition = "2021" 6 | rust-version = "1.65.0" 7 | 8 | [lib] 9 | crate-type = ["staticlib"] 10 | name = "b64" 11 | 12 | [dependencies] 13 | base64 = "0.21.7" 14 | extendr-api = { version = "0.8.0", features = ["either"] } 15 | itertools = "0.12.0" 16 | 17 | 18 | [profile.release] 19 | lto = true 20 | codegen-units = 1 21 | -------------------------------------------------------------------------------- /src/rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | use base64::{ 2 | alphabet, 3 | engine::{general_purpose, DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig}, 4 | prelude::*, 5 | read::DecoderReader, 6 | write::EncoderStringWriter, 7 | }; 8 | use extendr_api::prelude::*; 9 | use itertools::{Either, Itertools}; 10 | use std::io::Read; 11 | 12 | #[extendr] 13 | fn encode_(what: Either, engine: Robj) -> String { 14 | let eng: ExternalPtr = engine.try_into().unwrap(); 15 | match what { 16 | Either::Left(s) => eng.encode(s), 17 | Either::Right(r) => eng.encode(r.as_slice()), 18 | } 19 | } 20 | 21 | #[extendr] 22 | fn encode_vectorized_(what: Either, engine: Robj) -> Strings { 23 | let eng: ExternalPtr = engine.try_into().unwrap(); 24 | match what { 25 | Either::Left(s) => s 26 | .into_iter() 27 | .map(|s| { 28 | if s.is_na() { 29 | Rstr::na() 30 | } else { 31 | let to_encode = s.as_bytes(); 32 | Rstr::from(eng.encode(to_encode)) 33 | } 34 | }) 35 | .collect::(), 36 | Either::Right(r) => r 37 | .into_iter() 38 | .map(|(_, b)| { 39 | if b.is_null() { 40 | Rstr::na() 41 | } else { 42 | let raw: Raw = b.try_into().unwrap(); 43 | Rstr::from(eng.encode(raw.as_slice())) 44 | } 45 | }) 46 | .collect::(), 47 | } 48 | } 49 | 50 | #[extendr] 51 | fn encode_file_(path: &str, engine: Robj) -> String { 52 | let eng: ExternalPtr = engine.try_into().unwrap(); 53 | let eng = eng.addr(); 54 | let file = std::fs::File::open(path).unwrap(); 55 | let mut reader = std::io::BufReader::new(file); 56 | let mut encoder = EncoderStringWriter::new(eng); 57 | std::io::copy(&mut reader, &mut encoder).unwrap(); 58 | encoder.into_inner() 59 | } 60 | 61 | /// Utility Functions 62 | /// 63 | /// Functions to perform common tasks when working with base64 encoded strings. 64 | /// 65 | /// @details 66 | /// 67 | /// `b64_chunk()` splits a character vector of base64 encoded strings into chunks of a 68 | /// specified width. 69 | /// 70 | /// `b64_wrap()` wraps a character vector of base64 encoded strings with a newline character. 71 | /// 72 | /// @returns 73 | /// 74 | /// - `b64_chunk()` returns a list of character vectors. 75 | /// - `b64_wrap()` returns a scalar character vector. 76 | /// 77 | /// @examples 78 | /// encoded <- encode("Hello, world!") 79 | /// chunked <- b64_chunk(encoded, 4) 80 | /// chunked 81 | /// 82 | /// b64_wrap(chunked, "\n") 83 | /// @param width a numeric scalar defining the width of the chunks. Must be divisible by 4. 84 | /// @param encoded a character vector of base64 encoded strings. 85 | /// @export 86 | /// @rdname utils 87 | #[extendr] 88 | fn b64_chunk(encoded: Strings, width: Either) -> List { 89 | let width = match width { 90 | Left(l) => l, 91 | Right(r) => r as i32, 92 | }; 93 | 94 | if width % 4 != 0 { 95 | extendr_api::throw_r_error("Chunk size must be a multiple of 4."); 96 | } 97 | encoded 98 | .into_iter() 99 | .map(|s| { 100 | if s.is_na() { 101 | Strings::new(0) 102 | } else { 103 | s.chars() 104 | .chunks(width as usize) 105 | .into_iter() 106 | .map(|chunk| chunk.collect::()) 107 | .collect::() 108 | } 109 | }) 110 | .collect::() 111 | } 112 | 113 | /// @param chunks a character vector of base64 encoded strings. 114 | /// @param newline a character scalar defining the newline character. 115 | /// @export 116 | /// @rdname utils 117 | #[extendr] 118 | fn b64_wrap(chunks: Either, newline: &str) -> Strings { 119 | match chunks { 120 | Left(l) => l 121 | .into_iter() 122 | .map(|(_, s)| { 123 | if s.is_na() { 124 | Rstr::na() 125 | } else { 126 | let s = Strings::try_from(s).unwrap(); 127 | Rstr::from(b64_wrap_(s, newline)) 128 | } 129 | }) 130 | .collect::(), 131 | Right(r) => b64_wrap_(r, newline).into(), 132 | } 133 | } 134 | 135 | fn b64_wrap_(chunks: Strings, newline: &str) -> String { 136 | chunks.into_iter().join(newline) 137 | } 138 | 139 | #[extendr] 140 | fn decode_(input: Either, engine: Robj) -> List { 141 | let eng: ExternalPtr = engine.try_into().unwrap(); 142 | let res = match input { 143 | Either::Left(s) => { 144 | let res = eng.decode(s); 145 | match res { 146 | Ok(d) => d, 147 | Err(e) => throw_r_error(e.to_string().as_str()), 148 | } 149 | } 150 | Either::Right(r) => match eng.decode(r.as_slice()) { 151 | Ok(d) => d, 152 | Err(e) => throw_r_error(e.to_string().as_str()), 153 | }, 154 | }; 155 | 156 | list!(Raw::from_bytes(&res)) 157 | .set_class(&["blob", "vctrs_list_of", "vctrs_vctr", "list"]) 158 | .unwrap() 159 | .clone() 160 | } 161 | 162 | #[extendr] 163 | fn decode_vectorized_(what: Either, engine: Robj) -> List { 164 | let eng: ExternalPtr = engine.try_into().unwrap(); 165 | match what { 166 | Either::Left(s) => s 167 | .into_iter() 168 | .map(|s| { 169 | if s.is_na() { 170 | ().into_robj() 171 | } else { 172 | let to_encode = s.as_str(); 173 | let decoded = eng.decode(to_encode); 174 | match decoded { 175 | Ok(d) => { 176 | let r = Raw::from_bytes(&d); 177 | r.into_robj() 178 | } 179 | Err(_) => ().into_robj(), 180 | } 181 | } 182 | }) 183 | .collect::() 184 | .set_class(&["blob", "vctrs_list_of", "vctrs_vctr", "list"]) 185 | .unwrap() 186 | .clone(), 187 | Either::Right(r) => r 188 | .into_iter() 189 | .map(|(_, b)| { 190 | let raw = Raw::try_from(b); 191 | match raw { 192 | Ok(r) => { 193 | let decoded = eng.decode(r.as_slice()); 194 | match decoded { 195 | Ok(d) => Raw::from_bytes(&d).into_robj(), 196 | Err(_) => ().into_robj(), 197 | } 198 | } 199 | Err(_) => ().into_robj(), 200 | } 201 | }) 202 | .collect::() 203 | .set_class(&["blob", "vctrs_list_of", "vctrs_vctr", "list"]) 204 | .unwrap() 205 | .clone(), 206 | } 207 | } 208 | 209 | #[extendr] 210 | fn decode_as_string_(what: String, split: Option, engine: Robj) -> Result { 211 | let eng: ExternalPtr = engine.try_into().unwrap(); 212 | match split { 213 | Some(sp) => { 214 | let res = what 215 | .split(&sp) 216 | .map(|split| { 217 | let bytes = eng 218 | .decode(split) 219 | .map_err(|_| throw_r_error("Failed to decode split")) 220 | .unwrap(); 221 | 222 | String::from_utf8(bytes) 223 | .map_err(|_| throw_r_error("Failed to parse decoded bytes as a string")) 224 | .unwrap() 225 | }) 226 | .collect::(); 227 | Ok(res) 228 | } 229 | None => { 230 | let res = eng 231 | .decode(what) 232 | .map_err(|_| throw_r_error("Failed to decode input string")) 233 | .unwrap(); 234 | let res = String::from_utf8(res) 235 | .map_err(|_| throw_r_error("Failed to parse decoded bytes to a string")) 236 | .unwrap(); 237 | Ok(res) 238 | } 239 | } 240 | } 241 | 242 | #[extendr] 243 | fn decode_file_(path: &str, engine: Robj) -> Vec { 244 | let eng: ExternalPtr = engine.try_into().unwrap(); 245 | let eng = eng.addr(); 246 | let file = std::fs::File::open(path).unwrap(); 247 | let mut reader = std::io::BufReader::new(file); 248 | let mut decoder = DecoderReader::new(&mut reader, eng); 249 | let mut result = Vec::new(); 250 | decoder.read_to_end(&mut result).unwrap(); 251 | result 252 | } 253 | 254 | // use a built-in alphabet 255 | #[extendr] 256 | fn alphabet_(which: &str) -> ExternalPtr { 257 | match which { 258 | "bcrypt" => ExternalPtr::new(alphabet::BCRYPT), 259 | "bin_hex" => ExternalPtr::new(alphabet::BIN_HEX), 260 | "crypt" => ExternalPtr::new(alphabet::CRYPT), 261 | "imap_mutf7" => ExternalPtr::new(alphabet::IMAP_MUTF7), 262 | "standard" => ExternalPtr::new(alphabet::STANDARD), 263 | "url_safe" => ExternalPtr::new(alphabet::URL_SAFE), 264 | _ => extendr_api::throw_r_error(format!("Unknown alphabet: {}", which)), 265 | } 266 | } 267 | 268 | // Create new alphabet 269 | #[extendr] 270 | fn new_alphabet_(chars: &str) -> ExternalPtr { 271 | let res = alphabet::Alphabet::new(chars); 272 | 273 | match res { 274 | Ok(r) => ExternalPtr::new(r), 275 | Err(e) => extendr_api::throw_r_error(format!("Error creating alphabet: {}", e)), 276 | } 277 | } 278 | 279 | // get alphabet as a string for printing 280 | #[extendr] 281 | fn get_alphabet_(alphabet: Robj) -> String { 282 | let alph: ExternalPtr = alphabet.try_into().unwrap(); 283 | alph.as_str().to_string() 284 | } 285 | 286 | // default configs 287 | // padding = true, 288 | // decode_allow_trailing_bits = false, 289 | // and decode_padding_mode = DecodePaddingMode::RequireCanonicalPadding 290 | #[extendr] 291 | fn new_config_( 292 | encode_padding: bool, 293 | decode_padding_trailing_bits: bool, 294 | decode_padding_mode: &str, 295 | ) -> ExternalPtr { 296 | let pad_mode = match decode_padding_mode { 297 | "indifferent" => DecodePaddingMode::Indifferent, 298 | "canonical" => DecodePaddingMode::RequireCanonical, 299 | "none" => DecodePaddingMode::RequireNone, 300 | _ => extendr_api::throw_r_error(format!("Unknown padding mode: {}", decode_padding_mode)), 301 | }; 302 | 303 | let config = GeneralPurposeConfig::new() 304 | .with_encode_padding(encode_padding) 305 | .with_decode_allow_trailing_bits(decode_padding_trailing_bits) 306 | .with_decode_padding_mode(pad_mode); 307 | 308 | ExternalPtr::new(config) 309 | } 310 | 311 | #[extendr] 312 | fn print_config_(config: Robj) -> String { 313 | let conf: ExternalPtr = config.try_into().unwrap(); 314 | format!("{:#?}", conf) 315 | } 316 | 317 | #[extendr] 318 | fn engine_(which: &str) -> ExternalPtr { 319 | match which { 320 | "standard" => ExternalPtr::new(general_purpose::STANDARD), 321 | "standard_no_pad" => ExternalPtr::new(general_purpose::STANDARD_NO_PAD), 322 | "url_safe" => ExternalPtr::new(general_purpose::URL_SAFE), 323 | "url_safe_no_pad" => ExternalPtr::new(general_purpose::URL_SAFE_NO_PAD), 324 | _ => extendr_api::throw_r_error(format!("Unknown engine: {}", which)), 325 | } 326 | } 327 | 328 | // need to figure out a nice print pattern here 329 | #[extendr] 330 | fn print_engine_(engine: Robj) -> String { 331 | let eng: ExternalPtr = engine.try_into().unwrap(); 332 | format!("{:#?}", eng) 333 | } 334 | 335 | #[extendr] 336 | fn new_engine_(alphabet: Robj, config: Robj) -> ExternalPtr { 337 | let alph: ExternalPtr = alphabet.try_into().unwrap(); 338 | let conf: ExternalPtr = config.try_into().unwrap(); 339 | let inner = conf.addr(); 340 | let engine = general_purpose::GeneralPurpose::new(&alph, *inner); 341 | ExternalPtr::new(engine) 342 | } 343 | 344 | // Macro to generate exports. 345 | // This ensures exported functions are registered with R. 346 | // See corresponding C code in `entrypoint.c`. 347 | extendr_module! { 348 | mod b64; 349 | // encoding 350 | fn encode_; 351 | fn encode_file_; 352 | fn encode_vectorized_; 353 | 354 | // decoding 355 | fn decode_; 356 | fn decode_file_; 357 | fn decode_vectorized_; 358 | fn decode_as_string_; 359 | 360 | // alphabets 361 | fn alphabet_; 362 | fn new_alphabet_; 363 | fn get_alphabet_; 364 | 365 | // engines 366 | fn new_engine_; 367 | fn engine_; 368 | fn print_engine_; 369 | 370 | // config 371 | fn new_config_; 372 | fn print_config_; 373 | 374 | // helpers 375 | fn b64_chunk; 376 | fn b64_wrap; 377 | } 378 | -------------------------------------------------------------------------------- /src/rust/vendor-config.toml: -------------------------------------------------------------------------------- 1 | [source.crates-io] 2 | replace-with = "vendored-sources" 3 | 4 | [source.vendored-sources] 5 | directory = "vendor" 6 | -------------------------------------------------------------------------------- /src/rust/vendor.tar.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/extendr/b64/b456c3cc81d11f2d3c956cd27aef0f76b42720fa/src/rust/vendor.tar.xz -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | # This file is part of the standard setup for testthat. 2 | # It is recommended that you do not modify it. 3 | # 4 | # Where should you do additional test configuration? 5 | # Learn more about the roles of various files in: 6 | # * https://r-pkgs.org/testing-design.html#sec-tests-files-overview 7 | # * https://testthat.r-lib.org/articles/special-files.html 8 | 9 | library(testthat) 10 | library(b64) 11 | 12 | test_check("b64") 13 | -------------------------------------------------------------------------------- /tests/testthat/test-roundtrip-alphabet.R: -------------------------------------------------------------------------------- 1 | test_that("alphabet: standard", { 2 | a <- alphabet("standard") 3 | a2 <- new_alphabet(as.character(a)) 4 | 5 | expect_equal( 6 | as.character(a), 7 | as.character(a2) 8 | ) 9 | }) 10 | 11 | test_that("alphabet: bcrypt", { 12 | a <- alphabet("bcrypt") 13 | a2 <- new_alphabet(as.character(a)) 14 | 15 | expect_equal( 16 | as.character(a), 17 | as.character(a2) 18 | ) 19 | }) 20 | 21 | test_that("alphabet: bin_hex", { 22 | a <- alphabet("bin_hex") 23 | a2 <- new_alphabet(as.character(a)) 24 | 25 | expect_equal( 26 | as.character(a), 27 | as.character(a2) 28 | ) 29 | }) 30 | 31 | test_that("alphabet: crypt", { 32 | a <- alphabet("crypt") 33 | a2 <- new_alphabet(as.character(a)) 34 | 35 | expect_equal( 36 | as.character(a), 37 | as.character(a2) 38 | ) 39 | }) 40 | 41 | test_that("alphabet: imap_mutf7", { 42 | a <- alphabet("imap_mutf7") 43 | a2 <- new_alphabet(as.character(a)) 44 | 45 | expect_equal( 46 | as.character(a), 47 | as.character(a2) 48 | ) 49 | }) 50 | 51 | test_that("alphabet: url_safe", { 52 | a <- alphabet("url_safe") 53 | a2 <- new_alphabet(as.character(a)) 54 | 55 | expect_equal( 56 | as.character(a), 57 | as.character(a2) 58 | ) 59 | }) 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /tests/testthat/test-roundtrip.R: -------------------------------------------------------------------------------- 1 | test_that("engine: standard", { 2 | txt <- "hello world, it is me!" 3 | expect_equal( 4 | txt, 5 | rawToChar(decode(encode(txt))[[1]]) 6 | ) 7 | }) 8 | 9 | test_that("engine: standard_no_pad", { 10 | eng <- engine("standard_no_pad") 11 | txt <- "hello world, it is me!" 12 | encoded <- encode(txt, eng) 13 | decoded <- decode(encoded, eng) 14 | expect_equal( 15 | txt, 16 | rawToChar(decoded[[1]]) 17 | ) 18 | }) 19 | 20 | test_that("engine: url_safe", { 21 | eng <- engine("url_safe") 22 | txt <- "\xfa\xec U" 23 | encoded <- encode(txt, eng) 24 | decoded <- decode(encoded, eng) 25 | expect_equal( 26 | txt, 27 | rawToChar(decoded[[1]]) 28 | ) 29 | }) 30 | 31 | 32 | test_that("engine: url_safe_no_pad", { 33 | eng <- engine("url_safe_no_pad") 34 | txt <- "\xfa\xec U" 35 | encoded <- encode(txt, eng) 36 | decoded <- decode(encoded, eng) 37 | expect_equal( 38 | txt, 39 | rawToChar(decoded[[1]]) 40 | ) 41 | }) 42 | -------------------------------------------------------------------------------- /tools/config.R: -------------------------------------------------------------------------------- 1 | # Note: Any variables prefixed with `.` are used for text 2 | # replacement in the Makevars.in and Makevars.win.in 3 | 4 | # check the packages MSRV first 5 | source("tools/msrv.R") 6 | 7 | # check DEBUG and NOT_CRAN environment variables 8 | env_debug <- Sys.getenv("DEBUG") 9 | env_not_cran <- Sys.getenv("NOT_CRAN") 10 | 11 | # check if the vendored zip file exists 12 | vendor_exists <- file.exists("src/rust/vendor.tar.xz") 13 | 14 | is_not_cran <- env_not_cran != "" 15 | is_debug <- env_debug != "" 16 | 17 | if (is_debug) { 18 | # if we have DEBUG then we set not cran to true 19 | # CRAN is always release build 20 | is_not_cran <- TRUE 21 | message("Creating DEBUG build.") 22 | } 23 | 24 | if (!is_not_cran) { 25 | message("Building for CRAN.") 26 | } 27 | 28 | # we set cran flags only if NOT_CRAN is empty and if 29 | # the vendored crates are present. 30 | .cran_flags <- ifelse( 31 | !is_not_cran && vendor_exists, 32 | "-j 2 --offline", 33 | "" 34 | ) 35 | 36 | # when DEBUG env var is present we use `--debug` build 37 | .profile <- ifelse(is_debug, "", "--release") 38 | .clean_targets <- ifelse(is_debug, "", "$(TARGET_DIR)") 39 | 40 | # We specify this target when building for webR 41 | webr_target <- "wasm32-unknown-emscripten" 42 | 43 | # here we check if the platform we are building for is webr 44 | is_wasm <- identical(R.version$platform, webr_target) 45 | 46 | # print to terminal to inform we are building for webr 47 | if (is_wasm) { 48 | message("Building for WebR") 49 | } 50 | 51 | # we check if we are making a debug build or not 52 | # if so, the LIBDIR environment variable becomes: 53 | # LIBDIR = $(TARGET_DIR)/{wasm32-unknown-emscripten}/debug 54 | # this will be used to fill out the LIBDIR env var for Makevars.in 55 | target_libpath <- if (is_wasm) "wasm32-unknown-emscripten" else NULL 56 | cfg <- if (is_debug) "debug" else "release" 57 | 58 | # used to replace @LIBDIR@ 59 | .libdir <- paste(c(target_libpath, cfg), collapse = "/") 60 | 61 | # use this to replace @TARGET@ 62 | # we specify the target _only_ on webR 63 | # there may be use cases later where this can be adapted or expanded 64 | .target <- ifelse(is_wasm, paste0("--target=", webr_target), "") 65 | 66 | # read in the Makevars.in file checking 67 | is_windows <- .Platform[["OS.type"]] == "windows" 68 | 69 | # if windows we replace in the Makevars.win.in 70 | mv_fp <- ifelse( 71 | is_windows, 72 | "src/Makevars.win.in", 73 | "src/Makevars.in" 74 | ) 75 | 76 | # set the output file 77 | mv_ofp <- ifelse( 78 | is_windows, 79 | "src/Makevars.win", 80 | "src/Makevars" 81 | ) 82 | 83 | # delete the existing Makevars{.win} 84 | if (file.exists(mv_ofp)) { 85 | message("Cleaning previous `", mv_ofp, "`.") 86 | invisible(file.remove(mv_ofp)) 87 | } 88 | 89 | # read as a single string 90 | mv_txt <- readLines(mv_fp) 91 | 92 | # replace placeholder values 93 | new_txt <- gsub("@CRAN_FLAGS@", .cran_flags, mv_txt) |> 94 | gsub("@PROFILE@", .profile, x = _) |> 95 | gsub("@CLEAN_TARGET@", .clean_targets, x = _) |> 96 | gsub("@LIBDIR@", .libdir, x = _) |> 97 | gsub("@TARGET@", .target, x = _) 98 | 99 | message("Writing `", mv_ofp, "`.") 100 | con <- file(mv_ofp, open = "wb") 101 | writeLines(new_txt, con, sep = "\n") 102 | close(con) 103 | 104 | message("`tools/config.R` has finished.") 105 | -------------------------------------------------------------------------------- /tools/msrv.R: -------------------------------------------------------------------------------- 1 | # read the DESCRIPTION file 2 | desc <- read.dcf("DESCRIPTION") 3 | 4 | if (!"SystemRequirements" %in% colnames(desc)) { 5 | fmt <- c( 6 | "`SystemRequirements` not found in `DESCRIPTION`.", 7 | "Please specify `SystemRequirements: Cargo (Rust's package manager), rustc`" 8 | ) 9 | stop(paste(fmt, collapse = "\n")) 10 | } 11 | 12 | # extract system requirements 13 | sysreqs <- desc[, "SystemRequirements"] 14 | 15 | # check that cargo and rustc is found 16 | if (!grepl("cargo", sysreqs, ignore.case = TRUE)) { 17 | stop("You must specify `Cargo (Rust's package manager)` in your `SystemRequirements`") 18 | } 19 | 20 | if (!grepl("rustc", sysreqs, ignore.case = TRUE)) { 21 | stop("You must specify `Cargo (Rust's package manager), rustc` in your `SystemRequirements`") 22 | } 23 | 24 | # split into parts 25 | parts <- strsplit(sysreqs, ", ")[[1]] 26 | 27 | # identify which is the rustc 28 | rustc_ver <- parts[grepl("rustc", parts)] 29 | 30 | # perform checks for the presence of rustc and cargo on the OS 31 | no_cargo_msg <- c( 32 | "----------------------- [CARGO NOT FOUND]--------------------------", 33 | "The 'cargo' command was not found on the PATH. Please install Cargo", 34 | "from: https://www.rust-lang.org/tools/install", 35 | "", 36 | "Alternatively, you may install Cargo from your OS package manager:", 37 | " - Debian/Ubuntu: apt-get install cargo", 38 | " - Fedora/CentOS: dnf install cargo", 39 | " - macOS: brew install rust", 40 | "-------------------------------------------------------------------" 41 | ) 42 | 43 | no_rustc_msg <- c( 44 | "----------------------- [RUST NOT FOUND]---------------------------", 45 | "The 'rustc' compiler was not found on the PATH. Please install", 46 | paste(rustc_ver, "or higher from:"), 47 | "https://www.rust-lang.org/tools/install", 48 | "", 49 | "Alternatively, you may install Rust from your OS package manager:", 50 | " - Debian/Ubuntu: apt-get install rustc", 51 | " - Fedora/CentOS: dnf install rustc", 52 | " - macOS: brew install rust", 53 | "-------------------------------------------------------------------" 54 | ) 55 | 56 | # Add {user}/.cargo/bin to path before checking 57 | new_path <- paste0( 58 | Sys.getenv("PATH"), 59 | ":", 60 | paste0(Sys.getenv("HOME"), "/.cargo/bin") 61 | ) 62 | 63 | # set the path with the new path 64 | Sys.setenv("PATH" = new_path) 65 | 66 | # check for rustc installation 67 | rustc_version <- tryCatch( 68 | system("rustc --version", intern = TRUE), 69 | error = function(e) { 70 | stop(paste(no_rustc_msg, collapse = "\n")) 71 | } 72 | ) 73 | 74 | # check for cargo installation 75 | cargo_version <- tryCatch( 76 | system("cargo --version", intern = TRUE), 77 | error = function(e) { 78 | stop(paste(no_cargo_msg, collapse = "\n")) 79 | } 80 | ) 81 | 82 | # helper function to extract versions 83 | extract_semver <- function(ver) { 84 | if (grepl("\\d+\\.\\d+(\\.\\d+)?", ver)) { 85 | sub(".*?(\\d+\\.\\d+(\\.\\d+)?).*", "\\1", ver) 86 | } else { 87 | NA 88 | } 89 | } 90 | 91 | # get the MSRV 92 | msrv <- extract_semver(rustc_ver) 93 | 94 | # extract current version 95 | current_rust_version <- extract_semver(rustc_version) 96 | 97 | # perform check 98 | if (!is.na(msrv)) { 99 | # -1 when current version is later 100 | # 0 when they are the same 101 | # 1 when MSRV is newer than current 102 | is_msrv <- utils::compareVersion(msrv, current_rust_version) 103 | if (is_msrv == 1) { 104 | fmt <- paste0( 105 | "\n------------------ [UNSUPPORTED RUST VERSION]------------------\n", 106 | "- Minimum supported Rust version is %s.\n", 107 | "- Installed Rust version is %s.\n", 108 | "---------------------------------------------------------------" 109 | ) 110 | stop(sprintf(fmt, msrv, current_rust_version)) 111 | } 112 | } 113 | 114 | # print the versions 115 | versions_fmt <- "Using %s\nUsing %s" 116 | message(sprintf(versions_fmt, cargo_version, rustc_version)) 117 | --------------------------------------------------------------------------------