Skip to content

Commit 29cebcc

Browse files
Show error if selected CSV file contains null character (#982)
1 parent 3a9a6f0 commit 29cebcc

File tree

6 files changed

+63
-1
lines changed

6 files changed

+63
-1
lines changed

src/components/entity/upload.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ const parseEntities = async (file, headerResults, signal) => {
257257
warnings.value = results.warnings;
258258
};
259259
const selectFile = (file) => {
260+
alert.blank();
260261
headerErrors.value = null;
261262
dataError.value = null;
262263

src/locales/en.json5

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,7 @@
436436
"csv": {
437437
// {message} is a description of the problem.
438438
"readError": "There was a problem reading your file: {message}",
439+
"invalidCSV": "The file “{name}” is not a valid .csv file. It cannot be read.",
439440
// {row} is a row number. {message} is a description of the problem.
440441
"rowError": "There is a problem on row {row} of the file: {message}",
441442
// This is an error that is shown for a spreadsheet. The field may be any

src/util/csv.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ export const parseCSVHeader = async (i18n, file, signal = undefined) => {
8282
preview: 1
8383
});
8484
const columns = data.length !== 0 ? data[0] : [];
85+
// Make a simple try at detecting a binary file by searching for a null
86+
// character. We do that in order to avoid displaying unintelligible binary
87+
// data to the user. Also, Backend would probably reject a null character
88+
// that's sent to it.
89+
if (columns.some(column => column.includes('\0')))
90+
throw new Error(i18n.t('util.csv.invalidCSV', { name: file.name }));
8591
const unhandledErrors = errors.filter(error =>
8692
error.code !== 'UndetectableDelimiter');
8793
if (unhandledErrors.length === 0) {
@@ -200,6 +206,12 @@ export const parseCSV = async (i18n, file, columns, options = {}) => {
200206
throw new Error(i18n.tc('util.csv.dataWithoutHeader', columns.length, counts));
201207
}
202208

209+
if (values.some(value => value.includes('\0'))) {
210+
const error = new Error(i18n.t('util.csv.invalidCSV', { name: file.name }));
211+
error.row = NaN;
212+
throw error;
213+
}
214+
203215
data.push(transformRow(values, columns));
204216
for (const warning of warnings) warning.pushRow(values, index, columns);
205217
};
@@ -235,6 +247,9 @@ export const parseCSV = async (i18n, file, columns, options = {}) => {
235247
worker: true
236248
});
237249
} catch (error) {
250+
// Mention the row number in the error message unless the `row` property of
251+
// the error has been set to NaN.
252+
if (Number.isNaN(error.row)) throw error;
238253
throw new Error(i18n.t('util.csv.rowError', {
239254
message: error.message,
240255
row: i18n.n((error.row ?? rowIndex) + 1, 'default')

test/components/entity/upload.spec.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,38 @@ describe('EntityUpload', () => {
177177
});
178178
});
179179

180+
describe('binary file', () => {
181+
beforeEach(() => {
182+
testData.extendedDatasets.createPast(1);
183+
});
184+
185+
it('shows an alert for a null character in the header', async () => {
186+
const modal = await showModal();
187+
await selectFile(modal, createCSV('f\0o'));
188+
modal.should.alert('danger', 'The file “my_data.csv” is not a valid .csv file. It cannot be read.');
189+
modal.findComponent(EntityUploadHeaderErrors).exists().should.be.false();
190+
});
191+
192+
it('hides the alert after a valid file is selected', async () => {
193+
const modal = await showModal();
194+
await selectFile(modal, createCSV('f\0o'));
195+
await selectFile(modal);
196+
modal.should.not.alert();
197+
modal.findComponent(EntityUploadPopup).exists().should.be.true();
198+
});
199+
200+
// This is not necessarily the ideal behavior. Showing an alert would be
201+
// more consistent with what happens for a null character in the header.
202+
// This test documents the current expected behavior.
203+
it('renders EntityUploadDataError for a null character after header', async () => {
204+
const modal = await showModal();
205+
await selectFile(modal, createCSV('label\nf\0o'));
206+
const { message } = modal.getComponent(EntityUploadDataError).props();
207+
message.should.equal('The file “my_data.csv” is not a valid .csv file. It cannot be read.');
208+
modal.should.not.alert();
209+
});
210+
});
211+
180212
describe('warnings', () => {
181213
beforeEach(() => {
182214
testData.extendedDatasets.createPast(1, {

test/unit/csv.spec.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,12 @@ describe('util/csv', () => {
4848
});
4949
});
5050

51-
describe('error', () => {
51+
it('returns a rejected promise if there is a null character', () => {
52+
const promise = parseCSVHeader(i18n, createCSV('f\0o,bar'));
53+
return promise.should.be.rejectedWith('The file “my_data.csv” is not a valid .csv file. It cannot be read.');
54+
});
55+
56+
describe('Papa Parse error', () => {
5257
it('returns an error for a missing quote', async () => {
5358
const { errors } = await parseCSVHeader(i18n, createCSV('"a\n1'));
5459
errors.length.should.equal(1);
@@ -119,6 +124,11 @@ describe('util/csv', () => {
119124
});
120125
});
121126

127+
it('returns a rejected promise if there is a null character', () => {
128+
const promise = parseCSV(i18n, createCSV('a\nf\0o'), ['a']);
129+
return promise.should.be.rejectedWith('The file “my_data.csv” is not a valid .csv file. It cannot be read.');
130+
});
131+
122132
describe('number of cells', () => {
123133
it('allows a row to be ragged', async () => {
124134
const csv = createCSV('a,b\n1,2\n3');

transifex/strings_en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,9 @@
918918
"string": "There was a problem reading your file: {message}",
919919
"developer_comment": "{message} is a description of the problem."
920920
},
921+
"invalidCSV": {
922+
"string": "The file “{name}” is not a valid .csv file. It cannot be read."
923+
},
921924
"rowError": {
922925
"string": "There is a problem on row {row} of the file: {message}",
923926
"developer_comment": "{row} is a row number. {message} is a description of the problem."

0 commit comments

Comments
 (0)