├── .document ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ ├── sig.yml │ ├── test.yml │ └── push_gem.yml ├── Gemfile ├── bin ├── setup └── console ├── Rakefile ├── base64.gemspec ├── BSDL ├── test_sig └── test_base64.rb ├── README.md ├── COPYING ├── LEGAL ├── test └── base64 │ └── test_base64.rb ├── sig └── base64.rbs └── lib └── base64.rb /.document: -------------------------------------------------------------------------------- 1 | BSDL 2 | COPYING 3 | LEGAL 4 | lib/base64.rb 5 | README.md 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | Gemfile.lock 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "rake" 4 | gem "test-unit" 5 | 6 | group :sig do 7 | gem "rbs" 8 | gem "rdoc", "<= 6.11" 9 | end 10 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "base64" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start(__FILE__) 15 | -------------------------------------------------------------------------------- /.github/workflows/sig.yml: -------------------------------------------------------------------------------- 1 | name: sig 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | sig: 7 | runs-on: "ubuntu-latest" 8 | steps: 9 | - uses: actions/checkout@v6 10 | - name: Set up Ruby 11 | uses: ruby/setup-ruby@v1 12 | with: 13 | bundler-cache: true 14 | ruby-version: ruby 15 | - name: Install dependencies 16 | run: | 17 | bundle config set with 'sig' 18 | bundle install 19 | - name: Run RBS test, annotate and confirm 20 | run: bundle exec rake rbs:{test,annotate,confirm} 21 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new(:test) do |t| 5 | t.libs << "test" 6 | t.libs << "lib" 7 | t.test_files = FileList["test/**/test_*.rb"] 8 | end 9 | 10 | namespace :rbs do 11 | task :test do 12 | sh "ruby -I lib test_sig/test_base64.rb" 13 | end 14 | 15 | task :annotate do 16 | require "tmpdir" 17 | require "pathname" 18 | 19 | Dir.mktmpdir do |tmpdir| 20 | system("rdoc --ri --output #{tmpdir}/doc --root=. lib") 21 | system("rbs annotate --no-system --no-gems --no-site --no-home -d #{tmpdir}/doc sig") 22 | end 23 | end 24 | 25 | task :confirm do 26 | puts "Testing if RBS docs are updated with respect to RDoc" 27 | sh "git diff --exit-code sig/" 28 | end 29 | end 30 | 31 | task :default => :test 32 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ruby-versions: 7 | uses: ruby/actions/.github/workflows/ruby_versions.yml@master 8 | with: 9 | min_version: 2.4 10 | test: 11 | needs: ruby-versions 12 | name: build (${{ matrix.ruby }} / ${{ matrix.os }}) 13 | strategy: 14 | matrix: 15 | ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} 16 | os: [ ubuntu-latest, macos-latest, windows-latest ] 17 | exclude: 18 | - { os: macos-latest, ruby: 2.4 } 19 | - { os: macos-latest, ruby: 2.5 } 20 | - { os: windows-latest, ruby: truffleruby-head } 21 | - { os: windows-latest, ruby: truffleruby } 22 | runs-on: ${{ matrix.os }} 23 | steps: 24 | - uses: actions/checkout@v6 25 | - name: Set up Ruby 26 | uses: ruby/setup-ruby@v1 27 | with: 28 | ruby-version: ${{ matrix.ruby }} 29 | - name: Install dependencies 30 | run: | 31 | bundle config set without 'sig' 32 | bundle install 33 | - name: Run test 34 | run: rake test 35 | -------------------------------------------------------------------------------- /base64.gemspec: -------------------------------------------------------------------------------- 1 | name = File.basename(__FILE__, ".gemspec") 2 | version = ["lib", Array.new(name.count("-")+1).join("/")].find do |dir| 3 | break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line| 4 | /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 5 | end rescue nil 6 | end 7 | 8 | Gem::Specification.new do |spec| 9 | spec.name = name 10 | spec.version = version 11 | spec.authors = ["Yusuke Endoh"] 12 | spec.email = ["mame@ruby-lang.org"] 13 | 14 | spec.summary = %q{Support for encoding and decoding binary data using a Base64 representation.} 15 | spec.description = %q{Support for encoding and decoding binary data using a Base64 representation.} 16 | spec.homepage = "https://github.com/ruby/base64" 17 | spec.required_ruby_version = Gem::Requirement.new(">= 2.4") 18 | spec.licenses = ["Ruby", "BSD-2-Clause"] 19 | 20 | spec.metadata["homepage_uri"] = spec.homepage 21 | spec.metadata["source_code_uri"] = spec.homepage 22 | spec.metadata["changelog_uri"] = spec.homepage + "/releases" 23 | 24 | spec.files = ["README.md", "BSDL", "COPYING", "LEGAL", "lib/base64.rb", "sig/base64.rbs"] 25 | spec.bindir = "exe" 26 | spec.executables = [] 27 | spec.require_paths = ["lib"] 28 | end 29 | -------------------------------------------------------------------------------- /.github/workflows/push_gem.yml: -------------------------------------------------------------------------------- 1 | name: Publish gem to rubygems.org 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | push: 13 | if: github.repository == 'ruby/base64' 14 | runs-on: ubuntu-latest 15 | 16 | environment: 17 | name: rubygems.org 18 | url: https://rubygems.org/gems/base64 19 | 20 | permissions: 21 | contents: write 22 | id-token: write 23 | 24 | steps: 25 | - name: Harden Runner 26 | uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 27 | with: 28 | egress-policy: audit 29 | 30 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 31 | 32 | - name: Set up Ruby 33 | uses: ruby/setup-ruby@d5126b9b3579e429dd52e51e68624dda2e05be25 # v1.267.0 34 | with: 35 | bundler-cache: true 36 | ruby-version: "ruby" 37 | 38 | - name: Publish to RubyGems 39 | uses: rubygems/release-gem@1c162a739e8b4cb21a676e97b087e8268d8fc40b # v1.1.2 40 | 41 | - name: Create GitHub release 42 | run: | 43 | tag_name="$(git describe --tags --abbrev=0)" 44 | gh release create "${tag_name}" --verify-tag --generate-notes 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | -------------------------------------------------------------------------------- /BSDL: -------------------------------------------------------------------------------- 1 | Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /test_sig/test_base64.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'base64' 4 | require 'test/unit' 5 | require 'rbs/unit_test' 6 | 7 | class Base64SingletonTest < Test::Unit::TestCase 8 | include RBS::UnitTest::TypeAssertions 9 | 10 | library 'base64' 11 | testing "singleton(::Base64)" 12 | 13 | def test_decode64 14 | assert_send_type '(String) -> String', 15 | Base64, :decode64, 'aGVsbG8gd29ybGQ=' 16 | end 17 | 18 | def test_encode64 19 | assert_send_type '(String) -> String', 20 | Base64, :encode64, 'hello world' 21 | end 22 | 23 | def test_strict_decode64 24 | assert_send_type '(String) -> String', 25 | Base64, :strict_decode64, 'aGVsbG8gd29ybGQ=' 26 | end 27 | 28 | def test_strict_encode64 29 | assert_send_type '(String) -> String', 30 | Base64, :strict_encode64, 'hello world' 31 | end 32 | 33 | def test_urlsafe_decode64 34 | assert_send_type '(String) -> String', 35 | Base64, :urlsafe_decode64, 'aGVsbG8gd29ybGQ=' 36 | end 37 | 38 | def test_urlsafe_encode64 39 | assert_send_type '(String) -> String', 40 | Base64, :urlsafe_encode64, 'hello world' 41 | assert_send_type '(String, padding: bool) -> String', 42 | Base64, :urlsafe_encode64, '*', padding: false 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Base64 2 | 3 | The Base64 module provides for the encoding (`#encode64`, `#strict_encode64`, 4 | `#urlsafe_encode64`) and decoding (`#decode64`, `#strict_decode64`, 5 | `#urlsafe_decode64`) of binary data using a Base64 representation. 6 | 7 | ## Installation 8 | 9 | Add this line to your application's Gemfile: 10 | 11 | ```ruby 12 | gem 'base64' 13 | ``` 14 | 15 | And then execute: 16 | 17 | $ bundle install 18 | 19 | Or install it yourself as: 20 | 21 | $ gem install base64 22 | 23 | ## Usage 24 | 25 | A simple encoding and decoding. 26 | 27 | ```ruby 28 | require "base64" 29 | 30 | enc = Base64.encode64('Send reinforcements') 31 | # -> "U2VuZCByZWluZm9yY2VtZW50cw==\n" 32 | plain = Base64.decode64(enc) 33 | # -> "Send reinforcements" 34 | ``` 35 | 36 | The purpose of using base64 to encode data is that it translates any 37 | binary data into purely printable characters. 38 | 39 | ## Development 40 | 41 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 42 | 43 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 44 | 45 | ## Contributing 46 | 47 | Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/base64. 48 | 49 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Ruby is copyrighted free software by Yukihiro Matsumoto . 2 | You can redistribute it and/or modify it under either the terms of the 3 | 2-clause BSDL (see the file BSDL), or the conditions below: 4 | 5 | 1. You may make and give away verbatim copies of the source form of the 6 | software without restriction, provided that you duplicate all of the 7 | original copyright notices and associated disclaimers. 8 | 9 | 2. You may modify your copy of the software in any way, provided that 10 | you do at least ONE of the following: 11 | 12 | a. place your modifications in the Public Domain or otherwise 13 | make them Freely Available, such as by posting said 14 | modifications to Usenet or an equivalent medium, or by allowing 15 | the author to include your modifications in the software. 16 | 17 | b. use the modified software only within your corporation or 18 | organization. 19 | 20 | c. give non-standard binaries non-standard names, with 21 | instructions on where to get the original software distribution. 22 | 23 | d. make other distribution arrangements with the author. 24 | 25 | 3. You may distribute the software in object code or binary form, 26 | provided that you do at least ONE of the following: 27 | 28 | a. distribute the binaries and library files of the software, 29 | together with instructions (in the manual page or equivalent) 30 | on where to get the original distribution. 31 | 32 | b. accompany the distribution with the machine-readable source of 33 | the software. 34 | 35 | c. give non-standard binaries non-standard names, with 36 | instructions on where to get the original software distribution. 37 | 38 | d. make other distribution arrangements with the author. 39 | 40 | 4. You may modify and include the part of the software into any other 41 | software (possibly commercial). But some files in the distribution 42 | are not written by the author, so that they are not under these terms. 43 | 44 | For the list of those files and their copying conditions, see the 45 | file LEGAL. 46 | 47 | 5. The scripts and library files supplied as input to or produced as 48 | output from the software do not automatically fall under the 49 | copyright of the software, but belong to whomever generated them, 50 | and may be sold commercially, and may be aggregated with this 51 | software. 52 | 53 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 54 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 55 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 56 | PURPOSE. 57 | -------------------------------------------------------------------------------- /LEGAL: -------------------------------------------------------------------------------- 1 | # -*- rdoc -*- 2 | 3 | = LEGAL NOTICE INFORMATION 4 | -------------------------- 5 | 6 | All the files in this distribution are covered under either the Ruby's 7 | license (see the file COPYING) or public-domain except some files 8 | mentioned below. 9 | 10 | == MIT License 11 | >>> 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | == Old-style BSD license 32 | >>> 33 | Redistribution and use in source and binary forms, with or without 34 | modification, are permitted provided that the following conditions 35 | are met: 36 | 1. Redistributions of source code must retain the above copyright 37 | notice, this list of conditions and the following disclaimer. 38 | 2. Redistributions in binary form must reproduce the above copyright 39 | notice, this list of conditions and the following disclaimer in the 40 | documentation and/or other materials provided with the distribution. 41 | 3. Neither the name of the University nor the names of its contributors 42 | may be used to endorse or promote products derived from this software 43 | without specific prior written permission. 44 | 45 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 46 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 47 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 48 | ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 49 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 50 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 51 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 52 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 53 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 54 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 55 | SUCH DAMAGE. 56 | 57 | IMPORTANT NOTE:: 58 | 59 | From ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change 60 | paragraph 3 above is now null and void. 61 | -------------------------------------------------------------------------------- /test/base64/test_base64.rb: -------------------------------------------------------------------------------- 1 | # coding: US-ASCII 2 | # frozen_string_literal: true 3 | require "test/unit" 4 | require "base64" 5 | 6 | class TestBase64 < Test::Unit::TestCase 7 | def test_sample 8 | assert_equal("U2VuZCByZWluZm9yY2VtZW50cw==\n", Base64.encode64('Send reinforcements')) 9 | assert_equal('Send reinforcements', Base64.decode64("U2VuZCByZWluZm9yY2VtZW50cw==\n")) 10 | assert_equal( 11 | "Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBjb2RlcnMKdG8gbGVhcm4g\nUnVieQ==\n", 12 | Base64.encode64("Now is the time for all good coders\nto learn Ruby")) 13 | assert_equal( 14 | "Now is the time for all good coders\nto learn Ruby", 15 | Base64.decode64("Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBjb2RlcnMKdG8gbGVhcm4g\nUnVieQ==\n")) 16 | assert_equal( 17 | "VGhpcyBpcyBsaW5lIG9uZQpUaGlzIGlzIGxpbmUgdHdvClRoaXMgaXMgbGlu\nZSB0aHJlZQpBbmQgc28gb24uLi4K\n", 18 | Base64.encode64("This is line one\nThis is line two\nThis is line three\nAnd so on...\n")) 19 | assert_equal( 20 | "This is line one\nThis is line two\nThis is line three\nAnd so on...\n", 21 | Base64.decode64("VGhpcyBpcyBsaW5lIG9uZQpUaGlzIGlzIGxpbmUgdHdvClRoaXMgaXMgbGluZSB0aHJlZQpBbmQgc28gb24uLi4K")) 22 | end 23 | 24 | def test_encode64 25 | assert_equal("", Base64.encode64("")) 26 | assert_equal("AA==\n", Base64.encode64("\0")) 27 | assert_equal("AAA=\n", Base64.encode64("\0\0")) 28 | assert_equal("AAAA\n", Base64.encode64("\0\0\0")) 29 | assert_equal("/w==\n", Base64.encode64("\377")) 30 | assert_equal("//8=\n", Base64.encode64("\377\377")) 31 | assert_equal("////\n", Base64.encode64("\377\377\377")) 32 | assert_equal("/+8=\n", Base64.encode64("\xff\xef")) 33 | end 34 | 35 | def test_decode64 36 | assert_equal("", Base64.decode64("")) 37 | assert_equal("\0", Base64.decode64("AA==\n")) 38 | assert_equal("\0\0", Base64.decode64("AAA=\n")) 39 | assert_equal("\0\0\0", Base64.decode64("AAAA\n")) 40 | assert_equal("\377", Base64.decode64("/w==\n")) 41 | assert_equal("\377\377", Base64.decode64("//8=\n")) 42 | assert_equal("\377\377\377", Base64.decode64("////\n")) 43 | assert_equal("\xff\xef", Base64.decode64("/+8=\n")) 44 | end 45 | 46 | def test_strict_encode64 47 | assert_equal("", Base64.strict_encode64("")) 48 | assert_equal("AA==", Base64.strict_encode64("\0")) 49 | assert_equal("AAA=", Base64.strict_encode64("\0\0")) 50 | assert_equal("AAAA", Base64.strict_encode64("\0\0\0")) 51 | assert_equal("/w==", Base64.strict_encode64("\377")) 52 | assert_equal("//8=", Base64.strict_encode64("\377\377")) 53 | assert_equal("////", Base64.strict_encode64("\377\377\377")) 54 | assert_equal("/+8=", Base64.strict_encode64("\xff\xef")) 55 | end 56 | 57 | def test_strict_decode64 58 | assert_equal("", Base64.strict_decode64("")) 59 | assert_equal("\0", Base64.strict_decode64("AA==")) 60 | assert_equal("\0\0", Base64.strict_decode64("AAA=")) 61 | assert_equal("\0\0\0", Base64.strict_decode64("AAAA")) 62 | assert_equal("\377", Base64.strict_decode64("/w==")) 63 | assert_equal("\377\377", Base64.strict_decode64("//8=")) 64 | assert_equal("\377\377\377", Base64.strict_decode64("////")) 65 | assert_equal("\xff\xef", Base64.strict_decode64("/+8=")) 66 | 67 | assert_raise(ArgumentError) { Base64.strict_decode64("^") } 68 | assert_raise(ArgumentError) { Base64.strict_decode64("A") } 69 | assert_raise(ArgumentError) { Base64.strict_decode64("A^") } 70 | assert_raise(ArgumentError) { Base64.strict_decode64("AA") } 71 | assert_raise(ArgumentError) { Base64.strict_decode64("AA=") } 72 | assert_raise(ArgumentError) { Base64.strict_decode64("AA===") } 73 | assert_raise(ArgumentError) { Base64.strict_decode64("AA=x") } 74 | assert_raise(ArgumentError) { Base64.strict_decode64("AAA") } 75 | assert_raise(ArgumentError) { Base64.strict_decode64("AAA^") } 76 | assert_raise(ArgumentError) { Base64.strict_decode64("AB==") } 77 | assert_raise(ArgumentError) { Base64.strict_decode64("AAB=") } 78 | end 79 | 80 | def test_urlsafe_encode64 81 | assert_equal("", Base64.urlsafe_encode64("")) 82 | assert_equal("AA==", Base64.urlsafe_encode64("\0")) 83 | assert_equal("AAA=", Base64.urlsafe_encode64("\0\0")) 84 | assert_equal("AAAA", Base64.urlsafe_encode64("\0\0\0")) 85 | assert_equal("_w==", Base64.urlsafe_encode64("\377")) 86 | assert_equal("__8=", Base64.urlsafe_encode64("\377\377")) 87 | assert_equal("____", Base64.urlsafe_encode64("\377\377\377")) 88 | assert_equal("_-8=", Base64.urlsafe_encode64("\xff\xef")) 89 | end 90 | 91 | def test_urlsafe_encode64_unpadded 92 | assert_equal("", Base64.urlsafe_encode64("", padding: false)) 93 | assert_equal("AA", Base64.urlsafe_encode64("\0", padding: false)) 94 | assert_equal("AAA", Base64.urlsafe_encode64("\0\0", padding: false)) 95 | assert_equal("AAAA", Base64.urlsafe_encode64("\0\0\0", padding: false)) 96 | end 97 | 98 | def test_urlsafe_decode64 99 | assert_equal("", Base64.urlsafe_decode64("")) 100 | assert_equal("\0", Base64.urlsafe_decode64("AA==")) 101 | assert_equal("\0\0", Base64.urlsafe_decode64("AAA=")) 102 | assert_equal("\0\0\0", Base64.urlsafe_decode64("AAAA")) 103 | assert_equal("\377", Base64.urlsafe_decode64("_w==")) 104 | assert_equal("\377\377", Base64.urlsafe_decode64("__8=")) 105 | assert_equal("\377\377\377", Base64.urlsafe_decode64("____")) 106 | assert_equal("\xff\xef", Base64.urlsafe_decode64("_+8=")) 107 | end 108 | 109 | def test_urlsafe_decode64_unpadded 110 | assert_equal("\0", Base64.urlsafe_decode64("AA")) 111 | assert_equal("\0\0", Base64.urlsafe_decode64("AAA")) 112 | assert_equal("\0\0\0", Base64.urlsafe_decode64("AAAA")) 113 | assert_raise(ArgumentError) { Base64.urlsafe_decode64("AA=") } 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /sig/base64.rbs: -------------------------------------------------------------------------------- 1 | # 2 | # Module Base64 provides methods for: 3 | # 4 | # * Encoding a binary string (containing non-ASCII characters) as a string of 5 | # printable ASCII characters. 6 | # * Decoding such an encoded string. 7 | # 8 | # Base64 is commonly used in contexts where binary data is not allowed or 9 | # supported: 10 | # 11 | # * Images in HTML or CSS files, or in URLs. 12 | # * Email attachments. 13 | # 14 | # A Base64-encoded string is about one-third larger that its source. See the 15 | # [Wikipedia article](https://en.wikipedia.org/wiki/Base64) for more 16 | # information. 17 | # 18 | # This module provides three pairs of encode/decode methods. Your choices among 19 | # these methods should depend on: 20 | # 21 | # * Which character set is to be used for encoding and decoding. 22 | # * Whether "padding" is to be used. 23 | # * Whether encoded strings are to contain newlines. 24 | # 25 | # Note: Examples on this page assume that the including program has executed: 26 | # 27 | # require 'base64' 28 | # 29 | # ## Encoding Character Sets 30 | # 31 | # A Base64-encoded string consists only of characters from a 64-character set: 32 | # 33 | # * `('A'..'Z')`. 34 | # * `('a'..'z')`. 35 | # * `('0'..'9')`. 36 | # * `=`, the 'padding' character. 37 | # * Either: 38 | # * `%w[+ /]`: 39 | # [RFC-2045-compliant](https://datatracker.ietf.org/doc/html/rfc2045); 40 | # *not* safe for URLs. 41 | # * `%w[- _]`: 42 | # [RFC-4648-compliant](https://datatracker.ietf.org/doc/html/rfc4648); 43 | # safe for URLs. 44 | # 45 | # If you are working with Base64-encoded strings that will come from or be put 46 | # into URLs, you should choose this encoder-decoder pair of RFC-4648-compliant 47 | # methods: 48 | # 49 | # * Base64.urlsafe_encode64 and Base64.urlsafe_decode64. 50 | # 51 | # Otherwise, you may choose any of the pairs in this module, including the pair 52 | # above, or the RFC-2045-compliant pairs: 53 | # 54 | # * Base64.encode64 and Base64.decode64. 55 | # * Base64.strict_encode64 and Base64.strict_decode64. 56 | # 57 | # ## Padding 58 | # 59 | # Base64-encoding changes a triplet of input bytes into a quartet of output 60 | # characters. 61 | # 62 | # **Padding in Encode Methods** 63 | # 64 | # Padding -- extending an encoded string with zero, one, or two trailing `=` 65 | # characters -- is performed by methods Base64.encode64, Base64.strict_encode64, 66 | # and, by default, Base64.urlsafe_encode64: 67 | # 68 | # Base64.encode64('s') # => "cw==\n" 69 | # Base64.strict_encode64('s') # => "cw==" 70 | # Base64.urlsafe_encode64('s') # => "cw==" 71 | # Base64.urlsafe_encode64('s', padding: false) # => "cw" 72 | # 73 | # When padding is performed, the encoded string is always of length *4n*, where 74 | # `n` is a non-negative integer: 75 | # 76 | # * Input bytes of length *3n* generate unpadded output characters of length 77 | # *4n*: 78 | # 79 | # # n = 1: 3 bytes => 4 characters. 80 | # Base64.strict_encode64('123') # => "MDEy" 81 | # # n = 2: 6 bytes => 8 characters. 82 | # Base64.strict_encode64('123456') # => "MDEyMzQ1" 83 | # 84 | # * Input bytes of length *3n+1* generate padded output characters of length 85 | # *4(n+1)*, with two padding characters at the end: 86 | # 87 | # # n = 1: 4 bytes => 8 characters. 88 | # Base64.strict_encode64('1234') # => "MDEyMw==" 89 | # # n = 2: 7 bytes => 12 characters. 90 | # Base64.strict_encode64('1234567') # => "MDEyMzQ1Ng==" 91 | # 92 | # * Input bytes of length *3n+2* generate padded output characters of length 93 | # *4(n+1)*, with one padding character at the end: 94 | # 95 | # # n = 1: 5 bytes => 8 characters. 96 | # Base64.strict_encode64('12345') # => "MDEyMzQ=" 97 | # # n = 2: 8 bytes => 12 characters. 98 | # Base64.strict_encode64('12345678') # => "MDEyMzQ1Njc=" 99 | # 100 | # When padding is suppressed, for a positive integer *n*: 101 | # 102 | # * Input bytes of length *3n* generate unpadded output characters of length 103 | # *4n*: 104 | # 105 | # # n = 1: 3 bytes => 4 characters. 106 | # Base64.urlsafe_encode64('123', padding: false) # => "MDEy" 107 | # # n = 2: 6 bytes => 8 characters. 108 | # Base64.urlsafe_encode64('123456', padding: false) # => "MDEyMzQ1" 109 | # 110 | # * Input bytes of length *3n+1* generate unpadded output characters of length 111 | # *4n+2*, with two padding characters at the end: 112 | # 113 | # # n = 1: 4 bytes => 6 characters. 114 | # Base64.urlsafe_encode64('1234', padding: false) # => "MDEyMw" 115 | # # n = 2: 7 bytes => 10 characters. 116 | # Base64.urlsafe_encode64('1234567', padding: false) # => "MDEyMzQ1Ng" 117 | # 118 | # * Input bytes of length *3n+2* generate unpadded output characters of length 119 | # *4n+3*, with one padding character at the end: 120 | # 121 | # # n = 1: 5 bytes => 7 characters. 122 | # Base64.urlsafe_encode64('12345', padding: false) # => "MDEyMzQ" 123 | # # m = 2: 8 bytes => 11 characters. 124 | # Base64.urlsafe_encode64('12345678', padding: false) # => "MDEyMzQ1Njc" 125 | # 126 | # **Padding in Decode Methods** 127 | # 128 | # All of the Base64 decode methods support (but do not require) padding. 129 | # 130 | # Method Base64.decode64 does not check the size of the padding: 131 | # 132 | # Base64.decode64("MDEyMzQ1Njc") # => "01234567" 133 | # Base64.decode64("MDEyMzQ1Njc=") # => "01234567" 134 | # Base64.decode64("MDEyMzQ1Njc==") # => "01234567" 135 | # 136 | # Method Base64.strict_decode64 strictly enforces padding size: 137 | # 138 | # Base64.strict_decode64("MDEyMzQ1Njc") # Raises ArgumentError 139 | # Base64.strict_decode64("MDEyMzQ1Njc=") # => "01234567" 140 | # Base64.strict_decode64("MDEyMzQ1Njc==") # Raises ArgumentError 141 | # 142 | # Method Base64.urlsafe_decode64 allows padding in the encoded string, which if 143 | # present, must be correct: see 144 | # [Padding](Base64.html#module-Base64-label-Padding), above: 145 | # 146 | # Base64.urlsafe_decode64("MDEyMzQ1Njc") # => "01234567" 147 | # Base64.urlsafe_decode64("MDEyMzQ1Njc=") # => "01234567" 148 | # Base64.urlsafe_decode64("MDEyMzQ1Njc==") # Raises ArgumentError. 149 | # 150 | # ## Newlines 151 | # 152 | # An encoded string returned by Base64.encode64 has an embedded newline 153 | # character after each 60-character sequence, and, if non-empty, at the end: 154 | # 155 | # # No newline if empty. 156 | # encoded = Base64.encode64("\x00" * 0) 157 | # encoded.index("\n") # => nil 158 | # 159 | # # Newline at end of short output. 160 | # encoded = Base64.encode64("\x00" * 1) 161 | # encoded.size # => 4 162 | # encoded.index("\n") # => 4 163 | # 164 | # # Newline at end of longer output. 165 | # encoded = Base64.encode64("\x00" * 45) 166 | # encoded.size # => 60 167 | # encoded.index("\n") # => 60 168 | # 169 | # # Newlines embedded and at end of still longer output. 170 | # encoded = Base64.encode64("\x00" * 46) 171 | # encoded.size # => 65 172 | # encoded.rindex("\n") # => 65 173 | # encoded.split("\n").map {|s| s.size } # => [60, 4] 174 | # 175 | # The string to be encoded may itself contain newlines, which are encoded as 176 | # Base64: 177 | # 178 | # # Base64.encode64("\n\n\n") # => "CgoK\n" 179 | # s = "This is line 1\nThis is line 2\n" 180 | # Base64.encode64(s) # => "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK\n" 181 | # 182 | module Base64 183 | # 187 | # Returns a string containing the decoding of an RFC-2045-compliant 188 | # Base64-encoded string `encoded_string`: 189 | # 190 | # s = "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK\n" 191 | # Base64.decode64(s) # => "This is line 1\nThis is line 2\n" 192 | # 193 | # Non-Base64 characters in `encoded_string` are ignored; see [Encoding Character 194 | # Set](Base64.html#module-Base64-label-Encoding+Character+Sets) above: these 195 | # include newline characters and characters `-` and `/`: 196 | # 197 | # Base64.decode64("\x00\n-_") # => "" 198 | # 199 | # Padding in `encoded_string` (even if incorrect) is ignored: 200 | # 201 | # Base64.decode64("MDEyMzQ1Njc") # => "01234567" 202 | # Base64.decode64("MDEyMzQ1Njc=") # => "01234567" 203 | # Base64.decode64("MDEyMzQ1Njc==") # => "01234567" 204 | # 205 | def self?.decode64: (String str) -> String 206 | 207 | # 211 | # Returns a string containing the RFC-2045-compliant Base64-encoding of 212 | # `string`. 213 | # 214 | # Per RFC 2045, the returned string may contain the URL-unsafe characters `+` or 215 | # `/`; see [Encoding Character 216 | # Set](Base64.html#module-Base64-label-Encoding+Character+Sets) above: 217 | # 218 | # Base64.encode64("\xFB\xEF\xBE") # => "++++\n" 219 | # Base64.encode64("\xFF\xFF\xFF") # => "////\n" 220 | # 221 | # The returned string may include padding; see 222 | # [Padding](Base64.html#module-Base64-label-Padding) above. 223 | # 224 | # Base64.encode64('*') # => "Kg==\n" 225 | # 226 | # The returned string ends with a newline character, and if sufficiently long 227 | # will have one or more embedded newline characters; see 228 | # [Newlines](Base64.html#module-Base64-label-Newlines) above: 229 | # 230 | # Base64.encode64('*') # => "Kg==\n" 231 | # Base64.encode64('*' * 46) 232 | # # => "KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioq\nKg==\n" 233 | # 234 | # The string to be encoded may itself contain newlines, which will be encoded as 235 | # ordinary Base64: 236 | # 237 | # Base64.encode64("\n\n\n") # => "CgoK\n" 238 | # s = "This is line 1\nThis is line 2\n" 239 | # Base64.encode64(s) # => "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK\n" 240 | # 241 | def self?.encode64: (String bin) -> String 242 | 243 | # 247 | # Returns a string containing the decoding of an RFC-2045-compliant 248 | # Base64-encoded string `encoded_string`: 249 | # 250 | # s = "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK" 251 | # Base64.strict_decode64(s) # => "This is line 1\nThis is line 2\n" 252 | # 253 | # Non-Base64 characters in `encoded_string` are not allowed; see [Encoding 254 | # Character Set](Base64.html#module-Base64-label-Encoding+Character+Sets) above: 255 | # these include newline characters and characters `-` and `/`: 256 | # 257 | # Base64.strict_decode64("\n") # Raises ArgumentError 258 | # Base64.strict_decode64('-') # Raises ArgumentError 259 | # Base64.strict_decode64('_') # Raises ArgumentError 260 | # 261 | # Padding in `encoded_string`, if present, must be correct: 262 | # 263 | # Base64.strict_decode64("MDEyMzQ1Njc") # Raises ArgumentError 264 | # Base64.strict_decode64("MDEyMzQ1Njc=") # => "01234567" 265 | # Base64.strict_decode64("MDEyMzQ1Njc==") # Raises ArgumentError 266 | # 267 | def self?.strict_decode64: (String str) -> String 268 | 269 | # 273 | # Returns a string containing the RFC-2045-compliant Base64-encoding of 274 | # `string`. 275 | # 276 | # Per RFC 2045, the returned string may contain the URL-unsafe characters `+` or 277 | # `/`; see [Encoding Character 278 | # Set](Base64.html#module-Base64-label-Encoding+Character+Sets) above: 279 | # 280 | # Base64.strict_encode64("\xFB\xEF\xBE") # => "++++\n" 281 | # Base64.strict_encode64("\xFF\xFF\xFF") # => "////\n" 282 | # 283 | # The returned string may include padding; see 284 | # [Padding](Base64.html#module-Base64-label-Padding) above. 285 | # 286 | # Base64.strict_encode64('*') # => "Kg==\n" 287 | # 288 | # The returned string will have no newline characters, regardless of its length; 289 | # see [Newlines](Base64.html#module-Base64-label-Newlines) above: 290 | # 291 | # Base64.strict_encode64('*') # => "Kg==" 292 | # Base64.strict_encode64('*' * 46) 293 | # # => "KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKg==" 294 | # 295 | # The string to be encoded may itself contain newlines, which will be encoded as 296 | # ordinary Base64: 297 | # 298 | # Base64.strict_encode64("\n\n\n") # => "CgoK" 299 | # s = "This is line 1\nThis is line 2\n" 300 | # Base64.strict_encode64(s) # => "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK" 301 | # 302 | def self?.strict_encode64: (String bin) -> String 303 | 304 | # 308 | # Returns the decoding of an RFC-4648-compliant Base64-encoded string 309 | # `encoded_string`: 310 | # 311 | # `encoded_string` may not contain non-Base64 characters; see [Encoding 312 | # Character Set](Base64.html#module-Base64-label-Encoding+Character+Sets) above: 313 | # 314 | # Base64.urlsafe_decode64('+') # Raises ArgumentError. 315 | # Base64.urlsafe_decode64('/') # Raises ArgumentError. 316 | # Base64.urlsafe_decode64("\n") # Raises ArgumentError. 317 | # 318 | # Padding in `encoded_string`, if present, must be correct: see 319 | # [Padding](Base64.html#module-Base64-label-Padding), above: 320 | # 321 | # Base64.urlsafe_decode64("MDEyMzQ1Njc") # => "01234567" 322 | # Base64.urlsafe_decode64("MDEyMzQ1Njc=") # => "01234567" 323 | # Base64.urlsafe_decode64("MDEyMzQ1Njc==") # Raises ArgumentError. 324 | # 325 | def self?.urlsafe_decode64: (String str) -> String 326 | 327 | # 331 | # Returns the RFC-4648-compliant Base64-encoding of `string`. 332 | # 333 | # Per RFC 4648, the returned string will not contain the URL-unsafe characters 334 | # `+` or `/`, but instead may contain the URL-safe characters `-` and `_`; see 335 | # [Encoding Character 336 | # Set](Base64.html#module-Base64-label-Encoding+Character+Sets) above: 337 | # 338 | # Base64.urlsafe_encode64("\xFB\xEF\xBE") # => "----" 339 | # Base64.urlsafe_encode64("\xFF\xFF\xFF") # => "____" 340 | # 341 | # By default, the returned string may have padding; see 342 | # [Padding](Base64.html#module-Base64-label-Padding), above: 343 | # 344 | # Base64.urlsafe_encode64('*') # => "Kg==" 345 | # 346 | # Optionally, you can suppress padding: 347 | # 348 | # Base64.urlsafe_encode64('*', padding: false) # => "Kg" 349 | # 350 | # The returned string will have no newline characters, regardless of its length; 351 | # see [Newlines](Base64.html#module-Base64-label-Newlines) above: 352 | # 353 | # Base64.urlsafe_encode64('*') # => "Kg==" 354 | # Base64.urlsafe_encode64('*' * 46) 355 | # # => "KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKg==" 356 | # 357 | def self?.urlsafe_encode64: (String bin, ?padding: boolish) -> String 358 | end 359 | -------------------------------------------------------------------------------- /lib/base64.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # \Module \Base64 provides methods for: 4 | # 5 | # - \Encoding a binary string (containing non-ASCII characters) 6 | # as a string of printable ASCII characters. 7 | # - Decoding such an encoded string. 8 | # 9 | # \Base64 is commonly used in contexts where binary data 10 | # is not allowed or supported: 11 | # 12 | # - Images in HTML or CSS files, or in URLs. 13 | # - Email attachments. 14 | # 15 | # A \Base64-encoded string is about one-third larger that its source. 16 | # See the {Wikipedia article}[https://en.wikipedia.org/wiki/Base64] 17 | # for more information. 18 | # 19 | # This module provides three pairs of encode/decode methods. 20 | # Your choices among these methods should depend on: 21 | # 22 | # - Which character set is to be used for encoding and decoding. 23 | # - Whether "padding" is to be used. 24 | # - Whether encoded strings are to contain newlines. 25 | # 26 | # Note: Examples on this page assume that the including program has executed: 27 | # 28 | # require 'base64' 29 | # 30 | # == \Encoding Character Sets 31 | # 32 | # A \Base64-encoded string consists only of characters from a 64-character set: 33 | # 34 | # - ('A'..'Z'). 35 | # - ('a'..'z'). 36 | # - ('0'..'9'). 37 | # - =, the 'padding' character. 38 | # - Either: 39 | # - %w[+ /]: 40 | # {RFC-2045-compliant}[https://datatracker.ietf.org/doc/html/rfc2045]; 41 | # _not_ safe for URLs. 42 | # - %w[- _]: 43 | # {RFC-4648-compliant}[https://datatracker.ietf.org/doc/html/rfc4648]; 44 | # safe for URLs. 45 | # 46 | # If you are working with \Base64-encoded strings that will come from 47 | # or be put into URLs, you should choose this encoder-decoder pair 48 | # of RFC-4648-compliant methods: 49 | # 50 | # - Base64.urlsafe_encode64 and Base64.urlsafe_decode64. 51 | # 52 | # Otherwise, you may choose any of the pairs in this module, 53 | # including the pair above, or the RFC-2045-compliant pairs: 54 | # 55 | # - Base64.encode64 and Base64.decode64. 56 | # - Base64.strict_encode64 and Base64.strict_decode64. 57 | # 58 | # == Padding 59 | # 60 | # \Base64-encoding changes a triplet of input bytes 61 | # into a quartet of output characters. 62 | # 63 | # Padding in Encode Methods 64 | # 65 | # Padding -- extending an encoded string with zero, one, or two trailing 66 | # = characters -- is performed by methods Base64.encode64, 67 | # Base64.strict_encode64, and, by default, Base64.urlsafe_encode64: 68 | # 69 | # Base64.encode64('s') # => "cw==\n" 70 | # Base64.strict_encode64('s') # => "cw==" 71 | # Base64.urlsafe_encode64('s') # => "cw==" 72 | # Base64.urlsafe_encode64('s', padding: false) # => "cw" 73 | # 74 | # When padding is performed, the encoded string is always of length 4n, 75 | # where +n+ is a non-negative integer: 76 | # 77 | # - Input bytes of length 3n generate unpadded output characters 78 | # of length 4n: 79 | # 80 | # # n = 1: 3 bytes => 4 characters. 81 | # Base64.strict_encode64('123') # => "MDEy" 82 | # # n = 2: 6 bytes => 8 characters. 83 | # Base64.strict_encode64('123456') # => "MDEyMzQ1" 84 | # 85 | # - Input bytes of length 3n+1 generate padded output characters 86 | # of length 4(n+1), with two padding characters at the end: 87 | # 88 | # # n = 1: 4 bytes => 8 characters. 89 | # Base64.strict_encode64('1234') # => "MDEyMw==" 90 | # # n = 2: 7 bytes => 12 characters. 91 | # Base64.strict_encode64('1234567') # => "MDEyMzQ1Ng==" 92 | # 93 | # - Input bytes of length 3n+2 generate padded output characters 94 | # of length 4(n+1), with one padding character at the end: 95 | # 96 | # # n = 1: 5 bytes => 8 characters. 97 | # Base64.strict_encode64('12345') # => "MDEyMzQ=" 98 | # # n = 2: 8 bytes => 12 characters. 99 | # Base64.strict_encode64('12345678') # => "MDEyMzQ1Njc=" 100 | # 101 | # When padding is suppressed, for a positive integer n: 102 | # 103 | # - Input bytes of length 3n generate unpadded output characters 104 | # of length 4n: 105 | # 106 | # # n = 1: 3 bytes => 4 characters. 107 | # Base64.urlsafe_encode64('123', padding: false) # => "MDEy" 108 | # # n = 2: 6 bytes => 8 characters. 109 | # Base64.urlsafe_encode64('123456', padding: false) # => "MDEyMzQ1" 110 | # 111 | # - Input bytes of length 3n+1 generate unpadded output characters 112 | # of length 4n+2, with two padding characters at the end: 113 | # 114 | # # n = 1: 4 bytes => 6 characters. 115 | # Base64.urlsafe_encode64('1234', padding: false) # => "MDEyMw" 116 | # # n = 2: 7 bytes => 10 characters. 117 | # Base64.urlsafe_encode64('1234567', padding: false) # => "MDEyMzQ1Ng" 118 | # 119 | # - Input bytes of length 3n+2 generate unpadded output characters 120 | # of length 4n+3, with one padding character at the end: 121 | # 122 | # # n = 1: 5 bytes => 7 characters. 123 | # Base64.urlsafe_encode64('12345', padding: false) # => "MDEyMzQ" 124 | # # m = 2: 8 bytes => 11 characters. 125 | # Base64.urlsafe_encode64('12345678', padding: false) # => "MDEyMzQ1Njc" 126 | # 127 | # Padding in Decode Methods 128 | # 129 | # All of the \Base64 decode methods support (but do not require) padding. 130 | # 131 | # \Method Base64.decode64 does not check the size of the padding: 132 | # 133 | # Base64.decode64("MDEyMzQ1Njc") # => "01234567" 134 | # Base64.decode64("MDEyMzQ1Njc=") # => "01234567" 135 | # Base64.decode64("MDEyMzQ1Njc==") # => "01234567" 136 | # 137 | # \Method Base64.strict_decode64 strictly enforces padding size: 138 | # 139 | # Base64.strict_decode64("MDEyMzQ1Njc") # Raises ArgumentError 140 | # Base64.strict_decode64("MDEyMzQ1Njc=") # => "01234567" 141 | # Base64.strict_decode64("MDEyMzQ1Njc==") # Raises ArgumentError 142 | # 143 | # \Method Base64.urlsafe_decode64 allows padding in the encoded string, 144 | # which if present, must be correct: 145 | # see {Padding}[Base64.html#module-Base64-label-Padding], above: 146 | # 147 | # Base64.urlsafe_decode64("MDEyMzQ1Njc") # => "01234567" 148 | # Base64.urlsafe_decode64("MDEyMzQ1Njc=") # => "01234567" 149 | # Base64.urlsafe_decode64("MDEyMzQ1Njc==") # Raises ArgumentError. 150 | # 151 | # == Newlines 152 | # 153 | # An encoded string returned by Base64.encode64 has an embedded 154 | # newline character after each 60-character sequence, and, 155 | # if non-empty, at the end: 156 | # 157 | # # No newline if empty. 158 | # encoded = Base64.encode64("\x00" * 0) 159 | # encoded.index("\n") # => nil 160 | # 161 | # # Newline at end of short output. 162 | # encoded = Base64.encode64("\x00" * 1) 163 | # encoded.size # => 4 164 | # encoded.index("\n") # => 4 165 | # 166 | # # Newline at end of longer output. 167 | # encoded = Base64.encode64("\x00" * 45) 168 | # encoded.size # => 60 169 | # encoded.index("\n") # => 60 170 | # 171 | # # Newlines embedded and at end of still longer output. 172 | # encoded = Base64.encode64("\x00" * 46) 173 | # encoded.size # => 65 174 | # encoded.rindex("\n") # => 65 175 | # encoded.split("\n").map {|s| s.size } # => [60, 4] 176 | # 177 | # The string to be encoded may itself contain newlines, 178 | # which are encoded as \Base64: 179 | # 180 | # # Base64.encode64("\n\n\n") # => "CgoK\n" 181 | # s = "This is line 1\nThis is line 2\n" 182 | # Base64.encode64(s) # => "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK\n" 183 | # 184 | module Base64 185 | 186 | # The version of this module. 187 | VERSION = "0.3.0" 188 | 189 | module_function 190 | 191 | # :call-seq: 192 | # Base64.encode64(string) -> encoded_string 193 | # 194 | # Returns a string containing the RFC-2045-compliant \Base64-encoding of +string+. 195 | # 196 | # Per RFC 2045, the returned string may contain the URL-unsafe characters 197 | # + or /; 198 | # see {Encoding Character Set}[Base64.html#module-Base64-label-Encoding+Character+Sets] above: 199 | # 200 | # Base64.encode64("\xFB\xEF\xBE") # => "++++\n" 201 | # Base64.encode64("\xFF\xFF\xFF") # => "////\n" 202 | # 203 | # The returned string may include padding; 204 | # see {Padding}[Base64.html#module-Base64-label-Padding] above. 205 | # 206 | # Base64.encode64('*') # => "Kg==\n" 207 | # 208 | # The returned string ends with a newline character, and if sufficiently long 209 | # will have one or more embedded newline characters; 210 | # see {Newlines}[Base64.html#module-Base64-label-Newlines] above: 211 | # 212 | # Base64.encode64('*') # => "Kg==\n" 213 | # Base64.encode64('*' * 46) 214 | # # => "KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioq\nKg==\n" 215 | # 216 | # The string to be encoded may itself contain newlines, 217 | # which will be encoded as ordinary \Base64: 218 | # 219 | # Base64.encode64("\n\n\n") # => "CgoK\n" 220 | # s = "This is line 1\nThis is line 2\n" 221 | # Base64.encode64(s) # => "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK\n" 222 | # 223 | def encode64(bin) 224 | [bin].pack("m") 225 | end 226 | 227 | # :call-seq: 228 | # Base64.decode64(encoded_string) -> decoded_string 229 | # 230 | # Returns a string containing the decoding of an RFC-2045-compliant 231 | # \Base64-encoded string +encoded_string+: 232 | # 233 | # s = "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK\n" 234 | # Base64.decode64(s) # => "This is line 1\nThis is line 2\n" 235 | # 236 | # Non-\Base64 characters in +encoded_string+ are ignored; 237 | # see {Encoding Character Set}[Base64.html#module-Base64-label-Encoding+Character+Sets] above: 238 | # these include newline characters and characters - and /: 239 | # 240 | # Base64.decode64("\x00\n-_") # => "" 241 | # 242 | # Padding in +encoded_string+ (even if incorrect) is ignored: 243 | # 244 | # Base64.decode64("MDEyMzQ1Njc") # => "01234567" 245 | # Base64.decode64("MDEyMzQ1Njc=") # => "01234567" 246 | # Base64.decode64("MDEyMzQ1Njc==") # => "01234567" 247 | # 248 | def decode64(str) 249 | str.unpack1("m") 250 | end 251 | 252 | # :call-seq: 253 | # Base64.strict_encode64(string) -> encoded_string 254 | # 255 | # Returns a string containing the RFC-2045-compliant \Base64-encoding of +string+. 256 | # 257 | # Per RFC 2045, the returned string may contain the URL-unsafe characters 258 | # + or /; 259 | # see {Encoding Character Set}[Base64.html#module-Base64-label-Encoding+Character+Sets] above: 260 | # 261 | # Base64.strict_encode64("\xFB\xEF\xBE") # => "++++\n" 262 | # Base64.strict_encode64("\xFF\xFF\xFF") # => "////\n" 263 | # 264 | # The returned string may include padding; 265 | # see {Padding}[Base64.html#module-Base64-label-Padding] above. 266 | # 267 | # Base64.strict_encode64('*') # => "Kg==\n" 268 | # 269 | # The returned string will have no newline characters, regardless of its length; 270 | # see {Newlines}[Base64.html#module-Base64-label-Newlines] above: 271 | # 272 | # Base64.strict_encode64('*') # => "Kg==" 273 | # Base64.strict_encode64('*' * 46) 274 | # # => "KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKg==" 275 | # 276 | # The string to be encoded may itself contain newlines, 277 | # which will be encoded as ordinary \Base64: 278 | # 279 | # Base64.strict_encode64("\n\n\n") # => "CgoK" 280 | # s = "This is line 1\nThis is line 2\n" 281 | # Base64.strict_encode64(s) # => "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK" 282 | # 283 | def strict_encode64(bin) 284 | [bin].pack("m0") 285 | end 286 | 287 | # :call-seq: 288 | # Base64.strict_decode64(encoded_string) -> decoded_string 289 | # 290 | # Returns a string containing the decoding of an RFC-2045-compliant 291 | # \Base64-encoded string +encoded_string+: 292 | # 293 | # s = "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK" 294 | # Base64.strict_decode64(s) # => "This is line 1\nThis is line 2\n" 295 | # 296 | # Non-\Base64 characters in +encoded_string+ are not allowed; 297 | # see {Encoding Character Set}[Base64.html#module-Base64-label-Encoding+Character+Sets] above: 298 | # these include newline characters and characters - and /: 299 | # 300 | # Base64.strict_decode64("\n") # Raises ArgumentError 301 | # Base64.strict_decode64('-') # Raises ArgumentError 302 | # Base64.strict_decode64('_') # Raises ArgumentError 303 | # 304 | # Padding in +encoded_string+, if present, must be correct: 305 | # 306 | # Base64.strict_decode64("MDEyMzQ1Njc") # Raises ArgumentError 307 | # Base64.strict_decode64("MDEyMzQ1Njc=") # => "01234567" 308 | # Base64.strict_decode64("MDEyMzQ1Njc==") # Raises ArgumentError 309 | # 310 | def strict_decode64(str) 311 | str.unpack1("m0") 312 | end 313 | 314 | # :call-seq: 315 | # Base64.urlsafe_encode64(string) -> encoded_string 316 | # 317 | # Returns the RFC-4648-compliant \Base64-encoding of +string+. 318 | # 319 | # Per RFC 4648, the returned string will not contain the URL-unsafe characters 320 | # + or /, 321 | # but instead may contain the URL-safe characters 322 | # - and _; 323 | # see {Encoding Character Set}[Base64.html#module-Base64-label-Encoding+Character+Sets] above: 324 | # 325 | # Base64.urlsafe_encode64("\xFB\xEF\xBE") # => "----" 326 | # Base64.urlsafe_encode64("\xFF\xFF\xFF") # => "____" 327 | # 328 | # By default, the returned string may have padding; 329 | # see {Padding}[Base64.html#module-Base64-label-Padding], above: 330 | # 331 | # Base64.urlsafe_encode64('*') # => "Kg==" 332 | # 333 | # Optionally, you can suppress padding: 334 | # 335 | # Base64.urlsafe_encode64('*', padding: false) # => "Kg" 336 | # 337 | # The returned string will have no newline characters, regardless of its length; 338 | # see {Newlines}[Base64.html#module-Base64-label-Newlines] above: 339 | # 340 | # Base64.urlsafe_encode64('*') # => "Kg==" 341 | # Base64.urlsafe_encode64('*' * 46) 342 | # # => "KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKg==" 343 | # 344 | def urlsafe_encode64(bin, padding: true) 345 | str = strict_encode64(bin) 346 | str.chomp!("==") or str.chomp!("=") unless padding 347 | str.tr!("+/", "-_") 348 | str 349 | end 350 | 351 | # :call-seq: 352 | # Base64.urlsafe_decode64(encoded_string) -> decoded_string 353 | # 354 | # Returns the decoding of an RFC-4648-compliant \Base64-encoded string +encoded_string+: 355 | # 356 | # +encoded_string+ may not contain non-Base64 characters; 357 | # see {Encoding Character Set}[Base64.html#module-Base64-label-Encoding+Character+Sets] above: 358 | # 359 | # Base64.urlsafe_decode64('+') # Raises ArgumentError. 360 | # Base64.urlsafe_decode64('/') # Raises ArgumentError. 361 | # Base64.urlsafe_decode64("\n") # Raises ArgumentError. 362 | # 363 | # Padding in +encoded_string+, if present, must be correct: 364 | # see {Padding}[Base64.html#module-Base64-label-Padding], above: 365 | # 366 | # Base64.urlsafe_decode64("MDEyMzQ1Njc") # => "01234567" 367 | # Base64.urlsafe_decode64("MDEyMzQ1Njc=") # => "01234567" 368 | # Base64.urlsafe_decode64("MDEyMzQ1Njc==") # Raises ArgumentError. 369 | # 370 | def urlsafe_decode64(str) 371 | # NOTE: RFC 4648 does say nothing about unpadded input, but says that 372 | # "the excess pad characters MAY also be ignored", so it is inferred that 373 | # unpadded input is also acceptable. 374 | if !str.end_with?("=") && str.length % 4 != 0 375 | str = str.ljust((str.length + 3) & ~3, "=") 376 | str.tr!("-_", "+/") 377 | else 378 | str = str.tr("-_", "+/") 379 | end 380 | strict_decode64(str) 381 | end 382 | end 383 | --------------------------------------------------------------------------------