sup 1.0 → 1.2
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/checks.yml +58 -0
- data/.rubocop.yml +5 -0
- data/CONTRIBUTORS +5 -2
- data/Gemfile +5 -1
- data/History.txt +33 -0
- data/Manifest.txt +171 -0
- data/README.md +9 -4
- data/Rakefile +40 -1
- data/bin/sup-add +4 -8
- data/bin/sup-sync-back-maildir +1 -1
- data/contrib/nix/Gemfile +22 -0
- data/contrib/nix/Gemfile.lock +80 -0
- data/contrib/nix/README +7 -0
- data/contrib/nix/gem-install-shell.nix +12 -0
- data/contrib/nix/gemset.nix +339 -0
- data/contrib/nix/ruby2.4-Gemfile.lock +81 -0
- data/contrib/nix/ruby2.4-gemset.nix +309 -0
- data/contrib/nix/ruby2.4-shell.nix +30 -0
- data/contrib/nix/ruby2.5-Gemfile.lock +81 -0
- data/contrib/nix/ruby2.5-gemset.nix +309 -0
- data/contrib/nix/ruby2.5-shell.nix +30 -0
- data/contrib/nix/ruby2.6-Gemfile.lock +83 -0
- data/contrib/nix/ruby2.6-gemset.nix +319 -0
- data/contrib/nix/ruby2.6-shell.nix +30 -0
- data/contrib/nix/ruby2.7-shell.nix +23 -0
- data/contrib/nix/ruby3.0-shell.nix +23 -0
- data/contrib/nix/ruby3.1-shell.nix +23 -0
- data/contrib/nix/ruby3.2-shell.nix +23 -0
- data/contrib/nix/ruby3.3-shell.nix +23 -0
- data/contrib/nix/test-all-rubies.sh +6 -0
- data/doc/Hooks.txt +1 -1
- data/ext/mkrf_conf_xapian.rb +12 -6
- data/lib/sup/colormap.rb +1 -1
- data/lib/sup/crypto.rb +1 -1
- data/lib/sup/hook.rb +1 -1
- data/lib/sup/index.rb +4 -4
- data/lib/sup/keymap.rb +1 -1
- data/lib/sup/maildir.rb +5 -5
- data/lib/sup/mbox.rb +5 -5
- data/lib/sup/message.rb +8 -7
- data/lib/sup/message_chunks.rb +27 -19
- data/lib/sup/modes/completion_mode.rb +0 -1
- data/lib/sup/modes/console_mode.rb +1 -1
- data/lib/sup/modes/file_browser_mode.rb +2 -2
- data/lib/sup/modes/label_list_mode.rb +1 -1
- data/lib/sup/modes/search_list_mode.rb +2 -2
- data/lib/sup/modes/thread_view_mode.rb +1 -2
- data/lib/sup/rfc2047.rb +21 -6
- data/lib/sup/source.rb +8 -2
- data/lib/sup/textfield.rb +0 -1
- data/lib/sup/thread.rb +20 -21
- data/lib/sup/util.rb +31 -53
- data/lib/sup/version.rb +1 -1
- data/lib/sup.rb +12 -8
- data/man/sup-add.1 +39 -39
- data/man/sup-config.1 +31 -27
- data/man/sup-dump.1 +34 -35
- data/man/sup-import-dump.1 +36 -32
- data/man/sup-psych-ify-config-files.1 +29 -25
- data/man/sup-recover-sources.1 +32 -28
- data/man/sup-sync-back-maildir.1 +34 -30
- data/man/sup-sync.1 +40 -36
- data/man/sup-tweak-labels.1 +36 -32
- data/man/sup.1 +41 -37
- data/shell.nix +1 -0
- data/sup.gemspec +6 -4
- data/test/dummy_source.rb +21 -15
- data/test/fixtures/embedded-message.eml +34 -0
- data/test/fixtures/non-ascii-header-in-nested-message.eml +36 -0
- data/test/fixtures/non-ascii-header.eml +8 -0
- data/test/fixtures/rfc2047-header-encoding.eml +15 -0
- data/test/fixtures/text-attachments-with-charset.eml +15 -1
- data/test/fixtures/utf8-header.eml +17 -0
- data/test/integration/test_maildir.rb +3 -0
- data/test/integration/test_mbox.rb +4 -1
- data/test/integration/test_sup-add.rb +83 -0
- data/test/integration/test_sup-sync-back-maildir.rb +40 -0
- data/test/test_crypto.rb +44 -0
- data/test/test_header_parsing.rb +11 -3
- data/test/test_helper.rb +7 -4
- data/test/test_message.rb +124 -32
- data/test/test_messages_dir.rb +13 -15
- data/test/unit/test_horizontal_selector.rb +4 -4
- data/test/unit/test_locale_fiddler.rb +1 -1
- data/test/unit/util/test_query.rb +1 -1
- data/test/unit/util/test_string.rb +3 -3
- data/test/unit/util/test_uri.rb +2 -2
- metadata +69 -18
- data/.travis.yml +0 -18
- data/bin/sup-psych-ify-config-files +0 -21
- data/test/integration/test_label_service.rb +0 -18
- data/test/test_yaml_migration.rb +0 -85
data/lib/sup/message.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
3
|
require 'time'
|
4
|
+
require 'string-scrub' if /^2\.0\./ =~ RUBY_VERSION
|
4
5
|
|
5
6
|
module Redwood
|
6
7
|
|
@@ -71,9 +72,9 @@ class Message
|
|
71
72
|
return unless v
|
72
73
|
return v unless v.is_a? String
|
73
74
|
return unless v.size < MAX_HEADER_VALUE_SIZE # avoid regex blowup on spam
|
74
|
-
|
75
|
-
|
76
|
-
Rfc2047.decode_to $encoding,
|
75
|
+
## Header values should be either 7-bit with RFC2047-encoded words
|
76
|
+
## or UTF-8 as per RFC6532. Replace any invalid high bytes with U+FFFD.
|
77
|
+
Rfc2047.decode_to $encoding, v.dup.force_encoding(Encoding::UTF_8).scrub
|
77
78
|
end
|
78
79
|
|
79
80
|
def parse_header encoded_header
|
@@ -104,7 +105,7 @@ class Message
|
|
104
105
|
when String
|
105
106
|
begin
|
106
107
|
Time.parse date
|
107
|
-
rescue ArgumentError
|
108
|
+
rescue ArgumentError
|
108
109
|
#debug "faking mangled date header for #{@id} (orig #{header['date'].inspect} gave error: #{e.message})"
|
109
110
|
Time.now
|
110
111
|
end
|
@@ -270,7 +271,7 @@ class Message
|
|
270
271
|
message_to_chunks rmsg
|
271
272
|
rescue SourceError, SocketError, RMail::EncodingUnsupportedError => e
|
272
273
|
warn_with_location "problem reading message #{id}"
|
273
|
-
debug "could not load message
|
274
|
+
debug "could not load message, exception: #{e.inspect}"
|
274
275
|
|
275
276
|
[Chunk::Text.new(error_message.split("\n"))]
|
276
277
|
|
@@ -309,7 +310,7 @@ EOS
|
|
309
310
|
end
|
310
311
|
|
311
312
|
def each_raw_message_line &b
|
312
|
-
location.each_raw_message_line
|
313
|
+
location.each_raw_message_line(&b)
|
313
314
|
end
|
314
315
|
|
315
316
|
def sync_back
|
@@ -759,7 +760,7 @@ private
|
|
759
760
|
|
760
761
|
def warn_with_location msg
|
761
762
|
warn msg
|
762
|
-
warn "Message is in #{
|
763
|
+
warn "Message is in #{@locations}"
|
763
764
|
end
|
764
765
|
end
|
765
766
|
|
data/lib/sup/message_chunks.rb
CHANGED
@@ -128,7 +128,16 @@ EOS
|
|
128
128
|
|
129
129
|
text = case @content_type
|
130
130
|
when /^text\/plain\b/
|
131
|
-
|
131
|
+
if /^UTF-7$/i =~ encoded_content.charset
|
132
|
+
@raw_content.decode_utf7
|
133
|
+
else
|
134
|
+
begin
|
135
|
+
charset = Encoding.find(encoded_content.charset || 'US-ASCII')
|
136
|
+
rescue ArgumentError
|
137
|
+
charset = 'US-ASCII'
|
138
|
+
end
|
139
|
+
@raw_content.force_encoding(charset)
|
140
|
+
end
|
132
141
|
else
|
133
142
|
HookManager.run "mime-decode", :content_type => @content_type,
|
134
143
|
:filename => lambda { write_to_disk },
|
@@ -274,24 +283,19 @@ EOS
|
|
274
283
|
class EnclosedMessage
|
275
284
|
attr_reader :lines
|
276
285
|
def initialize from, to, cc, date, subj
|
277
|
-
@from = from ? "unknown sender" : from.full_address
|
278
|
-
@to = to ? "" : to.map { |p| p.full_address }.join(", ")
|
279
|
-
@cc = cc ? "" : cc.map { |p| p.full_address }.join(", ")
|
280
|
-
|
281
|
-
@date = date.rfc822
|
282
|
-
else
|
283
|
-
@date = ""
|
284
|
-
end
|
285
|
-
|
286
|
+
@from = !from ? "unknown sender" : from.full_address
|
287
|
+
@to = !to ? "" : to.map { |p| p.full_address }.join(", ")
|
288
|
+
@cc = !cc ? "" : cc.map { |p| p.full_address }.join(", ")
|
289
|
+
@date = !date ? "" : date.rfc822
|
286
290
|
@subj = subj
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
@lines
|
291
|
+
@lines = [
|
292
|
+
"From: #{@from}",
|
293
|
+
"To: #{@to}",
|
294
|
+
"Cc: #{@cc}",
|
295
|
+
"Date: #{@date}",
|
296
|
+
"Subject: #{@subj}"
|
297
|
+
]
|
298
|
+
@lines.delete_if{ |line| line == 'Cc: ' }
|
295
299
|
end
|
296
300
|
|
297
301
|
def inlineable?; false end
|
@@ -302,7 +306,11 @@ EOS
|
|
302
306
|
def viewable?; false end
|
303
307
|
|
304
308
|
def patina_color; :generic_notice_patina_color end
|
305
|
-
def patina_text
|
309
|
+
def patina_text
|
310
|
+
"Begin enclosed message" + (
|
311
|
+
@date == "" ? "" : " sent on #{@date}"
|
312
|
+
)
|
313
|
+
end
|
306
314
|
|
307
315
|
def color; :quote_color end
|
308
316
|
end
|
@@ -43,7 +43,7 @@ protected
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def view
|
46
|
-
|
46
|
+
_name, f = @files[curpos - RESERVED_ROWS]
|
47
47
|
return unless f && f.file?
|
48
48
|
|
49
49
|
begin
|
@@ -54,7 +54,7 @@ protected
|
|
54
54
|
end
|
55
55
|
|
56
56
|
def select_file_or_follow_directory
|
57
|
-
|
57
|
+
_name, f = @files[curpos - RESERVED_ROWS]
|
58
58
|
return unless f
|
59
59
|
|
60
60
|
if f.directory? && f.to_s != "."
|
@@ -131,13 +131,13 @@ protected
|
|
131
131
|
end
|
132
132
|
|
133
133
|
def select_search
|
134
|
-
name,
|
134
|
+
name, _num_unread = @searches[curpos]
|
135
135
|
return unless name
|
136
136
|
SearchResultsMode.spawn_from_query SearchManager.search_string_for(name)
|
137
137
|
end
|
138
138
|
|
139
139
|
def delete_selected_search
|
140
|
-
name,
|
140
|
+
name, _num_unread = @searches[curpos]
|
141
141
|
return unless name
|
142
142
|
reload if SearchManager.delete name
|
143
143
|
end
|
@@ -241,7 +241,7 @@ EOS
|
|
241
241
|
|
242
242
|
begin
|
243
243
|
u = URI.parse($1)
|
244
|
-
rescue URI::InvalidURIError
|
244
|
+
rescue URI::InvalidURIError
|
245
245
|
BufferManager.flash("Invalid unsubscribe link")
|
246
246
|
return
|
247
247
|
end
|
@@ -868,7 +868,6 @@ private
|
|
868
868
|
(0 ... text.length).each do |i|
|
869
869
|
@chunk_lines[@text.length + i] = m
|
870
870
|
@message_lines[@text.length + i] = m
|
871
|
-
lw = text[i].flatten.select { |x| x.is_a? String }.map { |x| x.display_length }.sum
|
872
871
|
end
|
873
872
|
|
874
873
|
@text += text
|
data/lib/sup/rfc2047.rb
CHANGED
@@ -17,7 +17,7 @@
|
|
17
17
|
# This file is distributed under the same terms as Ruby.
|
18
18
|
|
19
19
|
module Rfc2047
|
20
|
-
WORD = %r{=\?([!\#$%&'*+-/0-9A-Z\\^\`a-z{|}~]+)\?([BbQq])\?([!->@-~]+)\?=} # :nodoc: 'stupid ruby-mode
|
20
|
+
WORD = %r{=\?([!\#$%&'*+-/0-9A-Z\\^\`a-z{|}~]+)\?([BbQq])\?([!->@-~ ]+)\?=} # :nodoc: 'stupid ruby-mode
|
21
21
|
WORDSEQ = %r{(#{WORD.source})\s+(?=#{WORD.source})}
|
22
22
|
|
23
23
|
def Rfc2047.is_encoded? s; s =~ WORD end
|
@@ -28,16 +28,17 @@ module Rfc2047
|
|
28
28
|
# converted to the target encoding, it is left in its encoded form.
|
29
29
|
def Rfc2047.decode_to(target, from)
|
30
30
|
from = from.gsub(WORDSEQ, '\1')
|
31
|
-
|
31
|
+
from.gsub(WORD) do
|
32
32
|
|word|
|
33
33
|
charset, encoding, text = $1, $2, $3
|
34
34
|
|
35
35
|
# B64 or QP decode, as necessary:
|
36
36
|
case encoding
|
37
37
|
when 'b', 'B'
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
## Padding is optional in RFC 2047 words. Add some extra padding
|
39
|
+
## before decoding the base64, otherwise on Ruby 2.0 the final byte
|
40
|
+
## might be discarded.
|
41
|
+
text = (text + '===').unpack('m*')[0]
|
41
42
|
|
42
43
|
when 'q', 'Q'
|
43
44
|
# RFC 2047 has a variant of quoted printable where a ' ' character
|
@@ -50,7 +51,21 @@ module Rfc2047
|
|
50
51
|
# WORD.
|
51
52
|
end
|
52
53
|
|
53
|
-
|
54
|
+
# Handle UTF-7 specially because Ruby doesn't actually support it as
|
55
|
+
# a normal character encoding.
|
56
|
+
if charset == 'UTF-7'
|
57
|
+
begin
|
58
|
+
next text.decode_utf7.encode(target)
|
59
|
+
rescue ArgumentError, EncodingError
|
60
|
+
next word
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
begin
|
65
|
+
text.force_encoding(charset).encode(target)
|
66
|
+
rescue ArgumentError, EncodingError
|
67
|
+
word
|
68
|
+
end
|
54
69
|
end
|
55
70
|
end
|
56
71
|
end
|
data/lib/sup/source.rb
CHANGED
@@ -102,7 +102,7 @@ class Source
|
|
102
102
|
end
|
103
103
|
|
104
104
|
def synchronize &block
|
105
|
-
@poll_lock.synchronize
|
105
|
+
@poll_lock.synchronize(&block)
|
106
106
|
end
|
107
107
|
|
108
108
|
def try_lock
|
@@ -153,7 +153,7 @@ class Source
|
|
153
153
|
next unless Rfc2047.is_encoded? v
|
154
154
|
header[k] = begin
|
155
155
|
Rfc2047.decode_to $encoding, v
|
156
|
-
rescue Errno::EINVAL, Iconv::InvalidEncoding, Iconv::IllegalSequence
|
156
|
+
rescue Errno::EINVAL, Iconv::InvalidEncoding, Iconv::IllegalSequence
|
157
157
|
#debug "warning: error decoding RFC 2047 header (#{e.class.name}): #{e.message}"
|
158
158
|
v
|
159
159
|
end
|
@@ -169,6 +169,12 @@ protected
|
|
169
169
|
def Source.expand_filesystem_uri uri
|
170
170
|
uri.gsub "~", File.expand_path("~")
|
171
171
|
end
|
172
|
+
|
173
|
+
def Source.encode_path_for_uri path
|
174
|
+
path.gsub(Regexp.new("[#{Regexp.quote(URI_ENCODE_CHARS)}]")) { |c|
|
175
|
+
c.each_byte.map { |x| sprintf("%%%02X", x) }.join
|
176
|
+
}
|
177
|
+
end
|
172
178
|
end
|
173
179
|
|
174
180
|
## if you have a @labels instance variable, include this
|
data/lib/sup/textfield.rb
CHANGED
@@ -119,7 +119,6 @@ class TextField
|
|
119
119
|
Ncurses::Form::REQ_END_FIELD
|
120
120
|
when Ncurses::KEY_UP, Ncurses::KEY_DOWN
|
121
121
|
unless !@i || @history.empty?
|
122
|
-
value = get_cursed_value
|
123
122
|
#debug "history before #{@history.inspect}"
|
124
123
|
@i = @i + (c.is_keycode?(Ncurses::KEY_UP) ? -1 : 1)
|
125
124
|
@i = 0 if @i < 0
|
data/lib/sup/thread.rb
CHANGED
@@ -86,22 +86,22 @@ class Thread
|
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
|
-
def first; each { |m,
|
90
|
-
def has_message?; any? { |m,
|
91
|
-
def dirty?; any? { |m,
|
92
|
-
def date; map { |m,
|
89
|
+
def first; each { |m, *| return m if m }; nil; end
|
90
|
+
def has_message?; any? { |m, *| m.is_a? Message }; end
|
91
|
+
def dirty?; any? { |m, *| m && m.dirty? }; end
|
92
|
+
def date; map { |m, *| m.date if m }.compact.max; end
|
93
93
|
def snippet
|
94
|
-
with_snippets = select { |m,
|
95
|
-
first_unread, * = with_snippets.select { |m,
|
94
|
+
with_snippets = select { |m, *| m && m.snippet && !m.snippet.empty? }
|
95
|
+
first_unread, * = with_snippets.select { |m, *| m.has_label?(:unread) }.sort_by { |m, *| m.date }.first
|
96
96
|
return first_unread.snippet if first_unread
|
97
|
-
last_read, * = with_snippets.sort_by { |m,
|
97
|
+
last_read, * = with_snippets.sort_by { |m, *| m.date }.last
|
98
98
|
return last_read.snippet if last_read
|
99
99
|
""
|
100
100
|
end
|
101
|
-
def authors; map { |m,
|
101
|
+
def authors; map { |m, *| m.from if m }.compact.uniq; end
|
102
102
|
|
103
|
-
def apply_label t; each { |m,
|
104
|
-
def remove_label t; each { |m,
|
103
|
+
def apply_label t; each { |m, *| m && m.add_label(t) }; end
|
104
|
+
def remove_label t; each { |m, *| m && m.remove_label(t) }; end
|
105
105
|
|
106
106
|
def toggle_label label
|
107
107
|
if has_label? label
|
@@ -113,24 +113,24 @@ class Thread
|
|
113
113
|
end
|
114
114
|
end
|
115
115
|
|
116
|
-
def set_labels l; each { |m,
|
117
|
-
def has_label? t; any? { |m,
|
118
|
-
def each_dirty_message; each { |m,
|
116
|
+
def set_labels l; each { |m, *| m && m.labels = l }; end
|
117
|
+
def has_label? t; any? { |m, *| m && m.has_label?(t) }; end
|
118
|
+
def each_dirty_message; each { |m, *| m && m.dirty? && yield(m) }; end
|
119
119
|
|
120
120
|
def direct_participants
|
121
|
-
map { |m,
|
121
|
+
map { |m, *| [m.from] + m.to if m }.flatten.compact.uniq
|
122
122
|
end
|
123
123
|
|
124
124
|
def participants
|
125
|
-
map { |m,
|
125
|
+
map { |m, *| [m.from] + m.to + m.cc + m.bcc if m }.flatten.compact.uniq
|
126
126
|
end
|
127
127
|
|
128
|
-
def size; map { |m,
|
129
|
-
def subj; argfind { |m,
|
130
|
-
def labels; inject(Set.new) { |s, (m, *
|
128
|
+
def size; map { |m, *| m ? 1 : 0 }.sum; end
|
129
|
+
def subj; argfind { |m, *| m && m.subj }; end
|
130
|
+
def labels; inject(Set.new) { |s, (m, *)| m ? s | m.labels : s } end
|
131
131
|
def labels= l
|
132
132
|
raise ArgumentError, "not a set" unless l.is_a?(Set)
|
133
|
-
each { |m,
|
133
|
+
each { |m, *| m && m.labels = l.dup }
|
134
134
|
end
|
135
135
|
|
136
136
|
def latest_message
|
@@ -360,7 +360,7 @@ class ThreadSet
|
|
360
360
|
## merges in a pre-loaded thread
|
361
361
|
def add_thread t
|
362
362
|
raise "duplicate" if @threads.values.member? t
|
363
|
-
t.each { |m,
|
363
|
+
t.each { |m, *| add_message m }
|
364
364
|
end
|
365
365
|
|
366
366
|
## merges two threads together. both must be members of this threadset.
|
@@ -407,7 +407,6 @@ class ThreadSet
|
|
407
407
|
#puts "adding: #{message.id}, refs #{message.refs.inspect}"
|
408
408
|
|
409
409
|
el.message = message
|
410
|
-
oldroot = el.root
|
411
410
|
|
412
411
|
## link via references:
|
413
412
|
(message.refs + [el.id]).inject(nil) do |prev, ref_id|
|
data/lib/sup/util.rb
CHANGED
@@ -4,14 +4,16 @@ require 'thread'
|
|
4
4
|
require 'lockfile'
|
5
5
|
require 'mime/types'
|
6
6
|
require 'pathname'
|
7
|
+
require 'rmail'
|
7
8
|
require 'set'
|
8
9
|
require 'enumerator'
|
9
10
|
require 'benchmark'
|
10
11
|
require 'unicode'
|
11
12
|
require 'unicode/display_width'
|
12
13
|
require 'fileutils'
|
14
|
+
require 'string-scrub' if /^2\.0\./ =~ RUBY_VERSION
|
13
15
|
|
14
|
-
|
16
|
+
module ExtendedLockfile
|
15
17
|
def gen_lock_id
|
16
18
|
Hash[
|
17
19
|
'host' => "#{ Socket.gethostname }",
|
@@ -37,6 +39,7 @@ class Lockfile
|
|
37
39
|
|
38
40
|
def touch_yourself; touch path end
|
39
41
|
end
|
42
|
+
Lockfile.send :prepend, ExtendedLockfile
|
40
43
|
|
41
44
|
class File
|
42
45
|
# platform safe file.link which attempts a copy if hard-linking fails
|
@@ -106,7 +109,7 @@ module RMail
|
|
106
109
|
end
|
107
110
|
end
|
108
111
|
|
109
|
-
|
112
|
+
module CustomizedSerialize
|
110
113
|
## Don't add MIME-Version headers on serialization. Sup sometimes want's to serialize
|
111
114
|
## message parts where these headers are not needed and messing with the message on
|
112
115
|
## serialization breaks gpg signatures. The commented section shows the original RMail
|
@@ -118,48 +121,7 @@ module RMail
|
|
118
121
|
# end
|
119
122
|
end
|
120
123
|
end
|
121
|
-
|
122
|
-
class Header
|
123
|
-
|
124
|
-
# Convert to ASCII before trying to match with regexp
|
125
|
-
class Field
|
126
|
-
|
127
|
-
class << self
|
128
|
-
def parse(field)
|
129
|
-
field = field.dup.to_s
|
130
|
-
field = field.fix_encoding!.ascii
|
131
|
-
if field =~ EXTRACT_FIELD_NAME_RE
|
132
|
-
[ $1, $'.chomp ]
|
133
|
-
else
|
134
|
-
[ "", Field.value_strip(field) ]
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
## Be more cautious about invalid content-type headers
|
141
|
-
## the original RMail code calls
|
142
|
-
## value.strip.split(/\s*;\s*/)[0].downcase
|
143
|
-
## without checking if split returned an element
|
144
|
-
|
145
|
-
# This returns the full content type of this message converted to
|
146
|
-
# lower case.
|
147
|
-
#
|
148
|
-
# If there is no content type header, returns the passed block is
|
149
|
-
# executed and its return value is returned. If no block is passed,
|
150
|
-
# the value of the +default+ argument is returned.
|
151
|
-
def content_type(default = nil)
|
152
|
-
if value = self['content-type'] and ct = value.strip.split(/\s*;\s*/)[0]
|
153
|
-
return ct.downcase
|
154
|
-
else
|
155
|
-
if block_given?
|
156
|
-
yield
|
157
|
-
else
|
158
|
-
default
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|
162
|
-
end
|
124
|
+
Serialize.send :prepend, CustomizedSerialize
|
163
125
|
end
|
164
126
|
|
165
127
|
class Module
|
@@ -354,11 +316,8 @@ class String
|
|
354
316
|
# first try to encode to utf-8 from whatever current encoding
|
355
317
|
encode!('UTF-8', :invalid => :replace, :undef => :replace)
|
356
318
|
|
357
|
-
#
|
358
|
-
|
359
|
-
# ensures invalid chars are replaced.
|
360
|
-
encode!('UTF-16', 'UTF-8', :invalid => :replace, :undef => :replace)
|
361
|
-
encode!('UTF-8', 'UTF-16', :invalid => :replace, :undef => :replace)
|
319
|
+
# ensure invalid chars are replaced
|
320
|
+
scrub!
|
362
321
|
|
363
322
|
fail "Could not create valid UTF-8 string out of: '#{self.to_s}'." unless valid_encoding?
|
364
323
|
|
@@ -392,8 +351,23 @@ class String
|
|
392
351
|
self
|
393
352
|
end
|
394
353
|
|
354
|
+
## Decodes UTF-7 and returns the resulting decoded string as UTF-8.
|
355
|
+
##
|
356
|
+
## Ruby doesn't supply a UTF-7 encoding natively. There is
|
357
|
+
## Net::IMAP::decode_utf7 which only handles the IMAP "modified UTF-7"
|
358
|
+
## encoding. This implementation is inspired by that one but handles
|
359
|
+
## standard UTF-7 shift characters and not the IMAP-specific variation.
|
360
|
+
def decode_utf7
|
361
|
+
gsub(/\+([^-]+)?-/) {
|
362
|
+
if $1
|
363
|
+
($1 + "===").unpack("m")[0].encode(Encoding::UTF_8, Encoding::UTF_16BE)
|
364
|
+
else
|
365
|
+
"+"
|
366
|
+
end
|
367
|
+
}
|
368
|
+
end
|
369
|
+
|
395
370
|
def normalize_whitespace
|
396
|
-
fix_encoding!
|
397
371
|
gsub(/\t/, " ").gsub(/\r/, "")
|
398
372
|
end
|
399
373
|
|
@@ -405,7 +379,7 @@ class String
|
|
405
379
|
|
406
380
|
unless method_defined? :each
|
407
381
|
def each &b
|
408
|
-
each_line
|
382
|
+
each_line(&b)
|
409
383
|
end
|
410
384
|
end
|
411
385
|
|
@@ -507,7 +481,9 @@ module Enumerable
|
|
507
481
|
ret
|
508
482
|
end
|
509
483
|
|
510
|
-
|
484
|
+
if not method_defined? :sum
|
485
|
+
def sum; inject(0) { |x, y| x + y }; end
|
486
|
+
end
|
511
487
|
|
512
488
|
def map_to_hash
|
513
489
|
ret = {}
|
@@ -563,7 +539,9 @@ class Array
|
|
563
539
|
inject([]) { |a, e| a + e }
|
564
540
|
end
|
565
541
|
|
566
|
-
|
542
|
+
if not method_defined? :to_h
|
543
|
+
def to_h; Hash[*flatten_one_level]; end
|
544
|
+
end
|
567
545
|
def rest; self[1..-1]; end
|
568
546
|
|
569
547
|
def to_boolean_h; Hash[*map { |x| [x, true] }.flatten]; end
|
data/lib/sup/version.rb
CHANGED
data/lib/sup.rb
CHANGED
@@ -26,7 +26,7 @@ class Module
|
|
26
26
|
props = props.map { |p| p.to_s }
|
27
27
|
|
28
28
|
path = name.gsub(/::/, "/")
|
29
|
-
yaml_tag "
|
29
|
+
yaml_tag "tag:#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}/#{path}"
|
30
30
|
|
31
31
|
define_method :init_with do |coder|
|
32
32
|
initialize(*coder.map.values_at(*props))
|
@@ -38,9 +38,6 @@ class Module
|
|
38
38
|
hash
|
39
39
|
end
|
40
40
|
end
|
41
|
-
|
42
|
-
# Legacy
|
43
|
-
Psych.load_tags["!#{Redwood::LEGACY_YAML_DOMAIN},#{Redwood::YAML_DATE}/#{path}"] = self
|
44
41
|
end
|
45
42
|
end
|
46
43
|
|
@@ -139,10 +136,17 @@ module Redwood
|
|
139
136
|
|
140
137
|
def load_yaml_obj fn, compress=false
|
141
138
|
o = if File.exist? fn
|
142
|
-
if compress
|
143
|
-
Zlib::GzipReader.open(fn) { |f|
|
139
|
+
raw_contents = if compress
|
140
|
+
Zlib::GzipReader.open(fn) { |f| f.read }
|
141
|
+
else
|
142
|
+
File::open(fn) { |f| f.read }
|
143
|
+
end
|
144
|
+
## fix up malformed tag URIs created by earlier versions of sup
|
145
|
+
raw_contents.gsub!(/!supmua.org,2006-10-01\/(\S*)$/) { |m| "!<tag:supmua.org,2006-10-01/#{$1}>" }
|
146
|
+
if YAML.respond_to?(:unsafe_load) # Ruby 3.1+
|
147
|
+
YAML::unsafe_load raw_contents
|
144
148
|
else
|
145
|
-
YAML::
|
149
|
+
YAML::load raw_contents
|
146
150
|
end
|
147
151
|
end
|
148
152
|
if o.is_a?(Array)
|
@@ -349,7 +353,7 @@ EOM
|
|
349
353
|
name ||= ENV["USER"]
|
350
354
|
email = ENV["USER"] + "@" +
|
351
355
|
begin
|
352
|
-
|
356
|
+
Addrinfo.getaddrinfo(Socket.gethostname, 'smtp').first.getnameinfo.first
|
353
357
|
rescue SocketError
|
354
358
|
Socket.gethostname
|
355
359
|
end
|