sup 1.0 → 1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|