遠藤です。 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 の挙動の実現の両方を実現できそうですね。 元は中田さんのアイデアだった気がします。 > ただ、[ruby-dev:35904] に述べられている、「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>