Skip to content

Commit d714f58

Browse files
wtnclaude
andcommitted
Skip the whole record after a field size error
When parse_quotable_robust found a field longer than max_field_size, it skipped only to the end of the physical line. If a later quoted field in the record spanned lines, the rest of the record was parsed as the next row. Skip the rest of the record with parse_column_value, so the next row and CSV#line are correct. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent bb1588f commit d714f58

2 files changed

Lines changed: 97 additions & 2 deletions

File tree

‎lib/csv/parser.rb‎

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -942,7 +942,11 @@ def skip_line?(line)
942942
def validate_field_size(field)
943943
return unless @max_field_size
944944
return if field.size <= @max_field_size
945-
ignore_broken_line
945+
if block_given?
946+
yield
947+
else
948+
ignore_broken_line
949+
end
946950
message = "Field size exceeded: #{field.size} > #{@max_field_size}"
947951
raise MalformedCSVError.new(message, @lineno)
948952
end
@@ -1055,7 +1059,7 @@ def parse_quotable_robust(&block)
10551059
value = parse_column_value
10561060
if value
10571061
@scanner.scan_all(@strip_value) if @strip_value
1058-
validate_field_size(value)
1062+
validate_field_size(value) {ignore_broken_record}
10591063
end
10601064
if parse_column_end
10611065
row << value
@@ -1264,6 +1268,20 @@ def ignore_broken_line
12641268
@lineno += 1
12651269
end
12661270

1271+
def ignore_broken_record
1272+
lineno = @lineno
1273+
begin
1274+
while parse_column_end
1275+
@scanner.scan_all(@strip_value) if @strip_value
1276+
parse_column_value
1277+
@scanner.scan_all(@strip_value) if @strip_value
1278+
end
1279+
ignore_broken_line unless parse_row_end
1280+
rescue MalformedCSVError
1281+
end
1282+
@lineno = lineno + 1
1283+
end
1284+
12671285
def start_row
12681286
if @last_line
12691287
@last_line = nil

‎test/csv/parse/test_general.rb‎

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,83 @@ def test_max_field_size_in_extended_column_exceeding
329329
assert_parse_errors_out(data, max_field_size: 4)
330330
end
331331

332+
def test_max_field_size_multiline_record_liberal_parsing
333+
csv = CSV.new(%Q{abcde,"x\ny"\nc,d\n},
334+
max_field_size: 4, liberal_parsing: true)
335+
error = assert_raise(CSV::MalformedCSVError) do
336+
csv.shift
337+
end
338+
assert_equal("Field size exceeded: 5 > 4 in line 1.",
339+
error.message)
340+
assert_equal(%Q{abcde,"x\ny"\n}, csv.line)
341+
assert_equal(["c", "d"], csv.shift)
342+
assert_equal(2, csv.lineno)
343+
end
344+
345+
def test_max_field_size_three_line_record_liberal_parsing
346+
csv = CSV.new(%Q{abcde,"x\ny\nz"\nc,d\n},
347+
max_field_size: 4, liberal_parsing: true)
348+
error = assert_raise(CSV::MalformedCSVError) do
349+
csv.shift
350+
end
351+
assert_equal("Field size exceeded: 5 > 4 in line 1.",
352+
error.message)
353+
assert_equal(%Q{abcde,"x\ny\nz"\n}, csv.line)
354+
assert_equal(["c", "d"], csv.shift)
355+
assert_equal(2, csv.lineno)
356+
end
357+
358+
def test_max_field_size_multiline_record_after_multiline_row
359+
csv = CSV.new(%Q{"a\nb",c\nabcde,"x\ny"\nc,d\n}, max_field_size: 4)
360+
assert_equal(["a\nb", "c"], csv.shift)
361+
error = assert_raise(CSV::MalformedCSVError) do
362+
csv.shift
363+
end
364+
assert_equal("Field size exceeded: 5 > 4 in line 2.",
365+
error.message)
366+
assert_equal(%Q{abcde,"x\ny"\n}, csv.line)
367+
assert_equal(["c", "d"], csv.shift)
368+
assert_equal(3, csv.lineno)
369+
end
370+
371+
def test_max_field_size_multiline_record_strip
372+
csv = CSV.new(%Q{abcde , "x\ny" \nc,d\n},
373+
max_field_size: 4, strip: true)
374+
error = assert_raise(CSV::MalformedCSVError) do
375+
csv.shift
376+
end
377+
assert_equal("Field size exceeded: 5 > 4 in line 1.",
378+
error.message)
379+
assert_equal(%Q{abcde , "x\ny" \n}, csv.line)
380+
assert_equal(["c", "d"], csv.shift)
381+
assert_equal(2, csv.lineno)
382+
end
383+
384+
def test_max_field_size_value_after_quoted_field
385+
csv = CSV.new(%Q{"abcde"junk,b\nc,d\n}, max_field_size: 4)
386+
error = assert_raise(CSV::MalformedCSVError) do
387+
csv.shift
388+
end
389+
assert_equal("Field size exceeded: 5 > 4 in line 1.",
390+
error.message)
391+
assert_equal(%Q{"abcde"junk,b\n}, csv.line)
392+
assert_equal(["c", "d"], csv.shift)
393+
assert_equal(2, csv.lineno)
394+
end
395+
396+
def test_max_field_size_unclosed_quote_liberal_parsing
397+
csv = CSV.new(%Q{abcde,"x\nc,d\n},
398+
max_field_size: 4, liberal_parsing: true)
399+
error = assert_raise(CSV::MalformedCSVError) do
400+
csv.shift
401+
end
402+
assert_equal("Field size exceeded: 5 > 4 in line 1.",
403+
error.message)
404+
assert_equal(%Q{abcde,"x\nc,d\n}, csv.line)
405+
assert_nil(csv.shift)
406+
assert_equal(1, csv.lineno)
407+
end
408+
332409
def test_row_sep_auto_cr
333410
assert_equal([["a"]], CSV.parse("a\r"))
334411
end

0 commit comments

Comments
 (0)