Feature #471

pack format 'm' based on RFC 4648

Added by Yusuke Endoh over 6 years ago. Updated almost 4 years ago.

[ruby-dev:35904]
Status:Closed
Priority:Low
Assignee:Yusuke Endoh

Description

=begin
遠藤です。

Python の base64.py は RFC 3548 準拠ですが、Ruby の pack の m は現在
RFC 2045 準拠で、ちょっと古いです。
そこで RFC 4648 に準拠するのはどうでしょうか。

私が読んだ感じでは、RFC 2045 -> RFC 4648 の変更点は以下の 3 つのようです。

1) 折り返さない (MUST)

2) デコード対象外の文字 (改行含む) があったら拒絶する (MUST)

3) URL やファイル名安全なバリアントが定義されている (+/ でなく -_ を使う)
(定義されてるだけでサポート必須ではない)

今まで私が pack の m を使うときは [str].pack("m").gsub("\n", "") が
イディオムのようになっていたので、1) はとても便利だと思います。

必須ではないですが、3) も便利そうです。base64.py でも提供されています。
どういう形で提供するかは難しいところですが。

--
Yusuke ENDOH mame@tsg.ne.jp
=end

Associated revisions

Revision 39533
Added by Eric Hodel about 2 years ago

  • lib/rubygems/available_set.rb: Undent for style

  • lib/rubygems/dependency_installer.rb: Pick latest prerelease gem to
    install. Fixes RubyGems bug #468.

  • test/rubygems/test_gem_dependency_installer.rb: Test for the above.

  • lib/rubygems/dependency_installer.rb: Don't display "Done installing
    documentation" if documentation will not be installed.

  • lib/rubygems/rdoc.rb: ditto

  • lib/rubygems/dependency_list.rb: Use Array#concat for Ruby 1.x
    performance.

  • lib/rubygems/installer.rb: Use formatted program name when comparing
    executables. RubyGems pull request #471

  • test/rubygems/test_gem_installer.rb: Test for the above.

  • lib/rubygems/package.rb: Use more explicit feature check to work
    around JRuby bug #552

  • lib/rubygems/ssl_certs/GeoTrust_Global_CA.pem: Added GeoTrust root
    certificate.

  • test/rubygems/test_gem_source_list.rb: Use "example" instead of real
    hostname

Revision 39533
Added by Eric Hodel about 2 years ago

  • lib/rubygems/available_set.rb: Undent for style

  • lib/rubygems/dependency_installer.rb: Pick latest prerelease gem to
    install. Fixes RubyGems bug #468.

  • test/rubygems/test_gem_dependency_installer.rb: Test for the above.

  • lib/rubygems/dependency_installer.rb: Don't display "Done installing
    documentation" if documentation will not be installed.

  • lib/rubygems/rdoc.rb: ditto

  • lib/rubygems/dependency_list.rb: Use Array#concat for Ruby 1.x
    performance.

  • lib/rubygems/installer.rb: Use formatted program name when comparing
    executables. RubyGems pull request #471

  • test/rubygems/test_gem_installer.rb: Test for the above.

  • lib/rubygems/package.rb: Use more explicit feature check to work
    around JRuby bug #552

  • lib/rubygems/ssl_certs/GeoTrust_Global_CA.pem: Added GeoTrust root
    certificate.

  • test/rubygems/test_gem_source_list.rb: Use "example" instead of real
    hostname

History

#1 Updated by Koichi Sasada over 6 years ago

  • Assignee set to Yukihiro Matsumoto

=begin

=end

#2 Updated by Yuki Sonoda over 6 years ago

=begin
base64.rb無き今、改行を許さない形ですとメールを処理するプログラムなどが余りにも可哀想になります。

よって、本件の意義は分からなくはないですが、これを取り込むならば高水準ラッパーとしてのbase64.rbの再導入が必須だという結論になりました。(ko1, akr, yugui at akihabara)
=end

#3 Updated by Yuki Sonoda over 6 years ago

  • Assignee changed from Yukihiro Matsumoto to Yusuke Endoh

=begin
Matzを交えてakrさんと笹田さんと私で話しました。

  • 互換性を損なってまでpack formatの変更は誰も幸せにならない。許容できない
  • Base64クラスを復活させて、新しいRFCに準拠するメソッドを追加するならば反対しない。
  • Base64.rfc4648という名前は酷いので、名前は検討の必要がある。

ということで、遠藤さんよろしくお願いします。
=end

#4 Updated by Yusuke Endoh over 6 years ago

=begin
遠藤です。

2008/09/22 16:24 Yuki Sonoda redmine@ruby-lang.org:

Matzを交えてakrさんと笹田さんと私で話しました。

  • 互換性を損なってまでpack formatの変更は誰も幸せにならない。許容できない
  • Base64クラスを復活させて、新しいRFCに準拠するメソッドを追加するならば反対しない。
  • Base64.rfc4648という名前は酷いので、名前は検討の必要がある。

ということで、遠藤さんよろしくお願いします。

