new_rfc_2047 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/rfc_2047.rb +97 -0
  3. metadata +71 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f4c99d89c3066d41ad5dc5e362d9ae7203ebe68483eb4e5acec0bd2146d1e64a
4
+ data.tar.gz: f088e8752bbb1b8c6fd06c2c4669896ef9a0236ca093e5be3a9fdc7f613d8a02
5
+ SHA512:
6
+ metadata.gz: 146f547dedb2052365f618818528ff8a7799a1ba708f15c23ed62bd33c57dbe148ea7f971c8aeaff1a7b0fd98dfa6b8c9e67eaca41146045688d6a3679d67502
7
+ data.tar.gz: b02fd3440dd5c9e3ab867f128f524b18c6f72b25b6ec674a9ba9c8f82bfca4bdc79f9c97123d0b1c0f495f6994fe9ad7b78c727cfff991b0479b949e9ccffe04
@@ -0,0 +1,97 @@
1
+ # Copyright (c) 2020 Jian Weihang <tonytonyjan@gmail.com>
2
+ # frozen_string_literal: true
3
+
4
+ module Rfc2047
5
+ TOKEN = /[\041\043-\047\052\053\055\060-\071\101-\132\134\136\137\141-\176]+/.freeze
6
+ ENCODED_TEXT = /[\041-\076\100-\176]*/.freeze
7
+ ENCODED_WORD = /=\?(?<charset>#{TOKEN})\?(?<encoding>[QBqb])\?(?<encoded_text>#{ENCODED_TEXT})\?=/.freeze
8
+ ENCODED_WORD_SEQUENCE = /#{ENCODED_WORD}(?:\s*#{ENCODED_WORD})*/.freeze
9
+
10
+ class << self
11
+ # example:
12
+ #
13
+ # Rfc2047.encode('己所不欲,勿施於人。')
14
+ # # => "=?UTF-8?B?5bex5omA5LiN5qyy77yM5Yu/5pa95pa85Lq644CC?="
15
+ def encode(input, encoding: :B)
16
+ return input if input.ascii_only?
17
+
18
+ case encoding
19
+ when :B
20
+ size = 45
21
+ chunks = Array.new(((input.bytesize + size - 1) / size)) { input.byteslice(_1 * size, size) }
22
+ chunks.map! { "=?#{input.encoding}?B?#{[_1].pack('m0')}?=" }.join(' ')
23
+ when :Q
24
+ [input]
25
+ .pack('M').each_line
26
+ .map { "=?#{input.encoding}?Q?#{_1.chomp!.gsub(' ', '_')}?=" }
27
+ .join(' ')
28
+ else raise ":encoding should be either :B or :Q, got #{encoding}"
29
+ end
30
+ end
31
+
32
+ # example
33
+ #
34
+ # Rfc2047.decode '=?UTF-8?B?5Yu/5Lul5oOh5bCP6ICM54K65LmL77yM5Yu/5Lul5ZaE5bCP6ICM5LiN54K6?= =?UTF-8?B?44CC?='
35
+ # # => "勿以惡小而為之,勿以善小而不為。"
36
+ def decode(input)
37
+ return input unless input.match?(ENCODED_WORD)
38
+
39
+ input.gsub(ENCODED_WORD_SEQUENCE) do |match|
40
+ result = +''
41
+ match.scan(ENCODED_WORD) { result << decode_word($&) }
42
+ if result.encoding == Encoding::UTF_7
43
+ result.replace(
44
+ decode_utf7(result.force_encoding(Encoding::BINARY))
45
+ ).force_encoding(Encoding::UTF_8)
46
+ else
47
+ result.encode!(Encoding::UTF_8)
48
+ end
49
+ result
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def decode_word(input)
56
+ match_data = ENCODED_WORD.match(input)
57
+ raise ArgumentError if match_data.nil?
58
+
59
+ charset, encoding, encoded_text = match_data.captures
60
+ charset = 'CP950' if charset == 'MS950'
61
+
62
+ decoded =
63
+ case encoding
64
+ when 'Q', 'q' then encoded_text.gsub('_', '=20').unpack1('M')
65
+ when 'B', 'b' then encoded_text.unpack1('m')
66
+ end
67
+ found_encoding = find_encoding(charset)
68
+ found_encoding = Encoding::UTF_8 if found_encoding == Encoding::ASCII_8BIT
69
+ decoded.force_encoding(found_encoding)
70
+ end
71
+
72
+ def find_encoding(charset)
73
+ case charset.downcase
74
+ when 'utf-16' then Encoding::UTF_16BE
75
+ when 'utf-32' then Encoding::UTF_32BE
76
+ when 'ks_c_5601-1987' then Encoding::CP949
77
+ when 'shift-jis' then Encoding::Shift_JIS
78
+ when 'gb2312' then Encoding::GB18030
79
+ when 'ms950' then Encoding::CP950
80
+ when '8bit' then Encoding::ASCII_8BIT
81
+ when 'latin2' then Encoding::ISO_8859_2
82
+ else Encoding.find(charset)
83
+ end
84
+ end
85
+
86
+ # from Net::IMAP
87
+ def decode_utf7(s)
88
+ s.gsub(/&([^-]+)?-/n) do
89
+ if Regexp.last_match(1)
90
+ (Regexp.last_match(1).tr(',', '/') + '===').unpack1('m').encode(Encoding::UTF_8, Encoding::UTF_16BE)
91
+ else
92
+ '&'
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: new_rfc_2047
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jian Weihang
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-10-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13'
41
+ description: An implementation of RFC 2047
42
+ email: tonytonyjan@gmail.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - lib/rfc_2047.rb
48
+ homepage: https://github.com/tonytonyjan/rfc_2047
49
+ licenses:
50
+ - MIT
51
+ metadata: {}
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubygems_version: 3.1.2
68
+ signing_key:
69
+ specification_version: 4
70
+ summary: An implementation of RFC 2047
71
+ test_files: []