@@ -10,6 +10,8 @@ class CommandDataTest < Net::IMAP::TestCase
1010 Flag = Net ::IMAP ::Flag
1111 Literal = Net ::IMAP ::Literal
1212 Literal8 = Net ::IMAP ::Literal8
13+ RawText = Net ::IMAP ::RawText
14+ RawData = Net ::IMAP ::RawData
1315
1416 Output = Net ::IMAP ::Data . define ( :name , :args , :kwargs )
1517 TAG = Module . new . freeze
@@ -151,4 +153,207 @@ def send_data(*data, tag: TAG)
151153 ] , imap . output
152154 end
153155
156+ class RawTextTest < CommandDataTest
157+ test "basic ASCII string" do
158+ imap . send_data RawText . new ( 'foo "bar" (baz)' )
159+ assert_equal [ Output . put_string ( 'foo "bar" (baz)' ) ] , imap . output
160+ end
161+
162+ test "allows IMAP atom-special symbols" do
163+ imap . send_data RawText . new ( 'foo "bar" (baz)' )
164+ imap . send_data RawText . new ( "(){}[]%*\" \\ " )
165+ imap . send_data RawText . new ( "(((((((((((((((( unbalanced ]]]]]]]]]]]]]" )
166+ assert_equal [
167+ Output . put_string ( 'foo "bar" (baz)' ) ,
168+ Output . put_string ( "(){}[]%*\" \\ " ) ,
169+ Output . put_string ( "(((((((((((((((( unbalanced ]]]]]]]]]]]]]" ) ,
170+ ] , imap . output
171+ end
172+
173+ test "ASCII compatible string with another encodings" do
174+ imap . send_data RawText . new ( "foo bar" . encode ( "cp1252" ) )
175+ assert_equal [
176+ Output . put_string ( "foo bar" ) ,
177+ ] , imap . output
178+ end
179+
180+ test "allows ASCII control chars" do
181+ text = RawText . new ( "beep\b beep\b escape!\e delete this:\x1f " )
182+ imap . send_data text
183+ assert_equal [
184+ Output . put_string ( "beep\b beep\b escape!\e delete this:\x1f " ) ,
185+ ] , imap . output
186+ end
187+
188+ data (
189+ "NULL" => [ "with \0 NULL" , /NULL\b .+\b byte/i ] ,
190+ "CR" => [ "with \r CR" , /CR\b .+\b byte/i ] ,
191+ "LF" => [ "with \n LF" , /LF\b .+\b byte/i ] ,
192+ )
193+ test "invalid ASCII byte" do |( text , error_message ) |
194+ try_multiple_encodings ( error_message , text )
195+ end
196+
197+ # See Table 3-7, Well-Formed UTF-8 Byte Sequences, in The Unicode Standard:
198+ # https://www.unicode.org/versions/Unicode17.0.0/core-spec/chapter-3/#G27506
199+ data (
200+ "incomplete 2 byte sequence" => "\xc3 " . b ,
201+ "invalid 2 byte sequence" => "\xc3 \x7f " . b ,
202+ "incomplete 3 byte sequence" => "\xe0 \x80 \x80 " . b ,
203+ "invalid 3 byte sequence" => "\xe0 \x80 \x80 " . b ,
204+ "incomplete 4 byte sequence" => "\xf1 \x80 \x80 " . b ,
205+ "invalid 4 byte sequence" => "\xf0 \x80 \x80 \x80 " . b ,
206+ "first byte too high" => "\xff \xaa \xaa \xaa " . b ,
207+ "UTF-16 surrogate pair" => "\xFE \xFF \xD8 \x3D \xDC \xA3 \xFE \x0F " . b ,
208+ "windows-1252" => "åêïõü" . encode ( "windows-1252" ) ,
209+ )
210+ test "invalid UTF-8" do |text |
211+ try_multiple_encodings ( /invalid UTF-8/i , text )
212+ end
213+
214+ def with_multiple_encodings ( data )
215+ yield data . b # BINARY
216+ yield data . dup . force_encoding ( "ASCII" )
217+ yield data . dup . force_encoding ( "UTF-8" )
218+ yield data . dup . force_encoding ( "cp1252" )
219+ end
220+
221+ def try_multiple_encodings ( error_message , data )
222+ with_multiple_encodings ( data ) do |encoded |
223+ assert_raise_with_message ( DataFormatError , error_message ) do
224+ RawText [ encoded ]
225+ end
226+ end
227+ end
228+ end
229+
230+ class RawDataTest < CommandDataTest
231+ test "simple raw text" do
232+ raw = RawData . new ( 'foo "bar" baz' )
233+ assert_equal [ RawText [ 'foo "bar" baz' ] ] , raw . data
234+ imap . send_data raw
235+ assert_equal [ Output . put_string ( 'foo "bar" baz' ) ] , imap . output
236+ end
237+
238+ test "a single literal" do
239+ raw = RawData . new ( "{7}\r \n foo bar" )
240+ assert_equal [ Literal [ "foo bar" , false ] ] , raw . data
241+ imap . send_data raw , tag : "t1"
242+ assert_equal [
243+ Output . send_literal ( "foo bar" , "t1" , non_sync : false ) ,
244+ ] , imap . output
245+ end
246+
247+ test "literals embedded between text" do
248+ raw = RawData . new ( "foo bar {3}\r \n baz {4+}\r \n quux etc" )
249+ assert_equal [
250+ RawText [ "foo bar " ] ,
251+ Literal [ "baz" , false ] ,
252+ RawText [ " " ] ,
253+ Literal [ "quux" , true ] , # non-synchronizing
254+ RawText [ " etc" ] ,
255+ ] , raw . data
256+ imap . send_data raw , tag : "t2"
257+ assert_equal [
258+ Output . put_string ( "foo bar " ) ,
259+ Output . send_literal ( "baz" , "t2" , non_sync : false ) ,
260+ Output . put_string ( " " ) ,
261+ Output . send_literal ( "quux" , "t2" , non_sync : true ) ,
262+ Output . put_string ( " etc" ) ,
263+ ] , imap . output
264+ end
265+
266+ test "empty literals" do
267+ raw = RawData . new ( "{0}\r \n {0+}\r \n ~{0}\r \n ~{0+}\r \n " )
268+ assert_equal [
269+ Literal [ "" , false ] ,
270+ Literal [ "" , true ] ,
271+ Literal8 [ "" , false ] ,
272+ Literal8 [ "" , true ] ,
273+ ] , raw . data
274+ imap . send_data raw , tag : "t2.2"
275+ assert_equal [
276+ Output . send_literal ( "" , "t2.2" , non_sync : false ) ,
277+ Output . send_literal ( "" , "t2.2" , non_sync : true ) ,
278+ Output . send_binary_literal ( "" , "t2.2" , non_sync : false ) ,
279+ Output . send_binary_literal ( "" , "t2.2" , non_sync : true ) ,
280+ ] , imap . output
281+ end
282+
283+ test "raw text embedded between literals" do
284+ raw = RawData . new ( "{3}\r \n foo bar" )
285+ assert_equal [
286+ Literal [ "foo" , false ] ,
287+ RawText [ " bar" ]
288+ ] , raw . data
289+ imap . send_data raw , tag : "t3"
290+ assert_equal [
291+ Output . send_literal ( "foo" , "t3" , non_sync : false ) ,
292+ Output . put_string ( " bar" ) ,
293+ ] , imap . output
294+ end
295+
296+ test "raw text followed by literal" do
297+ raw = RawData . new ( "foo {3}\r \n bar" )
298+ assert_equal [
299+ RawText [ "foo " ] ,
300+ Literal [ "bar" , false ] ,
301+ ] , raw . data
302+ imap . send_data raw , tag : "t4"
303+ assert_equal [
304+ Output . put_string ( "foo " ) ,
305+ Output . send_literal ( "bar" , "t4" , non_sync : false ) ,
306+ ] , imap . output
307+ imap . clear
308+ end
309+
310+ test "binary literal with regular literal" do
311+ raw = RawData . new ( "foo ~{7}\r \n \0 bar\r \n baz {4}\r \n quux" )
312+ assert_equal [
313+ RawText [ "foo " ] ,
314+ Literal8 [ "\0 bar\r \n b" , false ] ,
315+ RawText [ "az " ] ,
316+ Literal [ "quux" , false ] ,
317+ ] , raw . data
318+ imap . send_data raw , tag : "t5"
319+ assert_equal [
320+ Output . put_string ( "foo " ) ,
321+ Output . send_binary_literal ( "\0 bar\r \n b" , "t5" , non_sync : false ) ,
322+ Output . put_string ( "az " ) ,
323+ Output . send_literal ( "quux" , "t5" , non_sync : false ) ,
324+ ] , imap . output
325+ end
326+
327+ data (
328+ "CR" => "with \r byte" ,
329+ "LF" => "with \n byte" ,
330+ "NULL" => "with \0 byte" ,
331+ "CRLF" => "with \r \n bytes" ,
332+ )
333+ test "invalid bytes in raw text" do |data |
334+ assert_raise_with_message ( DataFormatError , /must be.* literal encoded/i ) do
335+ RawData . new ( data :)
336+ end
337+ end
338+
339+ test "invalid literal" do |data |
340+ assert_raise_with_message ( DataFormatError , /too few bytes/i ) do
341+ RawData . new ( data : "invalid literal {123}\r \n too small" )
342+ end
343+
344+ assert_raise_with_message ( DataFormatError , /NULL byte.*in.*literal/i ) do
345+ RawData . new ( data : "invalid literal {10}\r \n contains \0 null" )
346+ end
347+ end
348+
349+ test "invalid literal ending ('{123}')" do
350+ assert_raise ( DataFormatError ) do RawData . new ( data : "literal {123}" ) end
351+ assert_raise ( DataFormatError ) do RawData . new ( data : "literal+ {123+}" ) end
352+ assert_raise ( DataFormatError ) do RawData . new ( data : "~literal ~{123}" ) end
353+ assert_raise ( DataFormatError ) do RawData . new ( data : "~literal+ ~{123+}" ) end
354+ raw = RawData . new ( data : " {123} " )
355+ assert_equal [ RawText [ " {123} " ] ] , raw . data
356+ end
357+ end
358+
154359end
0 commit comments