今気がついたんですが、RFC 3548 準拠をかたる base64.py はあろうことか
改行を無視しますね。バグだと思いますが。

ちなみに RFC 4648 には以下のような記述があります。

(12. Security Considerations)

If non-alphabet characters are ignored, instead of causing rejection
of the entire encoding (as recommended), a covert channel that can be
used to "leak" information is made possible.  The ignored characters
could also be used for other nefarious purposes, such as to avoid a
string equality comparison or to trigger implementation bugs.  The
implications of ignoring non-alphabet characters should be understood
in applications that do not follow the recommended practice.

実際に security issue につなげるのは難しそうですが、現状の仕様を残す
なら一応ドキュメントに一言を書いておく方がいいかもしれません。

それはともかく。
古い RFC は組み込みで最新の RFC はライブラリというのは悲しいので、
m0 の時だけ RFC 4648 準拠というのはどうでしょうか。

つまり pack("m0") は改行を出力せず、unpack("m0") は改行があったら
例外を投げる、というように。

念のため付け加えると、現状でも m8 とかすると出力幅が 8 になります。

--
Yusuke ENDOH mame@tsg.ne.jp

=end

#5 Updated by Yusuke Endoh over 6 years ago

=begin
遠藤です。

2008/09/24 1:57 Tanaka Akira akr@fsij.org:

In article e0b1e5700809220338g5f3b5627p95e94744d5c10505@mail.gmail.com,
"Yusuke ENDOH" mame@tsg.ne.jp writes:

つまり pack("m0") は改行を出力せず、unpack("m0") は改行があったら
例外を投げる、というように。

なるほど。それであれば、base64.rb を復活させなくても互換性と
RFC 4648 の挙動の実現の両方を実現できそうですね。

元は中田さんのアイデアだった気がします。

ただ、 に述べられている、「URL やファイル名
安全なバリアント」はどうするんでしょうか。

そっちも提供することを考えるなら、pack/unpack での整数指定だ
けで区別するのはやはり難しくて、base64.rb が欲しくなったりし
ないでしょうか。

うーん。base64.rb には decode_b とか b64encode とか禍々しい
メソッドがあるので、個人的にはあまり復活させたくないところ
です。

とりあえず m0 の実装と、base64.rb を復活させて以下のメソッドを
追加したパッチを書いてみました。

  • Base64.standard_encode64 : RFC 4648 準拠 (のはず) のエンコード
  • Base64.standard_decode64 : RFC 4648 準拠 (のはず) のデコード
  • Base64.urlsafe_encode64 : URL セーフなバリアントのエンコード
  • Base64.urlsafe_decode64 : URL セーフなバリアントのデコード

standard_ とかの prefix は base64.py を参考にしました。
decode_b が Kconv を使っていますが、M17N はよくわからないので
そのままにしてあります。

Index: pack.c
===================================================================
--- pack.c (revision 19494)
+++ pack.c (working copy)
@@ -362,7 +362,7 @@
#endif
static const char toofew[] = "too few arguments";

-static void encodes(VALUE,const char*,long,int);
+static void encodes(VALUE,const char*,long,int,int);
static void qpencode(VALUE,VALUE,long);

static unsigned long utf8_to_uv(const char*,long*);
@@ -887,6 +887,11 @@
ptr = RSTRING_PTR(from);
plen = RSTRING_LEN(from);

  • if (len == 0) {
  • encodes(res, ptr, plen, type, 0);
  • ptr += plen;
  • break;
  • } if (len <= 2) len = 45; else @@ -898,7 +903,7 @@ todo = len; else todo = plen;
  • encodes(res, ptr, todo, type);
  • encodes(res, ptr, todo, type, 1); plen -= todo; ptr += todo; } @@ -1007,7 +1012,7 @@ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static void
-encodes(VALUE str, const char *s, long len, int type)
+encodes(VALUE str, const char *s, long len, int type, int tail_lf)
{
char buff[4096];
long i = 0;
@@ -1048,7 +1053,7 @@
buff[i++] = padding;
buff[i++] = padding;
}
- buff[i++] = '\n';
+ if (tail_lf) buff[i++] = '\n';
rb_str_buf_cat(str, buff, i);
}

