ob64 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c8b76a774595914a6124939fe2410fe4ae5bb0687286315ab4ec1f05b8569d70
4
- data.tar.gz: 662b74f7505f28362436d44771e51720884cf0ea48076b12e59094eacf4e87b6
3
+ metadata.gz: 110cd68288a2dd3ff785dca2a4dc2bdc0b3d47445fae88d0fef3a3a3db1b0d96
4
+ data.tar.gz: a0d837a2ba44079613b99fd862cdf156d3105b69359edbefeae32711a2be402e
5
5
  SHA512:
6
- metadata.gz: b0e357acff49af3e42b36cb41b521393ed4f6604fc4f5943c491a504741b985d5a47e63217acd27e0896cb2b23a8df2412fe133fff1d76d275decc5cf0a39f7f
7
- data.tar.gz: 560f3439cff65bc442f16a8815afa729422e6286f6305a044f92035bf28666b80c80c8471ffd7ac0b83379ea999643a531ef9a5905449093dafe10cff36490e9
6
+ metadata.gz: 6a4fa2cabc1fad23055020ed1c49906de65e494f82fbf19966cc0a511cc8e073026dc77c4e0cb9b57d77d67e21f068fdbf79c6dfa2de978381444e1793a4793a
7
+ data.tar.gz: 432523720a899411c6a3840fc410bd4e540b7f8211dda82dff2643046959ce52ceec8bc0a8f15a6518bddb913cce69129b52ade76d8dbd33f3a7e980a690c693
@@ -89,7 +89,8 @@ jobs:
89
89
  require "ob64"
90
90
  require "ob64/core_ext"
91
91
  s = Random.bytes(4096)
92
- abort "Base64 round trip failed" unless Ob64::Base64.decode(Ob64::Base64.encode(s)) == s
92
+ abort "RFC 4648 round trip failed" unless Ob64::Base64::RFC4648.decode(Ob64::Base64::RFC4648.encode(s)) == s
93
+ abort "RFC 2045 round trip failed" unless Ob64::Base64::RFC2045.decode(Ob64::Base64::RFC2045.encode(s)) == s
93
94
  abort "UU round trip failed" unless Ob64::UU.decode(Ob64::UU.encode(s)) == s
94
95
  abort "pack round trip failed" unless [s].pack("m0").unpack1("m0") == s
95
96
  '
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## [0.7.0] - 2026-10-26
2
+
3
+ - **Breaking:** Split `Ob64::Base64` into one module per RFC. `Ob64::Base64::RFC4648` has `encode`, `decode`, `encode_urlsafe`, `decode_urlsafe`, `encoded_length_of` and `decoded_length_of` (were `Ob64::Base64.encode`, `decode`, `urlsafe_encode`, `urlsafe_decode`, `encoded_length_of` and `decoded_length_of`).
4
+ - Add `Ob64::Base64::RFC2045` with `encode` (optionally with CRLF line breaks), `decode`, `encoded_length_of` and `decoded_length_of`, like `pack("m")` and `unpack1("m")`.
5
+
1
6
  ## [0.6.0] - 2026-10-26
2
7
 
3
8
  - **Breaking:** `ob64/core_ext` patches `Array#pack`, `String#unpack` and `String#unpack1` instead of `Base64`.
data/Gemfile CHANGED
@@ -5,7 +5,7 @@ source "https://rubygems.org"
5
5
  gemspec
6
6
 
7
7
  gem "benchmark-ips"
8
- gem "byebug"
8
+ gem "debug"
9
9
  gem "guard"
10
10
  gem "guard-rspec"
