imap-backup 16.6.0 → 17.0.0.rc0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 325c46cce0eff42c96d4c44e686b32f0d52352cdfa038c17a132069b71bdf11e
4
- data.tar.gz: 349426301b37fa38db3da6c2d71a3979892345828b88ffa2b52752a559f6a95b
3
+ metadata.gz: 4b4c01b65abcd4b7fe7f57d214ce3d0f141085058c3aac3245842bac7dc3bda3
4
+ data.tar.gz: 418df48403a0e2d28e63b4ab233d69de327e3e139b975ceac8056eb972d05eba
5
5
  SHA512:
6
- metadata.gz: 6c4735412c68747d5365048e7748cc849bb1302a2da6e4af60354bf2a44c3c4dab5f14cf9ea7111cd7c6f416faea8ad429657a551755167662998e20ff7aae3a
7
- data.tar.gz: 213bc6df849b8e7d72ded14920695e1a158d8657ac0d3bcd6632068f0c76258d2b5d0f37a5073867cf1f549249b1a2df651a0327ce803051311ed33fe5e757a0
6
+ metadata.gz: 6a31419c312cfcfe21bf8dcb36a9093827dd1c3f9a6d4bec599c8fd53a7e426801bf1f9c014c734a73334321e1ecb1973cdac18f5410a9330021c72473df4821
7
+ data.tar.gz: 81c4d68861e9b77ef064897834e9631b682d576e25ca4fb7625afb15b87dc11ba729bb27940edaefb8bc65a8a88f614ae11129171c8ced9a2d8ad03f122fcb5d
data/docs/TODO.md CHANGED
@@ -33,3 +33,24 @@ Currently, only the Download Strategy Chooser screen has localized help function
33
33
  - Other setup screens (folder chooser, backup path, etc.)
34
34
  - Add translation keys for help text to locale files (`lib/imap/backup/locales/en.yml` and `lib/imap/backup/locales/it.yml`)
35
35
  - Follow the pattern: menu choice for "help" that displays localized help text and waits for key press
36
+
37
+ # Fix mboxrd Quote Serialization Bug
38
+
39
+ Status: [x]
40
+
41
+ ## Description
42
+
43
+ The `add_extra_quote` method in `Email::Mboxrd::Message` incorrectly quotes all lines beginning with `From` (e.g. `From:` headers), rather than only lines beginning with `From ` (with a trailing space). The corresponding `clean_serialized` method has the same problem on load. This results in mbox files storing corrupted content. The fix involves correcting both regexes, bumping the metadata format version, and migrating existing files on load.
44
+
45
+ ## Technical Specifics
46
+
47
+ - In [lib/imap/backup/serializer/imap.rb](lib/imap/backup/serializer/imap.rb):
48
+ - Bump `CURRENT_VERSION` from `3` to `3.1`
49
+ - Add `3` to `LOADABLE_VERSIONS`: `LOADABLE_VERSIONS = [3, 3.1].freeze`
50
+ - In `ensure_loaded`, use a `case data[:version]` branch:
51
+ - `when CURRENT_VERSION` — load messages normally
52
+ - `when 3` — load each message using the old (buggy) deserialization logic (`from_serialized_v3`), and set `@version = CURRENT_VERSION` so the next save writes version 3.1
53
+ - In [lib/imap/backup/email/mboxrd/message.rb](lib/imap/backup/email/mboxrd/message.rb):
54
+ - Fix `add_extra_quote`: change regex from `/\n(>*From)/` to `/\n(>*From )/`
55
+ - Fix `clean_serialized`: change regex from `/^>(>*From)/` to `/^>(>*From )/`
56
+ - Add `clean_serialized_v3` (using the old regex `/^>(>*From)/`) and a corresponding `from_serialized_v3` class method for use during migration of version 3 files
@@ -14,7 +14,7 @@ module Imap::Backup
14
14
  # and with one level of '>' quoting removed from other lines
15
15
  # that start with 'From'
16
16
  def self.clean_serialized(serialized)
17
- cleaned = serialized.gsub(/^>(>*From)/, "\\1")
17
+ cleaned = serialized.gsub(/^>(>*From )/, "\\1")
18
18
  # Serialized messages in this format *should* start with a line
19
19
  # From xxx yy zz
20
20
  # rubocop:disable Style/IfUnlessModifier
@@ -32,6 +32,21 @@ module Imap::Backup
32
32
  new(clean_serialized(serialized))
33
33
  end
34
34
 
35
+ # Deserializes a message stored with the old (v3) quoting logic,
36
+ # which incorrectly quoted all 'From' lines, not just 'From ' lines.
37
+ def self.clean_serialized_v3(serialized)
38
+ cleaned = serialized.gsub(/^>(>*From)/, "\\1")
39
+ cleaned = cleaned.sub(/^From .*[\r\n]*/, "") if cleaned.start_with?("From ")
40
+ cleaned
41
+ end
42
+
43
+ # @param serialized [String] the on-disk version of a v3 message
44
+ #
45
+ # @return [Message] the original message
46
+ def self.from_serialized_v3(serialized)
47
+ new(clean_serialized_v3(serialized))
48
+ end
49
+
35
50
  # @return [String] the original message body
36
51
  attr_reader :supplied_body
37
52
 