@@ -1793,7 +1798,7 @@
{
VALUE buf = infected_str_new(0, (send - s)*3/4, str);
char *ptr = RSTRING_PTR(buf);
- int a = -1,b = -1,c = 0,d;
+ int a = -1,b = -1,c = 0,d = 0;
static signed char b64_xtable[256];

    if (b64_xtable['/'] <= 0) {

@@ -1806,32 +1811,64 @@
b64_xtable[(unsigned char)b64_table[i]] = i;
}
}
- while (s < send) {
- a = b = c = d = -1;
- while ((a = b64_xtable[(unsigned char)s]) == -1 && s < send) {s++;}
- if (s >= send) break;
- s++;
- while ((b = b64_xtable[(unsigned char)s]) == -1 && s < send) {s++;}
- if (s >= send) break;
- s++;
- while ((c = b64_xtable[(unsigned char)s]) == -1 && s < send)
{if (*s == '=') break; s++;}
- if (*s == '=' || s >= send) break;
- s++;
- while ((d = b64_xtable[(unsigned char)*s]) == -1 && s < send)
{if (*s == '=') break; s++;}
- if (*s == '=' || s >= send) break;
- s++;
- *ptr++ = a << 2 | b >> 4;
- *ptr++ = b << 4 | c >> 2;
- *ptr++ = c << 6 | d;
- }
- if (a != -1 && b != -1) {
- if (c == -1 && *s == '=')
+ if (len == 0) {
+ while (s < send) {
+ a = b = c = d = -1;
+ a = b64_xtable[(unsigned char)
s++];
+ if (s >= send || a == -1) rb_raise(rb_eArgError, "invalid base64");
+ b = b64_xtable[(unsigned char)*s++];
+ if (s >= send || b == -1) rb_raise(rb_eArgError, "invalid base64");
+ if (*s == '=') {
+ if (s + 2 == send && *(s + 1) == '=') break;
+ rb_raise(rb_eArgError, "invalid base64");
+ }
+ c = b64_xtable[(unsigned char)
s++];
+ if (s >= send || c == -1) rb_raise(rb_eArgError, "invalid base64");
+ if (s + 1 == send && *s == '=') break;
+ d = b64_xtable[(unsigned char)*s++];
+ if (d == -1) rb_raise(rb_eArgError, "invalid base64");
*ptr++ = a << 2 | b >> 4;
- else if (c != -1 && *s == '=') {
+ *ptr++ = b << 4 | c >> 2;
+ *ptr++ = c << 6 | d;
+ }
+ if (c == -1) {
*ptr++ = a << 2 | b >> 4;
+ if (b & 0xf) rb_raise(rb_eArgError, "invalid base64");
+ }
+ else if (d == -1) {
+ *ptr++ = a << 2 | b >> 4;
*ptr++ = b << 4 | c >> 2;
+ if (c & 0x3) rb_raise(rb_eArgError, "invalid base64");
}
}
+ else {
+ while (s < send) {
+ a = b = c = d = -1;
+ while ((a = b64_xtable[(unsigned char)
s]) == -1 && s < send) {s++;}
+ if (s >= send) break;
+ s++;
+ while ((b = b64_xtable[(unsigned char)*s]) == -1 && s < send) {s++;}
+ if (s >= send) break;
+ s++;
+ while ((c = b64_xtable[(unsigned char)*s]) == -1 && s < send) {if
(*s == '=') break; s++;}
+ if (*s == '=' || s >= send) break;
+ s++;
+ while ((d = b64_xtable[(unsigned char)*s]) == -1 && s < send) {if
(*s == '=') break; s++;}
+ if (*s == '=' || s >= send) break;
+ s++;
+ *ptr++ = a << 2 | b >> 4;
+ *ptr++ = b << 4 | c >> 2;
+ *ptr++ = c << 6 | d;
+ }
+ if (a != -1 && b != -1) {
+ if (c == -1 && *s == '=')
+ *ptr++ = a << 2 | b >> 4;
+ else if (c != -1 && *s == '=') {
+ *ptr++ = a << 2 | b >> 4;
+ *ptr++ = b << 4 | c >> 2;
+ }
+ }
+ }
rb_str_set_len(buf, ptr - RSTRING_PTR(buf));
UNPACK_PUSH(buf);
}
Index: lib/base64.rb
===================================================================
--- lib/base64.rb (revision 19466)
+++ lib/base64.rb (working copy)
@@ -40,6 +40,22 @@
module Base64
module_function

  • def standard_decode64(str)
  • str.unpack("m0")[0]
  • end +
  • def standard_encode64(bin)
  • [bin].pack("m0")
  • end +
  • def urlsafe_decode64(str)
  • standard_decode64(str.tr("-_", "+/"))
  • end +
  • def urlsafe_encode64(str)
  • standard_encode64(bin).tr("+/", "-_")
  • end
    +

    Returns the Base64-decoded version of +str+.

    #

    require 'base64'

    Index: test/ruby/test_pack.rb

    --- test/ruby/test_pack.rb (revision 19494)
    +++ test/ruby/test_pack.rb (working copy)
    @@ -379,6 +379,36 @@
    assert_equal(["\377\377\377"], "////\n".unpack("m"))
    end

  • def test_pack_unpack_m0

  • assert_equal("", [""].pack("m0"))

  • assert_equal("AA==", ["\0"].pack("m0"))

  • assert_equal("AAA=", ["\0\0"].pack("m0"))

  • assert_equal("AAAA", ["\0\0\0"].pack("m0"))

  • assert_equal("/w==", ["\377"].pack("m0"))

  • assert_equal("//8=", ["\377\377"].pack("m0"))

  • assert_equal("////", ["\377\377\377"].pack("m0"))
    +

  • assert_equal([""], "".unpack("m0"))

  • assert_equal(["\0"], "AA==".unpack("m0"))

  • assert_equal(["\0\0"], "AAA=".unpack("m0"))

  • assert_equal(["\0\0\0"], "AAAA".unpack("m0"))

  • assert_equal(["\377"], "/w==".unpack("m0"))

  • assert_equal(["\377\377"], "//8=".unpack("m0"))

  • assert_equal(["\377\377\377"], "////".unpack("m0"))
    +

  • assert_raise(ArgumentError) { "".unpack("m0") }

  • assert_raise(ArgumentError) { "A".unpack("m0") }

  • assert_raise(ArgumentError) { "A".unpack("m0") }

  • assert_raise(ArgumentError) { "AA".unpack("m0") }

  • assert_raise(ArgumentError) { "AA=".unpack("m0") }

  • assert_raise(ArgumentError) { "AA===".unpack("m0") }

  • assert_raise(ArgumentError) { "AA=x".unpack("m0") }

  • assert_raise(ArgumentError) { "AAA".unpack("m0") }

  • assert_raise(ArgumentError) { "AAA".unpack("m0") }

  • assert_raise(ArgumentError) { "AB==".unpack("m0") }

  • assert_raise(ArgumentError) { "AAB=".unpack("m0") }

  • end
    +
    def test_pack_unpack_M
    assert_equal("a b c\td =\n\ne=\n", ["a b c\td \ne"].pack("M"))
    assert_equal(["a b c\td \ne"], "a b c\td =\n\ne=\n".unpack("M"))

--
Yusuke ENDOH mame@tsg.ne.jp

=end

#6 Updated by Yui NARUSE over 6 years ago

=begin
成瀬です。

Yusuke ENDOH wrote:

decode_b が Kconv を使っていますが、M17N はよくわからないので
そのままにしてあります。

日本語以外に対応させる修正が必要でしょうねぇ。

しかし、これ、そもそも BASE64 の decode じゃなくて、
MIME の decode ですよね。
ここにいるべきメソッドではない気がします。

mime.rb とかを新設するならばありなんでしょうが。

=end

#7 Updated by Yusuke Endoh over 6 years ago

=begin
遠藤です。

2008/09/24 7:13 Tanaka Akira akr@fsij.org:

In article e0b1e5700809231144n376fd4eencfe06c49ed66665e@mail.gmail.com,
"Yusuke ENDOH" mame@tsg.ne.jp writes:

うーん。base64.rb には decode_b とか b64encode とか禍々しい
メソッドがあるので、個人的にはあまり復活させたくないところ
です。

そのへんは消しちゃっていいと思います。

そのへんと deprecated なコードを消しちゃったらだいぶすっきりしました。

module Base64 は以下の 6 つの module_function を持ちます。

  • Base64.encode64 : RFC 2045 準拠なエンコード (改行を入れる)
  • Base64.decode64 : RFC 2045 準拠なデコード (改行などを無視する)
  • Base64.strict_encode64 : RFC 4648 準拠なエンコード (改行を入れない)
  • Base64.strict_decode64 : RFC 4648 準拠なデコード (改行や = の不足は例外)
  • Base64.urlsafe_encode64 : URL セーフなバリアントのエンコード
  • Base64.urlsafe_decode64 : URL セーフなバリアントのデコード

standard_encode64 は RFC を意識しすぎな名前だなぁと思ったので、
一般ユーザにわかりやすそうな strict にしてみました。
standard の方が良ければ直します。

これでよければコミットしたいと思いますが、いかがでしょうか。

Index: pack.c
===================================================================
--- pack.c (revision 19526)
+++ pack.c (working copy)
@@ -362,7 +362,7 @@
#endif
static const char toofew[] = "too few arguments";

-static void encodes(VALUE,const char*,long,int);
+static void encodes(VALUE,const char*,long,int,int);
static void qpencode(VALUE,VALUE,long);

static unsigned long utf8_to_uv(const char*,long*);
@@ -887,6 +887,11 @@
ptr = RSTRING_PTR(from);
plen = RSTRING_LEN(from);

  • if (len == 0) {
  • encodes(res, ptr, plen, type, 0);
  • ptr += plen;
  • break;
  • } if (len <= 2) len = 45; else @@ -898,7 +903,7 @@ todo = len; else todo = plen;
  • encodes(res, ptr, todo, type);
  • encodes(res, ptr, todo, type, 1); plen -= todo; ptr += todo; } @@ -1007,7 +1012,7 @@ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static void