11
11
  gem "rake", "~> 13.0"
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/ob64.svg)](http://rubygems.org/gems/ob64)
4
4
  [![CI](https://github.com/jcmfernandes/ob64/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/jcmfernandes/ob64/actions/workflows/main.yml)
5
- [![Yard Docs](https://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/gems/ob64/0.6.0)
5
+ [![Yard Docs](https://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/gems/ob64/0.7.0)
6
6
 
7
7
  A *fast* Base64 and uuencode encoder and decoder as a Ruby gem.
8
8
 
@@ -40,26 +40,34 @@ require "ob64"
40
40
 
41
41
  ### Base64
42
42
 
43
+ One module per RFC, each with `encode`, `decode`, `encoded_length_of` and `decoded_length_of`.
44
+
45
+ RFC 4648, no line breaks, same output as `pack("m0")` / `unpack1("m0")`:
46
+
43
47
  ```ruby
44
48
  bin1 = "This is data!"
45
- string = Ob64::Base64.encode(bin1)
49
+ string = Ob64::Base64::RFC4648.encode(bin1)
46
50
  # string = "VGhpcyBpcyBkYXRhIQ=="
47
51
 
48
- bin2 = Ob64::Base64.decode(string)
52
+ bin2 = Ob64::Base64::RFC4648.decode(string)
49
53
  puts "Same? #{bin1 == bin2}"
50
54
  # "Same? true"
55
+
56
+ Ob64::Base64::RFC4648.decode("VGhpcyBpcyBkYXRhIQ", loose: true) # padding optional
57
+ Ob64::Base64::RFC4648.encode_urlsafe(bin1, padding: false) # '-' and '_' instead of '+' and '/'
58
+ Ob64::Base64::RFC4648.decode_urlsafe(string)
51
59
  ```
52
60
 
53
- Decode input with or without padding:
61
+ Bad padding, non-zero trailing bits and non-alphabet characters raise `ArgumentError`.
62
+
63
+ RFC 2045 (MIME), line-wrapped, same output as `pack("m")` / `unpack1("m")`:
54
64
 
55
65
  ```ruby
56
- Ob64::Base64.decode("VGhpcyBpcyBkYXRhIQ", loose: true)
66
+ Ob64::Base64::RFC2045.encode(bin) # 60-char lines, each ending in "\n"
67
+ Ob64::Base64::RFC2045.encode(bin, 76, crlf: true)
68
+ Ob64::Base64::RFC2045.decode(string) # skips line breaks and other non-alphabet characters
57
69
  ```
58
70
 
59
- Bad padding, non-zero trailing bits and non-alphabet characters still raise `ArgumentError`.
60
-
61
- URL-safe alphabet: `Ob64::Base64.urlsafe_encode(bin, padding: false)`, `Ob64::Base64.urlsafe_decode(string)`.
62
-
63
71
  ### uuencode
64
72
 
65
73
  Same output as `pack("u")` / `unpack1("u")`:
@@ -118,8 +126,6 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/jcmfer
118
126
  ## TODO
119
127
 
120
128
  * Streaming interface
121
- * RFC 2045 (MIME) encoding/decoding
122
- * Windows support
123
129
 
124
130
  ## License
125
131
 
data/benchmark.rb CHANGED
@@ -41,28 +41,30 @@ each_size do |bin|
41
41
 
42
42
  compare(
43
43
  "Ruby pack(\"m0\")" => -> { NATIVE_PACK.bind_call([bin], "m0") },
44
- "ob64 Base64.encode" => -> { Ob64::Base64.encode(bin) },
44
+ "ob64 Base64::RFC4648.encode" => -> { Ob64::Base64::RFC4648.encode(bin) },
45
45
  "ob64 pack(\"m0\")" => -> { [bin].pack("m0") }
46
46
  )
47
47
  compare(
48
48
  "Ruby unpack1(\"m0\")" => -> { NATIVE_UNPACK1.bind_call(m0, "m0") },
49
- "ob64 Base64.decode" => -> { Ob64::Base64.decode(m0) },
49
+ "ob64 Base64::RFC4648.decode" => -> { Ob64::Base64::RFC4648.decode(m0) },
50
50
  "ob64 unpack1(\"m0\")" => -> { m0.unpack1("m0") }
51
51
  )
52
52
  compare(
53
53
  "Ruby pack(\"m0\").tr" => -> { NATIVE_PACK.bind_call([bin], "m0").tr("+/", "-_") },
54
- "ob64 Base64.urlsafe_encode" => -> { Ob64::Base64.urlsafe_encode(bin) }
54
+ "ob64 Base64::RFC4648.encode_urlsafe" => -> { Ob64::Base64::RFC4648.encode_urlsafe(bin) }
55
55
  )
56
56
  compare(
57
57
  "Ruby tr.unpack1(\"m0\")" => -> { NATIVE_UNPACK1.bind_call(urlsafe.tr("-_", "+/"), "m0") },
58
- "ob64 Base64.urlsafe_decode" => -> { Ob64::Base64.urlsafe_decode(urlsafe) }
58
+ "ob64 Base64::RFC4648.decode_urlsafe" => -> { Ob64::Base64::RFC4648.decode_urlsafe(urlsafe) }
59
59
  )
60
60
  compare(
61
61
  "Ruby pack(\"m\")" => -> { NATIVE_PACK.bind_call([bin], "m") },
62
+ "ob64 Base64::RFC2045.encode" => -> { Ob64::Base64::RFC2045.encode(bin) },
62
63
  "ob64 pack(\"m\")" => -> { [bin].pack("m") }
63
64
  )
64
65
  compare(
65
66
  "Ruby unpack1(\"m\")" => -> { NATIVE_UNPACK1.bind_call(m, "m") },
67
+ "ob64 Base64::RFC2045.decode" => -> { Ob64::Base64::RFC2045.decode(m) },
66
68
  "ob64 unpack1(\"m\")" => -> { m.unpack1("m") }
67
69
  )
68
70
  end if $run_base64_benchmark
data/bin/rake ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rake' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ bundle_binstub = File.expand_path("bundle", __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require "rubygems"
25
+ require "bundler/setup"
26
+
27
+ load Gem.bin_path("rake", "rake")
data/ext/ob64/ob64_pack.c CHANGED
@@ -60,6 +60,20 @@ uuencode(VALUE from, long line_len)
60
60
  return ascii_result(res, n);
61
61
  }
62
62
 
63
+ /* line_len: bytes per line, a multiple of 3; crlf: lines end in "\r\n" */
64
+ static VALUE
65
+ base64_encode_rfc2045(VALUE from, long line_len, int crlf)
66
+ {
67
+ long plen = RSTRING_LEN(from);
68
+ /* A line break per line */
69
+ long lines = (plen + line_len - 1) / line_len;
70
+ VALUE res = rb_str_buf_new((plen + 2) / 3 * 4 + lines * (crlf ? 2 : 1));
71
+ size_t n = pack_base64_encode_rfc2045(RSTRING_PTR(from), (size_t)plen, RSTRING_PTR(res), (int)line_len, crlf);
72
+ RB_GC_GUARD(from);
73
+
74
+ return ascii_result(res, n);
75
+ }
76
+
63
77
  static VALUE
64
78
  Ob64_pack(int argc, VALUE *argv, VALUE ary)
65
79
  {
@@ -75,31 +89,22 @@ Ob64_pack(int argc, VALUE *argv, VALUE ary)
75
89
  return rb_call_super_kw(argc, argv, RB_PASS_CALLED_KEYWORDS);
76
90
  }
77
91
 
78
- VALUE res;
79
- size_t n;
80
-
81
92
  if (type == 'm' && count == 0) {
82
- res = rb_str_buf_new((plen + 2) / 3 * 4);
83
- n = pack_base64_encode(RSTRING_PTR(from), (size_t)plen, RSTRING_PTR(res), 0);
84
- }
85
- else {
86
- long len = count;
93
+ VALUE res = rb_str_buf_new((plen + 2) / 3 * 4);
94
+ size_t n = pack_base64_encode(RSTRING_PTR(from), (size_t)plen, RSTRING_PTR(res), 0);
95
+ RB_GC_GUARD(from);
87
96
 
88
- /* '*' gets 45, as natively */
89
- if (len <= 2) len = 45;
90
- else if (len > 63 && type == 'u') len = 63;
91
- else len = len / 3 * 3;
97
+ return ascii_result(res, n);
98
+ }
92
99
 
93
- if (type == 'u') return uuencode(from, len);
100
+ long len = count;
94
101
 
95
- /* A newline per line */
96
- long lines = (plen + len - 1) / len;
97
- res = rb_str_buf_new((plen + 2) / 3 * 4 + lines);
98
- n = pack_base64_encode_rfc2045(RSTRING_PTR(from), (size_t)plen, RSTRING_PTR(res), (int)len);
99
- }
100
- RB_GC_GUARD(from);
102
+ /* '*' gets 45, as natively */
103
+ if (len <= 2) len = 45;
104
+ else if (len > 63 && type == 'u') len = 63;
105
+ else len = len / 3 * 3;
101
106
 
102
- return ascii_result(res, n);
107
+ return type == 'u' ? uuencode(from, len) : base64_encode_rfc2045(from, len, 0);
103
108
  }
104
109
 
105
110
  static VALUE
@@ -174,6 +179,58 @@ Ob64_unpack1(int argc, VALUE *argv, VALUE str)
174
179
  return res;
175
180
  }
176
181
 
182
+ /* Bytes per line for a line_length in chars; validates both arguments */
183
+ static long
184
+ rfc2045_line_bytes(VALUE bin, VALUE line_length)
185
+ {
186
+ long chars = NUM2LONG(line_length);
187
+
188
+ StringValue(bin);
189
+ if (chars < 4 || chars % 4 || chars / 4 * 3 > INT_MAX) {
190
+ rb_raise(rb_eArgError, "line length must be a positive multiple of 4: %ld", chars);
191
+ }
192
+ /* Bound the output size */
193
+ if (RSTRING_LEN(bin) > LONG_MAX / 4) rb_raise(rb_eArgError, "argument too big");
194
+
195
+ return chars / 4 * 3;
196
+ }
197
+
198
+ static VALUE
199
+ Ob64_encode_rfc_2045(VALUE self, VALUE bin, VALUE line_length, VALUE crlf)
200
+ {
201
+ long line_bytes = rfc2045_line_bytes(bin, line_length);
202
+
203
+ return base64_encode_rfc2045(bin, line_bytes, RTEST(crlf));
204
+ }
205
+
206
+ static VALUE
207
+ Ob64_decode_rfc_2045(VALUE self, VALUE string)
208
+ {
209
+ StringValue(string);
210
+
211
+ return base64_decode(string, 1);
212
+ }
213
+
214
+ static VALUE
215
+ Ob64_encoded_length_of_rfc_2045(VALUE self, VALUE bin, VALUE line_length, VALUE crlf)
216
+ {
217
+ long line_bytes = rfc2045_line_bytes(bin, line_length);
218
+ long plen = RSTRING_LEN(bin);
219
+
220
+ /* 4 chars per 3 bytes, a line break per line */
221
+ return LONG2NUM((plen + 2) / 3 * 4 + (plen + line_bytes - 1) / line_bytes * (RTEST(crlf) ? 2 : 1));
222
+ }
223
+
224
+ static VALUE
225
+ Ob64_decoded_length_of_rfc_2045(VALUE self, VALUE string)
226
+ {
227
+ StringValue(string);
228
+ size_t n = pack_base64_decoded_length_rfc2045(RSTRING_PTR(string), (size_t)RSTRING_LEN(string));
229
+ RB_GC_GUARD(string);
230
+
231
+ return SIZET2NUM(n);
232
+ }
233
+
177
234
  static VALUE
178
235
  Ob64_uuencode(VALUE self, VALUE bin)
179
236
  {
@@ -221,6 +278,10 @@ Init_ob64_pack(VALUE mExt)
221
278
  rb_define_method(mUnpack, "unpack", Ob64_unpack, -1);
222
279
  rb_define_method(mUnpack, "unpack1", Ob64_unpack1, -1);
223
280
 
281
+ rb_define_private_method(mExt, "__encode_rfc_2045", Ob64_encode_rfc_2045, 3);
282
+ rb_define_private_method(mExt, "__decode_rfc_2045", Ob64_decode_rfc_2045, 1);
283
+ rb_define_private_method(mExt, "__encoded_length_of_rfc_2045", Ob64_encoded_length_of_rfc_2045, 3);
284
+ rb_define_private_method(mExt, "__decoded_length_of_rfc_2045", Ob64_decoded_length_of_rfc_2045, 1);
224
285
  rb_define_private_method(mExt, "__uuencode", Ob64_uuencode, 1);
225
286
  rb_define_private_method(mExt, "__uudecode", Ob64_uudecode, 1);
226
287
  rb_define_private_method(mExt, "__uuencoded_length_of", Ob64_uuencoded_length_of, 1);
@@ -4993,7 +4993,7 @@ pack_base64_decode (const char *src, size_t srclen, char *out, size_t *outlen, i
4993
4993
  * ====================================================================== */
4994
4994
 
4995
4995
  static size_t
4996
- encode_rfc2045_plain(const char *src, size_t srclen, char *out, int bytes_per_line)
4996
+ encode_rfc2045_plain(const char *src, size_t srclen, char *out, int bytes_per_line, int crlf)
4997
4997
  {
4998
4998
  const uint8_t *s = (const uint8_t *)src;
4999
4999
  char *o = out;
@@ -5012,6 +5012,7 @@ encode_rfc2045_plain(const char *src, size_t srclen, char *out, int bytes_per_li
5012
5012
  srclen -= 3;
5013
5013
  line_bytes += 3;
5014
5014
  if (line_bytes >= bytes_per_line) {
5015
+ if (crlf) *o++ = '\r';
5015
5016
  *o++ = '\n';
5016
5017
  line_bytes = 0;
5017
5018
  }
@@ -5022,6 +5023,7 @@ encode_rfc2045_plain(const char *src, size_t srclen, char *out, int bytes_per_li
5022
5023
  *o++ = base64_table_enc_6bit[((s[0] & 0x03) << 4) | (s[1] >> 4)];
5023
5024
  *o++ = base64_table_enc_6bit[(s[1] & 0x0F) << 2];
5024
5025
  *o++ = '=';
5026
+ if (crlf) *o++ = '\r';
5025
5027
  *o++ = '\n';
5026
5028
  }
5027
5029
  else if (srclen == 1) {
@@ -5029,9 +5031,11 @@ encode_rfc2045_plain(const char *src, size_t srclen, char *out, int bytes_per_li
5029
5031
  *o++ = base64_table_enc_6bit[(s[0] & 0x03) << 4];
5030
5032
  *o++ = '=';
5031
5033
  *o++ = '=';
5034
+ if (crlf) *o++ = '\r';
5032
5035
  *o++ = '\n';
5033
5036
  }
5034
5037
  else if (line_bytes > 0) {
5038
+ if (crlf) *o++ = '\r';
5035
5039
  *o++ = '\n';
5036
5040
  }
5037
5041
 
@@ -5052,13 +5056,13 @@ encode_whole (const struct codec *codec, const char *src, size_t srclen, char *o
5052
5056
  }
5053
5057
 
5054
5058
  size_t
5055
- pack_base64_encode_rfc2045(const char *src, size_t srclen, char *out, int bytes_per_line)
5059
+ pack_base64_encode_rfc2045(const char *src, size_t srclen, char *out, int bytes_per_line, int crlf)
5056
5060
  {
5057
5061
  const struct codec *codec = codec_get();
5058
5062
 
5059
5063
  bytes_per_line = bytes_per_line / 3 * 3;
5060
5064
  if (bytes_per_line == 0) bytes_per_line = 45;
5061
- if (!codec->enc_loop) return encode_rfc2045_plain(src, srclen, out, bytes_per_line);
5065
+ if (!codec->enc_loop) return encode_rfc2045_plain(src, srclen, out, bytes_per_line, crlf);
5062
5066
 
5063
5067
  size_t line = (size_t)bytes_per_line;
5064
5068
  size_t chars = line / 3 * 4;
@@ -5072,6 +5076,7 @@ pack_base64_encode_rfc2045(const char *src, size_t srclen, char *out, int bytes_
5072
5076
  if (lines_per_batch == 0) {
5073
5077
  /* Lines longer than the buffer: encode in place */
5074
5078
  o += encode_whole(codec, src, line, o);
5079
+ if (crlf) *o++ = '\r';
5075
5080
  *o++ = '\n';
5076
5081
  lines = 1;
5077
5082
  }
@@ -5081,6 +5086,7 @@ pack_base64_encode_rfc2045(const char *src, size_t srclen, char *out, int bytes_
5081
5086
  for (size_t i = 0; i < lines; i++) {
5082
5087
  memcpy(o, buf + i * chars, chars);
5083
5088
  o += chars;
5089
+ if (crlf) *o++ = '\r';
5084
5090
  *o++ = '\n';
5085
5091
  }
5086
5092
  }
@@ -5088,7 +5094,7 @@ pack_base64_encode_rfc2045(const char *src, size_t srclen, char *out, int bytes_
5088
5094
  srclen -= lines * line;
5089
5095
  }
5090
5096
 
5091
- return (size_t)(o - out) + encode_rfc2045_plain(src, srclen, o, bytes_per_line);
5097
+ return (size_t)(o - out) + encode_rfc2045_plain(src, srclen, o, bytes_per_line, crlf);
5092
5098
  }
5093
5099
 
5094
5100
  /*
@@ -5180,3 +5186,24 @@ pack_base64_decode_rfc2045(const char *src, size_t srclen, char *out)
5180
5186
  if (n == 0) return (size_t)(o - (uint8_t *)out) + decode_rfc2045_from(s, end, o, NULL, 0);
5181
5187
  return (size_t)(o - (uint8_t *)out) + decode_rfc2045_from(s, end, o, buf, n);
5182
5188
  }
5189
+
5190
+ size_t
5191
+ pack_base64_decoded_length_rfc2045(const char *src, size_t srclen)
5192
+ {
5193
+ const uint8_t *s = (const uint8_t *)src;
5194
+ const uint8_t *end = s + srclen;
5195
+ size_t n = 0;
5196
+
5197
+ /* Counts what pack_base64_decode_rfc2045 decodes: alphabet chars up to a '=' in group position 3-4 */
5198
+ for (; s < end; s++) {
5199
+ const uint8_t v = base64_table_dec_8bit[*s];
5200
+
5201
+ if (v == 255) continue;
5202
+ if (v == 254) {
5203
+ if (n % 4 >= 2) break;
5204
+ continue;
5205
+ }
5206
+ n++;
5207
+ }
5208
+ return n / 4 * 3 + (n % 4 == 3 ? 2 : n % 4 == 2 ? 1 : 0);
5209
+ }
@@ -27,7 +27,10 @@ size_t pack_base64_encode(const char *src, size_t srclen, char *out, int flags);
27
27
  int pack_base64_decode(const char *src, size_t srclen, char *out, size_t *outlen, int flags);
28
28
 
29
29
  /* RFC 2045 codec (m with line wrapping, lenient decode) */
30
- size_t pack_base64_encode_rfc2045(const char *src, size_t srclen, char *out, int bytes_per_line);
30
+ /* Lines end in "\r\n" if crlf, "\n" otherwise */
31
+ size_t pack_base64_encode_rfc2045(const char *src, size_t srclen, char *out, int bytes_per_line, int crlf);
31
32
  size_t pack_base64_decode_rfc2045(const char *src, size_t srclen, char *out);
33
+ /* Length pack_base64_decode_rfc2045 would return; reads all of src */
34
+ size_t pack_base64_decoded_length_rfc2045(const char *src, size_t srclen);
32
35
 
33
36
  #endif /* PACK_BASE64_H */
data/lib/ob64/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ob64
4
- VERSION = "0.6.0"
4
+ VERSION = "0.7.0"
5
5
  end
data/lib/ob64.rb CHANGED
@@ -3,7 +3,8 @@
3
3
  require_relative "ob64/version"
4
4
  require_relative "ob64_ext"
5
5
 
6
- # Fast base64 and uuencode encoding and decoding: see {Base64} and {UU}.
6
+ # Fast base64 and uuencode encoding and decoding: see {Base64::RFC4648},
7
+ # {Base64::RFC2045} and {UU}.
7
8
  module Ob64
8
9
  # The glue between Ruby and the C codecs. See +../ext/ob64/ob64_ext.c+.
9
10
  module Ext; end
@@ -44,88 +45,150 @@ module Ob64
44
45
  codecs.last
45
46
  end
46
47
 
47
- # Methods for base64-encoding and -decoding strings.
48
+ # Base64 encoding and decoding, one module per RFC: {RFC4648} and {RFC2045}.
48
49
  module Base64
49
- include Ext
50
- extend Ext
51
-
52
- module_function
53
-
54
- # Returns the Base64-encoded version of +bin+.
55
- # This method complies with RFC 4648.
56
- # No line feeds are added.
57
- #
58
- # @param bin [String]
59
- # @return [String]
60
- def encode(bin)
61
- __encode(bin, false, true)
62
- end
63
-
64
- # Returns the Base64-decoded version of +string+.
65
- # This method complies with RFC 4648.
66
- # ArgumentError is raised if +string+ is incorrectly padded or contains
67
- # non-alphabet characters. Note that CR or LF are also rejected.
68
- #
69
- # With +loose+, the padding character is optional: both correctly-padded
70
- # and unpadded input are accepted. Incorrectly-padded input is still
71
- # rejected.
72
- #
73
- # @param string [String]
74
- # @param loose [Boolean] - if unpadded input is accepted
75
- # @return [String]
76
- # @raise [ArgumentError] if +string+ cannot be decoded
77
- def decode(string, loose: false)
78
- __decode(string, false, loose)
79
- end
80
-
81
- # Returns the Base64-encoded version of +bin+.
82
- # This method complies with ``Base 64 Encoding with URL and Filename Safe
83
- # Alphabet'' in RFC 4648.
84
- # The alphabet uses '-' instead of '+' and '_' instead of '/'.
85
- # Note that the result can still contain '='.
86
- # You can remove the padding by setting +padding+ as false.
87
- #
88
- # @param bin [String]
89
- # @param padding [Boolean] - if the output must be padded
90
- # @return [String]
91
- def urlsafe_encode(bin, padding: true)
92
- __encode(bin, true, padding)
93
- end
94
-
95
- # Returns the Base64-decoded version of +string+.
96
- # This method complies with ``Base 64 Encoding with URL and Filename Safe
97
- # Alphabet'' in RFC 4648.
98
- # The alphabet uses '-' instead of '+' and '_' instead of '/'.
99
- #
100
- # The padding character is optional.
101
- # This method accepts both correctly-padded and unpadded input.
102
- # Note that it still rejects incorrectly-padded input.
103
- #
104
- # @param string [String]
105
- # @return [String]
106
- # @raise [ArgumentError] if +string+ cannot be decoded
107
- def urlsafe_decode(string)
108
- __decode(string, true, true)
50
+ # Base64 as in RFC 4648: no line breaks, strict decoding.
51
+ module RFC4648
52
+ include Ext
53
+ extend Ext
54
+
55
+ module_function
56
+
57
+ # Returns the Base64-encoded version of +bin+, like
58
+ # <tt>[bin].pack("m0")</tt>. No line feeds are added.
59
+ #
60
+ # @param bin [String]
61
+ # @return [String]
62
+ def encode(bin)
63
+ __encode(bin, false, true)
64
+ end
65
+
66
+ # Returns the Base64-decoded version of +string+, like
67
+ # <tt>string.unpack1("m0")</tt>.
68
+ # ArgumentError is raised if +string+ is incorrectly padded or contains
69
+ # non-alphabet characters. Note that CR or LF are also rejected.
70
+ #
71
+ # With +loose+, the padding character is optional: both correctly-padded
72
+ # and unpadded input are accepted. Incorrectly-padded input is still
73
+ # rejected.
74
+ #
75
+ # @param string [String]
76
+ # @param loose [Boolean] - if unpadded input is accepted
77
+ # @return [String]
78
+ # @raise [ArgumentError] if +string+ cannot be decoded
79
+ def decode(string, loose: false)
80
+ __decode(string, false, loose)
81
+ end
82
+
83
+ # Returns the Base64-encoded version of +bin+ with the ``URL and Filename
84
+ # Safe'' alphabet: '-' instead of '+' and '_' instead of '/'.
85
+ # Note that the result can still contain '='.
86
+ # You can remove the padding by setting +padding+ as false.
87
+ #
88
+ # @param bin [String]
89
+ # @param padding [Boolean] - if the output must be padded
90
+ # @return [String]
91
+ def encode_urlsafe(bin, padding: true)
92
+ __encode(bin, true, padding)
93
+ end
94
+
95
+ # Returns the Base64-decoded version of +string+ with the ``URL and
96
+ # Filename Safe'' alphabet: '-' instead of '+' and '_' instead of '/'.
97
+ #
98
+ # The padding character is optional.
99
+ # This method accepts both correctly-padded and unpadded input.
100
+ # Note that it still rejects incorrectly-padded input.
101
+ #
102
+ # @param string [String]
103
+ # @return [String]
104
+ # @raise [ArgumentError] if +string+ cannot be decoded
105
+ def decode_urlsafe(string)
106
+ __decode(string, true, true)
107
+ end
108
+
109
+ # Returns the length of {encode} (or, with the same +padding+,
110
+ # {encode_urlsafe}) of +bin+.
111
+ #
112
+ # Fast: computed from +bin+'s length.
113
+ #
114
+ # @param bin [String]
115
+ # @param padding [Boolean] - if the Base64-encoded version of +bin+ will be padded
116
+ # @return [Integer]
117
+ def encoded_length_of(bin, padding: true)
118
+ __encoded_length_of(bin, padding)
119
+ end
120
+
121
+ # Returns the length of {decode} (or {decode_urlsafe}) of +string+,
122
+ # padded or not.
123
+ #
124
+ # Fast: computed from +string+'s length and its last two characters. It
125
+ # doesn't validate the rest, so decoding may still raise.
126
+ #
127
+ # @param string [String]
128
+ # @return [Integer]
129
+ # @raise [ArgumentError] if +string+ has an invalid length
130
+ def decoded_length_of(string)
131
+ __decoded_length_of(string)
132
+ end
109
133
  end
110
134
 
111
- # Returns the length of the Base64-encoded version of +bin+.
112
- #
113
- # @param bin [String]
114
- # @param padding [Boolean] - if the Base64-encoded version of +bin+ will be padded
115
- # @return [Integer]
116
- def encoded_length_of(bin, padding: true)
117
- __encoded_length_of(bin, padding)
118
- end
119
-
120
- # Returns the length of the Base64-decoded version of +string+.
121
- #
122
- # ArgumentError is raised if +string+ has an invalid length.
123
- #
124
- # @param string [String]
125
- # @return [Integer]
126
- # @raise [ArgumentError] if +string+ has an invalid length
127
- def decoded_length_of(string)
128
- __decoded_length_of(string)
135
+ # Base64 as in RFC 2045 (MIME): line breaks, lenient decoding.
136
+ module RFC2045
137
+ include Ext
138
+ extend Ext
139
+
140
+ module_function
141
+
142
+ # Returns the Base64-encoded version of +bin+ in lines of +line_length+
143
+ # characters, each ending in a line break. Like <tt>[bin].pack("m")</tt>
144
+ # by default; with +crlf+, lines end in "\r\n", as MIME requires.
145
+ # RFC 2045 allows lines of up to 76 characters; longer ones are allowed
146
+ # here, as in +pack+, but break the RFC.
147
+ #
148
+ # @param bin [String]
149
+ # @param line_length [Integer] - characters per line, a positive multiple of 4
150
+ # @param crlf [Boolean] - if lines end in "\r\n" instead of "\n"
151
+ # @return [String]
152
+ # @raise [ArgumentError] if +line_length+ is not a positive multiple of 4
153
+ def encode(bin, line_length = 60, crlf: false)
154
+ __encode_rfc_2045(bin, line_length, crlf)
155
+ end
156
+
157
+ # Returns the Base64-decoded version of +string+, like
158
+ # <tt>string.unpack1("m")</tt>: characters outside the alphabet, such
159
+ # as line breaks, are skipped. Decoding is lenient, as in Ruby, and
160
+ # never raises for malformed input.
161
+ #
162
+ # @param string [String]
163
+ # @return [String]
164
+ def decode(string)
165
+ __decode_rfc_2045(string)
166
+ end
167
+
168
+ # Returns the length of {encode} of +bin+.
169
+ #
170
+ # Fast: computed from +bin+'s length.
171
+ #
172
+ # @param bin [String]
173
+ # @param line_length [Integer] - characters per line, a positive multiple of 4
174
+ # @param crlf [Boolean] - if lines end in "\r\n" instead of "\n"
175
+ # @return [Integer]
176
+ # @raise [ArgumentError] if +line_length+ is not a positive multiple of 4
177
+ def encoded_length_of(bin, line_length = 60, crlf: false)
178
+ __encoded_length_of_rfc_2045(bin, line_length, crlf)
179
+ end
180
+
181
+ # Returns the length of {decode} of +string+.
182
+ #
183
+ # Costly: reads all of +string+, since skipped characters and +=+ decide
184
+ # the length. To size a buffer, <tt>string.bytesize * 3 / 4</tt> is
185
+ # always enough and fast.
186
+ #
187
+ # @param string [String]
188
+ # @return [Integer]
189
+ def decoded_length_of(string)
190
+ __decoded_length_of_rfc_2045(string)
191
+ end
129
192
  end
130
193
  end
131
194
 
@@ -155,7 +218,9 @@ module Ob64
155
218
  __uudecode(string)
156
219
  end
157
220
 
158
- # Returns the length of the uuencoded version of +bin+.
221
+ # Returns the length of {encode} of +bin+.
222
+ #
223
+ # Fast: computed from +bin+'s length.
159
224
  #
160
225
  # @param bin [String]
161
226
  # @return [Integer]
@@ -163,9 +228,13 @@ module Ob64
163
228
  __uuencoded_length_of(bin)
164
229
  end
165
230
 
166
- # Returns the length of the uudecoded version of +string+.
231
+ # Returns the length of {decode} of +string+.
167
232
  # Like {decode}, never raises for malformed input.
168
233
  #
234
+ # Costly: reads all of +string+, since each line's length character
235
+ # decides the length. To size a buffer, <tt>string.bytesize * 3 / 4</tt>
236
+ # is always enough and fast.
237
+ #
169
238
  # @param string [String]
170
239
  # @return [Integer]
171
240
  def decoded_length_of(string)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ob64
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - João Fernandes
@@ -35,6 +35,7 @@ files:
35
35
  - bin/_guard-core
36
36
  - bin/console
37
37
  - bin/guard
38
+ - bin/rake
38
39
  - bin/rspec
39
40
  - bin/setup
40
41
  - bin/standardrb
@@ -58,8 +59,8 @@ licenses:
58
59
  metadata:
59
60
  homepage_uri: https://github.com/jcmfernandes/ob64
60
61
  changelog_uri: https://github.com/jcmfernandes/ob64/blob/master/CHANGELOG.md
61
- documentation_uri: https://www.rubydoc.info/gems/ob64/0.6.0
62
- source_code_uri: https://github.com/jcmfernandes/ob64/tree/v0.6.0
62
+ documentation_uri: https://www.rubydoc.info/gems/ob64/0.7.0
63
+ source_code_uri: https://github.com/jcmfernandes/ob64/tree/v0.7.0
63
64
  rdoc_options: []
64
65
  require_paths:
65
66
  - lib
@@ -74,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
74
75
  - !ruby/object:Gem::Version
75
76
  version: '0'
76
77
  requirements: []
77
- rubygems_version: 3.6.9
78
+ rubygems_version: 4.0.23
78
79
  specification_version: 4
79
80
  summary: A fast Base64 and uuencode encoder and decoder.
80
81
  test_files: []