@@ -110,7 +125,7 @@ module Imap::Backup
110
125
  # 'From ' can be taken as the beginning of messages.
111
126
  # http://www.digitalpreservation.gov/formats/fdd/fdd000385.shtml
112
127
  # Here we add an extra '>' before any "From" or ">From".
113
- body.gsub(/\n(>*From)/, "\n>\\1")
128
+ body.gsub(/\n(>*From )/, "\n>\\1")
114
129
  end
115
130
 
116
131
  def asctime
@@ -12,7 +12,9 @@ module Imap::Backup
12
12
  # Stores message metadata
13
13
  class Serializer::Imap
14
14
  # The version number to store in the metadata file
15
- CURRENT_VERSION = 3
15
+ CURRENT_VERSION = 3.1
16
+
17
+ LOADABLE_VERSIONS = [3, 3.1].freeze
16
18
 
17
19
  # @return [Serializer::Files::Path] The path of the imap metadata file, without the '.imap'
18
20
  # extension
@@ -41,7 +43,9 @@ module Imap::Backup
41
43
  tsx.begin({savepoint: {messages: messages.dup, uid_validity: uid_validity}}) do
42
44
  block.call
43
45
 
44
- save_internal(version: version, uid_validity: uid_validity, messages: messages) if tsx.data
46
+ if tsx.data
47
+ save_internal(version: CURRENT_VERSION, uid_validity: uid_validity, messages: messages)
48
+ end
45
49
  rescue Exception => e
46
50
  Logger.logger.error "#{self.class} handling #{e.class}"
47
51
  rollback
@@ -72,7 +76,7 @@ module Imap::Backup
72
76
 
73
77
  def valid?
74
78
  return false if !exist?
75
- return false if version != CURRENT_VERSION
79
+ return false if !loadable_version?(version)
76
80
  return false if !uid_validity
77
81
 
78
82
  true
@@ -208,7 +212,7 @@ module Imap::Backup
208
212
 
209
213
  ensure_loaded
210
214
 
211
- save_internal(version: version, uid_validity: uid_validity, messages: messages)
215
+ save_internal(version: CURRENT_VERSION, uid_validity: uid_validity, messages: messages)
212
216
  end
213
217
 
214
218
  private
@@ -232,9 +236,19 @@ module Imap::Backup
232
236
 
233
237
  data = load
234
238
  if data
235
- @messages = data[:messages].map { |m| Serializer::Message.new(mbox: mbox, **m) }
239
+ case data[:version]
240
+ when CURRENT_VERSION
241
+ @messages = data[:messages].map { |m| Serializer::Message.new(mbox: mbox, **m) }
242
+ @version = CURRENT_VERSION
243
+ when 3
244
+ @messages = load_v3_messages(data[:messages])
245
+ @uid_validity = data[:uid_validity]
246
+ @version = CURRENT_VERSION
247
+ @loaded = true
248
+ save_internal(version: CURRENT_VERSION, uid_validity: uid_validity, messages: messages)
249
+ return
250
+ end
236
251
  @uid_validity = data[:uid_validity]
237
- @version = data[:version]
238
252
  else
239
253
  @messages = []
240
254
  @uid_validity = nil
@@ -255,6 +269,7 @@ module Imap::Backup
255
269
  end
256
270
 
257
271
  return nil if !data.key?(:version)
272
+ return nil if !loadable_version?(data[:version])
258
273
  return nil if !data.key?(:uid_validity)
259
274
  return nil if !data.key?(:messages)
260
275
  return nil if !data[:messages].is_a?(Array)
@@ -262,6 +277,29 @@ module Imap::Backup
262
277
  data
263
278
  end
264
279
 
280
+ def loadable_version?(version)
281
+ LOADABLE_VERSIONS.include?(version)
282
+ end
283
+
284
+ def load_v3_messages(message_data)
285
+ messages = []
286
+ offset = 0
287
+ new_content = +""
288
+ message_data.each do |m|
289
+ raw = mbox.read(m[:offset], m[:length])
290
+ original = Email::Mboxrd::Message.from_serialized_v3(raw)
291
+ serialized = original.to_serialized
292
+ messages << Serializer::Message.new(
293
+ mbox: mbox, uid: m[:uid], offset: offset,
294
+ length: serialized.bytesize, flags: m[:flags] || []
295
+ )
296
+ new_content << serialized
297
+ offset += serialized.bytesize
298
+ end
299
+ File.open(mbox.pathname, "wb") { |f| f.write(new_content) }
300
+ messages
301
+ end
302
+
265
303
  def mbox
266
304
  @mbox ||= Serializer::Mbox.new(files_path: files_path)
267
305
  end
@@ -2,13 +2,13 @@ module Imap; end
2
2
 
3
3
  module Imap::Backup
4
4
  # @private
5
- MAJOR = 16
5
+ MAJOR = 17
6
6
  # @private
7
- MINOR = 6
7
+ MINOR = 0
8
8
  # @private
9
9
  REVISION = 0
10
10
  # @private
11
- PRE = nil
11
+ PRE = "rc0"
12
12
  # The application version
13
13
  VERSION = [MAJOR, MINOR, REVISION, PRE].compact.map(&:to_s).join(".")
14
14
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: imap-backup
3
3
  version: !ruby/object:Gem::Version
4
- version: 16.6.0
4
+ version: 17.0.0.rc0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Yates