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 +4 -4
- data/.github/workflows/main.yml +2 -1
- data/CHANGELOG.md +5 -0
- data/Gemfile +1 -1
- data/README.md +17 -11
- data/benchmark.rb +6 -4
- data/bin/rake +27 -0
- data/ext/ob64/ob64_pack.c +81 -20
- data/ext/ob64/pack_base64.c +31 -4
- data/ext/ob64/pack_base64.h +4 -1
- data/lib/ob64/version.rb +1 -1
- data/lib/ob64.rb +151 -82
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 110cd68288a2dd3ff785dca2a4dc2bdc0b3d47445fae88d0fef3a3a3db1b0d96
|
|
4
|
+
data.tar.gz: a0d837a2ba44079613b99fd862cdf156d3105b69359edbefeae32711a2be402e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6a4fa2cabc1fad23055020ed1c49906de65e494f82fbf19966cc0a511cc8e073026dc77c4e0cb9b57d77d67e21f068fdbf79c6dfa2de978381444e1793a4793a
|
|
7
|
+
data.tar.gz: 432523720a899411c6a3840fc410bd4e540b7f8211dda82dff2643046959ce52ceec8bc0a8f15a6518bddb913cce69129b52ade76d8dbd33f3a7e980a690c693
|
data/.github/workflows/main.yml
CHANGED
|
@@ -89,7 +89,8 @@ jobs:
|
|
|
89
89
|
require "ob64"
|
|
90
90
|
require "ob64/core_ext"
|
|
91
91
|
s = Random.bytes(4096)
|
|
92
|
-
abort "
|
|
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
data/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](http://rubygems.org/gems/ob64)
|
|
4
4
|
[](https://github.com/jcmfernandes/ob64/actions/workflows/main.yml)
|
|
5
|
-
[](http://www.rubydoc.info/gems/ob64/0.
|
|
5
|
+
[](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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
89
|
-
|
|
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
|
-
|
|
100
|
+
long len = count;
|
|
94
101
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
|
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);
|
data/ext/ob64/pack_base64.c
CHANGED
|
@@ -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
|
+
}
|
data/ext/ob64/pack_base64.h
CHANGED
|
@@ -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
|
-
|
|
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
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}
|
|
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
|
-
#
|
|
48
|
+
# Base64 encoding and decoding, one module per RFC: {RFC4648} and {RFC2045}.
|
|
48
49
|
module Base64
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
#
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
62
|
-
source_code_uri: https://github.com/jcmfernandes/ob64/tree/v0.
|
|
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:
|
|
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: []
|