From 20b68b2bbcbd7a802b7c910ef06370e45dec241b Mon Sep 17 00:00:00 2001 From: MAEDA Go Date: Mon, 16 Feb 2026 22:08:51 +0900 Subject: [PATCH] CSV import fails with error for quoted fields containing newlines and CRLF line endings --- app/models/import.rb | 4 +++ ...ort_crlf_with_newline_in_quoted_header.csv | 3 ++ test/functional/imports_controller_test.rb | 29 +++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 test/fixtures/files/import_crlf_with_newline_in_quoted_header.csv diff --git a/app/models/import.rb b/app/models/import.rb index d5e8e0cbc..601b44235 100644 --- a/app/models/import.rb +++ b/app/models/import.rb @@ -72,6 +72,7 @@ class Import < ApplicationRecord content = read_file_head separator = [',', ';'].max_by {|sep| content.count(sep)} + newline = content.index("\r\n") ? "\r\n" : '' # blank means auto wrapper = ['"', "'"].max_by {|quote_char| content.count(quote_char)} guessed_encoding = Redmine::CodesetUtil.guess_encoding(content) @@ -89,6 +90,7 @@ class Import < ApplicationRecord self.settings.merge!( 'separator' => separator, + 'newline' => newline, 'wrapper' => wrapper, 'encoding' => encoding, 'date_format' => date_format, @@ -272,6 +274,8 @@ class Import < ApplicationRecord csv_options[:encoding] = 'bom|UTF-8' if csv_options[:encoding] == 'UTF-8' separator = settings['separator'].to_s csv_options[:col_sep] = separator if separator.size == 1 + newline = settings['newline'].to_s + csv_options[:row_sep] = newline unless newline.empty? wrapper = settings['wrapper'].to_s csv_options[:quote_char] = wrapper if wrapper.size == 1 diff --git a/test/fixtures/files/import_crlf_with_newline_in_quoted_header.csv b/test/fixtures/files/import_crlf_with_newline_in_quoted_header.csv new file mode 100644 index 000000000..a6f877752 --- /dev/null +++ b/test/fixtures/files/import_crlf_with_newline_in_quoted_header.csv @@ -0,0 +1,3 @@ +plain_header,"quoted_header +contains_LF" +foo,bar diff --git a/test/functional/imports_controller_test.rb b/test/functional/imports_controller_test.rb index f1c477019..d9be74b01 100644 --- a/test/functional/imports_controller_test.rb +++ b/test/functional/imports_controller_test.rb @@ -187,6 +187,35 @@ class ImportsControllerTest < Redmine::ControllerTest assert_select 'div#flash_error', /The file is not a CSV file or does not match the settings below \([[:print:]]+\)/ end + def test_post_settings_with_crlf_and_newline_in_quoted_header_should_not_fail + import = new_record(Import) do + post( + :create, + :params => { + :type => 'IssueImport', + :file => uploaded_test_file('import_crlf_with_newline_in_quoted_header.csv', 'text/csv') + } + ) + assert_response :found + end + assert_equal "\r\n", import.settings['newline'] + + post( + :settings, + :params => { + :id => import.to_param, + :import_settings => { + :separator => ',', + :wrapper => '"', + :encoding => 'UTF-8' + } + } + ) + assert_response :found + import.reload + assert_equal 1, import.total_items + end + def test_post_settings_with_no_data_row_should_display_error import = generate_import('import_issues_no_data_row.csv') -- 2.50.1