-encodes(VALUE str, const char *s, long len, int type)
+encodes(VALUE str, const char *s, long len, int type, int tail_lf)
{
char buff[4096];
long i = 0;
@@ -1048,7 +1053,7 @@
buff[i++] = padding;
buff[i++] = padding;
}
- buff[i++] = '\n';
+ if (tail_lf) buff[i++] = '\n';
rb_str_buf_cat(str, buff, i);
}

@@ -1793,7 +1798,7 @@
{
VALUE buf = infected_str_new(0, (send - s)*3/4, str);
char *ptr = RSTRING_PTR(buf);
- int a = -1,b = -1,c = 0,d;
+ int a = -1,b = -1,c = 0,d = 0;
static signed char b64_xtable[256];

    if (b64_xtable['/'] <= 0) {

@@ -1806,32 +1811,64 @@
b64_xtable[(unsigned char)b64_table[i]] = i;
}
}
- while (s < send) {
- a = b = c = d = -1;
- while ((a = b64_xtable[(unsigned char)*s]) == -1 && s < send) {s++;}
- if (s >= send) break;
- s++;
- while ((b = b64_xtable[(unsigned char)*s]) == -1 && s < send) {s++;}
- if (s >= send) break;
- s++;
- while ((c = b64_xtable[(unsigned char)*s]) == -1 && s < send)
{if (*s == '=') break; s++;}
- if (*s == '=' || s >= send) break;
- s++;
- while ((d = b64_xtable[(unsigned char)*s]) == -1 && s < send)
{if (*s == '=') break; s++;}
- if (*s == '=' || s >= send) break;
- s++;
- *ptr++ = a << 2 | b >> 4;
- *ptr++ = b << 4 | c >> 2;
- *ptr++ = c << 6 | d;
- }
- if (a != -1 && b != -1) {
- if (c == -1 && *s == '=')
+ if (len == 0) {
+ while (s < send) {
+ a = b = c = d = -1;
+ a = b64_xtable[(unsigned char)*s++];
+ if (s >= send || a == -1) rb_raise(rb_eArgError, "invalid base64");
+ b = b64_xtable[(unsigned char)*s++];
+ if (s >= send || b == -1) rb_raise(rb_eArgError, "invalid base64");
+ if (*s == '=') {
+ if (s + 2 == send && *(s + 1) == '=') break;
+ rb_raise(rb_eArgError, "invalid base64");
+ }
+ c = b64_xtable[(unsigned char)*s++];
+ if (s >= send || c == -1) rb_raise(rb_eArgError, "invalid base64");
+ if (s + 1 == send && *s == '=') break;
+ d = b64_xtable[(unsigned char)*s++];
+ if (d == -1) rb_raise(rb_eArgError, "invalid base64");
*ptr++ = a << 2 | b >> 4;
- else if (c != -1 && *s == '=') {
+ *ptr++ = b << 4 | c >> 2;
+ *ptr++ = c << 6 | d;
+ }
+ if (c == -1) {
*ptr++ = a << 2 | b >> 4;
+ if (b & 0xf) rb_raise(rb_eArgError, "invalid base64");
+ }
+ else if (d == -1) {
+ *ptr++ = a << 2 | b >> 4;
*ptr++ = b << 4 | c >> 2;
+ if (c & 0x3) rb_raise(rb_eArgError, "invalid base64");
}
}
+ else {
+ while (s < send) {
+ a = b = c = d = -1;
+ while ((a = b64_xtable[(unsigned char)*s]) == -1 && s < send) {s++;}
+ if (s >= send) break;
+ s++;
+ while ((b = b64_xtable[(unsigned char)*s]) == -1 && s < send) {s++;}
+ if (s >= send) break;
+ s++;
+ while ((c = b64_xtable[(unsigned char)*s]) == -1 && s < send) {if
(*s == '=') break; s++;}
+ if (*s == '=' || s >= send) break;
+ s++;
+ while ((d = b64_xtable[(unsigned char)*s]) == -1 && s < send) {if
(*s == '=') break; s++;}
+ if (*s == '=' || s >= send) break;
+ s++;
+ *ptr++ = a << 2 | b >> 4;
+ *ptr++ = b << 4 | c >> 2;
+ *ptr++ = c << 6 | d;
+ }
+ if (a != -1 && b != -1) {
+ if (c == -1 && *s == '=')
+ *ptr++ = a << 2 | b >> 4;
+ else if (c != -1 && *s == '=') {
+ *ptr++ = a << 2 | b >> 4;
+ *ptr++ = b << 4 | c >> 2;
+ }
+ }
+ }
rb_str_set_len(buf, ptr - RSTRING_PTR(buf));
UNPACK_PUSH(buf);
}
Index: lib/base64.rb
===================================================================
--- lib/base64.rb (revision 0)
+++ lib/base64.rb (revision 0)
@@ -0,0 +1,91 @@
+#
+# = base64.rb: methods for base64-encoding and -decoding stings
+#
+
+# The Base64 module provides for the encoding (#encode64, #strict_encode64,
+# #urlsafe_encode64) and decoding (#decode64, #strict_decode64,
+# #urlsafe_decode64) of binary data using a Base64 representation.
+#
+# == Example
+#
+# A simple encoding and decoding.
+#
+# require "base64"
+#
+# enc = Base64.encode64('Send reinforcements')
+# # -> "U2VuZCByZWluZm9yY2VtZW50cw==\n"
+# plain = Base64.decode64(enc)
+# # -> "Send reinforcements"
+#
+# The purpose of using base64 to encode data is that it translates any
+# binary data into purely printable characters.
+
+module Base64
+ module_function
+
+ # Returns the Base64-encoded version of +bin+.
+ # This method complies with RFC 2045.
+ # Line feeds are added to every 60 encoded charactors.
+ #
+ # require 'base64'
+ # Base64.encode64("Now is the time for all good coders\nto learn Ruby")
+ #
+ # Generates:
+ #
+ # Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBjb2RlcnMKdG8gbGVhcm4g
+ # UnVieQ==
+ def encode64(bin)
+ [bin].pack("m")
+ end
+
+ # Returns the Base64-decoded version of +str+.
+ # This method complies with RFC 2045.
+ # Characters outside the base alphabet are ignored.
+ #
+ # require 'base64'
+ # str = 'VGhpcyBpcyBsaW5lIG9uZQpUaGlzIG' +
+ # 'lzIGxpbmUgdHdvClRoaXMgaXMgbGlu' +
+ # 'ZSB0aHJlZQpBbmQgc28gb24uLi4K'
+ # puts Base64.decode64(str)
+ #
+ # Generates:
+ #
+ # This is line one
+ # This is line two
+ # This is line three
+ # And so on...
+ def decode64(str)
+ str.unpack("m").first
+ end
+
+ # Returns the Base64-encoded version of +bin+.
+ # This method complies with RFC 4648.
+ # No line feeds are added.
+ def strict_encode64(bin)
+ [bin].pack("m0")
+ end
+
+ # Returns the Base64-decoded version of +str+.
+ # This method complies with RFC 4648.
+ # ArgumentError is raised if +str+ is incorrectly padded or contains
+ # non-alphabet characters. Note that CR or LF are also rejected.
+ def strict_decode64(str)
+ str.unpack("m0").first
+ end
+
+ # Returns the Base64-encoded version of +bin+.
+ # This method complies with Base 64 Encoding with URL and Filename Safe
+ # Alphabet'' in RFC 4648.
+ # The alphabet uses '-' instead of '+' and '_' instead of '/'.
+ def urlsafe_encode64(bin)
+ strict_encode64(bin).tr("+/", "-_")
+ end
+
+ # Returns the Base64-decoded version of +str+.
+ # This method complies with
Base 64 Encoding with URL and Filename Safe
+ # Alphabet'' in RFC 4648.
+ # The alphabet uses '-' instead of '+' and '' instead of '/'.
+ def urlsafe_decode64(str)
+ strict_decode64(str.tr("-
", "+/"))
+ end
+end
Index: test/ruby/test_pack.rb
===================================================================
--- test/ruby/test_pack.rb (revision 19526)
+++ test/ruby/test_pack.rb (working copy)
@@ -379,6 +379,36 @@
assert_equal(["\377\377\377"], "////\n".unpack("m"))
end

  • def test_pack_unpack_m0
  • assert_equal("", [""].pack("m0"))
  • assert_equal("AA==", ["\0"].pack("m0"))
  • assert_equal("AAA=", ["\0\0"].pack("m0"))
  • assert_equal("AAAA", ["\0\0\0"].pack("m0"))
  • assert_equal("/w==", ["\377"].pack("m0"))
  • assert_equal("//8=", ["\377\377"].pack("m0"))
  • assert_equal("////", ["\377\377\377"].pack("m0")) +
  • assert_equal([""], "".unpack("m0"))
  • assert_equal(["\0"], "AA==".unpack("m0"))
  • assert_equal(["\0\0"], "AAA=".unpack("m0"))
  • assert_equal(["\0\0\0"], "AAAA".unpack("m0"))
  • assert_equal(["\377"], "/w==".unpack("m0"))
  • assert_equal(["\377\377"], "//8=".unpack("m0"))
  • assert_equal(["\377\377\377"], "////".unpack("m0")) +
  • assert_raise(ArgumentError) { "".unpack("m0") }
  • assert_raise(ArgumentError) { "A".unpack("m0") }
  • assert_raise(ArgumentError) { "A".unpack("m0") }
  • assert_raise(ArgumentError) { "AA".unpack("m0") }
  • assert_raise(ArgumentError) { "AA=".unpack("m0") }
  • assert_raise(ArgumentError) { "AA===".unpack("m0") }
  • assert_raise(ArgumentError) { "AA=x".unpack("m0") }
  • assert_raise(ArgumentError) { "AAA".unpack("m0") }
  • assert_raise(ArgumentError) { "AAA".unpack("m0") }
  • assert_raise(ArgumentError) { "AB==".unpack("m0") }
  • assert_raise(ArgumentError) { "AAB=".unpack("m0") }
  • end + def test_pack_unpack_M assert_equal("a b c\td =\n\ne=\n", ["a b c\td \ne"].pack("M")) assert_equal(["a b c\td \ne"], "a b c\td =\n\ne=\n".unpack("M")) Index: test/base64/test_base64.rb =================================================================== --- test/base64/test_base64.rb (revision 0) +++ test/base64/test_base64.rb (revision 0) @@ -0,0 +1,99 @@ +require "test/unit" +require "base64" + +class TestBase64 < Test::Unit::TestCase
  • def test_sample
  • assert_equal("U2VuZCByZWluZm9yY2VtZW50cw==\n", Base64.encode64('Send reinforcements'))
  • assert_equal('Send reinforcements', Base64.decode64("U2VuZCByZWluZm9yY2VtZW50cw==\n"))
  • assert_equal(
  • "Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBjb2RlcnMKdG8gbGVhcm4g\nUnVieQ==\n",
  • Base64.encode64("Now is the time for all good coders\nto learn Ruby"))
  • assert_equal(
  • "Now is the time for all good coders\nto learn Ruby",
  • Base64.decode64("Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBjb2RlcnMKdG8gbGVhcm4g\nUnVieQ==\n"))
  • assert_equal(
  • "VGhpcyBpcyBsaW5lIG9uZQpUaGlzIGlzIGxpbmUgdHdvClRoaXMgaXMgbGlu\nZSB0aHJlZQpBbmQgc28gb24uLi4K\n",
  • Base64.encode64("This is line one\nThis is line two\nThis is line three\nAnd so on...\n"))
  • assert_equal(
  • "This is line one\nThis is line two\nThis is line three\nAnd so on...\n",
  • Base64.decode64("VGhpcyBpcyBsaW5lIG9uZQpUaGlzIGlzIGxpbmUgdHdvClRoaXMgaXMgbGluZSB0aHJlZQpBbmQgc28gb24uLi4K"))
  • end +
  • def test_encode64
  • assert_equal("", Base64.encode64(""))
  • assert_equal("AA==\n", Base64.encode64("\0"))
  • assert_equal("AAA=\n", Base64.encode64("\0\0"))
  • assert_equal("AAAA\n", Base64.encode64("\0\0\0"))
  • assert_equal("/w==\n", Base64.encode64("\377"))
  • assert_equal("//8=\n", Base64.encode64("\377\377"))
  • assert_equal("////\n", Base64.encode64("\377\377\377"))
  • assert_equal("/+8=\n", Base64.encode64("\xff\xef"))
  • end +
  • def test_decode64
  • assert_equal("", Base64.decode64(""))
  • assert_equal("\0", Base64.decode64("AA==\n"))
  • assert_equal("\0\0", Base64.decode64("AAA=\n"))
  • assert_equal("\0\0\0", Base64.decode64("AAAA\n"))
  • assert_equal("\377", Base64.decode64("/w==\n"))
  • assert_equal("\377\377", Base64.decode64("//8=\n"))
  • assert_equal("\377\377\377", Base64.decode64("////\n"))
  • assert_equal("\xff\xef", Base64.decode64("/+8=\n"))
  • end +
  • def test_strict_encode64
  • assert_equal("", Base64.strict_encode64(""))
  • assert_equal("AA==", Base64.strict_encode64("\0"))
  • assert_equal("AAA=", Base64.strict_encode64("\0\0"))
  • assert_equal("AAAA", Base64.strict_encode64("\0\0\0"))
  • assert_equal("/w==", Base64.strict_encode64("\377"))
  • assert_equal("//8=", Base64.strict_encode64("\377\377"))
  • assert_equal("////", Base64.strict_encode64("\377\377\377"))
  • assert_equal("/+8=", Base64.strict_encode64("\xff\xef"))
  • end +
  • def test_strict_decode64
  • assert_equal("", Base64.strict_decode64(""))
  • assert_equal("\0", Base64.strict_decode64("AA=="))
  • assert_equal("\0\0", Base64.strict_decode64("AAA="))
  • assert_equal("\0\0\0", Base64.strict_decode64("AAAA"))
  • assert_equal("\377", Base64.strict_decode64("/w=="))
  • assert_equal("\377\377", Base64.strict_decode64("//8="))
  • assert_equal("\377\377\377", Base64.strict_decode64("////"))
  • assert_equal("\xff\xef", Base64.strict_decode64("/+8=")) +
  • assert_raise(ArgumentError) { Base64.strict_decode64("") }
  • assert_raise(ArgumentError) { Base64.strict_decode64("A") }
  • assert_raise(ArgumentError) { Base64.strict_decode64("A") }
  • assert_raise(ArgumentError) { Base64.strict_decode64("AA") }
  • assert_raise(ArgumentError) { Base64.strict_decode64("AA=") }
  • assert_raise(ArgumentError) { Base64.strict_decode64("AA===") }
  • assert_raise(ArgumentError) { Base64.strict_decode64("AA=x") }
  • assert_raise(ArgumentError) { Base64.strict_decode64("AAA") }
  • assert_raise(ArgumentError) { Base64.strict_decode64("AAA") }
  • assert_raise(ArgumentError) { Base64.strict_decode64("AB==") }
  • assert_raise(ArgumentError) { Base64.strict_decode64("AAB=") }
  • end +
  • def test_urlsafe_encode64
  • assert_equal("", Base64.urlsafe_encode64(""))
  • assert_equal("AA==", Base64.urlsafe_encode64("\0"))
  • assert_equal("AAA=", Base64.urlsafe_encode64("\0\0"))
  • assert_equal("AAAA", Base64.urlsafe_encode64("\0\0\0"))
  • assert_equal("w==", Base64.urlsafeencode64("\377"))
  • assert_equal("__8=", Base64.urlsafe_encode64("\377\377"))
  • assert_equal("____", Base64.urlsafe_encode64("\377\377\377"))
  • assert_equal("_-8=", Base64.urlsafe_encode64("\xff\xef"))
  • end +
  • def test_urlsafe_decode64
  • assert_equal("", Base64.urlsafe_decode64(""))
  • assert_equal("\0", Base64.urlsafe_decode64("AA=="))
  • assert_equal("\0\0", Base64.urlsafe_decode64("AAA="))
  • assert_equal("\0\0\0", Base64.urlsafe_decode64("AAAA"))
  • assert_equal("\377", Base64.urlsafe_decode64("_w=="))
  • assert_equal("\377\377", Base64.urlsafe_decode64("__8="))
  • assert_equal("\377\377\377", Base64.urlsafe_decode64("____"))
  • assert_equal("\xff\xef", Base64.urlsafe_decode64("_+8="))
  • end +end

--
Yusuke ENDOH mame@tsg.ne.jp

=end

#8 Updated by Yusuke Endoh over 6 years ago

=begin
遠藤です。

2008/09/25 0:40 Tanaka Akira akr@fsij.org:

In article e0b1e5700809240654u61c92ab3o85ad5a115a212b70@mail.gmail.com,
"Yusuke ENDOH" mame@tsg.ne.jp writes:

そのへんと deprecated なコードを消しちゃったらだいぶすっきりしました。

pack/unpack のドキュメントには手をつけてないんですね。

忘れてました。他のドキュメントにあわせて短く書くのが難しいですが、
こんな感じでどうでしょう。

@@ -414,7 +414,8 @@
* L | Unsigned long
* l | Long
* M | Quoted printable, MIME encoding (see RFC2045)
- * m | Base64 encoded string
+ * m | Base64 encoded string (see RFC 2045, count is width)
+ * | (no line feed are added if count is 0, see RFC 4648)
* N | Long, network (big-endian) byte order
* n | Short, network (big-endian) byte-order
* P | Pointer to a structure (fixed-length string)
@@ -1242,7 +1248,8 @@
* -------+---------+-----------------------------------------
* M | String | quoted-printable
* -------+---------+-----------------------------------------
- * m | String | base64-encoded
+ * m | String | base64-encoded (RFC 2045) (default)
+ * | | base64-encoded (RFC 4648) if followed by 0
* -------+---------+-----------------------------------------
* N | Integer | treat four characters as an unsigned
* | | long in network byte order

--
Yusuke ENDOH mame@tsg.ne.jp

=end

#9 Updated by Yukihiro Matsumoto over 6 years ago

=begin
まつもと ゆきひろです

In message "Re: Re: [Feature #471] pack format 'm' based on RFC 4648"
on Thu, 25 Sep 2008 01:14:00 +0900, "Yusuke ENDOH" mame@tsg.ne.jp writes:

|2008/09/25 0:40 Tanaka Akira akr@fsij.org:
|> In article e0b1e5700809240654u61c92ab3o85ad5a115a212b70@mail.gmail.com,
|> "Yusuke ENDOH" mame@tsg.ne.jp writes:
|>
|>> そのへんと deprecated なコードを消しちゃったらだいぶすっきりしました。
|>
|> pack/unpack のドキュメントには手をつけてないんですね。
|
|忘れてました。他のドキュメントにあわせて短く書くのが難しいですが、
|こんな感じでどうでしょう。

コミットしてください。新base64.rbも含めて。

=end

#10 Updated by Yusuke Endoh over 6 years ago

=begin
遠藤です。

2008/09/25 1:19 Yukihiro Matsumoto matz@ruby-lang.org:

In message "Re: Re: [Feature #471] pack format 'm' based on RFC 4648"
on Thu, 25 Sep 2008 01:14:00 +0900, "Yusuke ENDOH" mame@tsg.ne.jp writes:

|2008/09/25 0:40 Tanaka Akira akr@fsij.org:
|> In article e0b1e5700809240654u61c92ab3o85ad5a115a212b70@mail.gmail.com,
|> "Yusuke ENDOH" mame@tsg.ne.jp writes:
|>
|>> そのへんと deprecated なコードを消しちゃったらだいぶすっきりしました。
|>
|> pack/unpack のドキュメントには手をつけてないんですね。
|
|忘れてました。他のドキュメントにあわせて短く書くのが難しいですが、
|こんな感じでどうでしょう。

コミットしてください。新base64.rbも含めて。

ありがとうございます。コミットしました。

--
Yusuke ENDOH mame@tsg.ne.jp

=end

#11 Updated by Yuki Sonoda over 6 years ago

  • Status changed from Open to Closed
  • Target version set to 1.9.0-5

=begin

=end

Also available in: Atom PDF