Skip to content

Commit 02a991b

Browse files
committed
Excape input text for csv export
1 parent 2edc80a commit 02a991b

File tree

1 file changed

+31
-9
lines changed

1 file changed

+31
-9
lines changed

lib/services/export_service.dart

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,25 @@ class ExportService {
2020
final SettingsService _settingsService = SettingsService.instance;
2121
final DatabaseService _databaseService = DatabaseService.instance;
2222

23+
/// Escapes a string for use in a CSV file.
24+
///
25+
/// If the string contains a comma, a double quote, or a newline character,
26+
/// it will be enclosed in double quotes. Any double quotes within the string
27+
/// will be replaced by two double quotes.
28+
String _csvEscape(String? text) {
29+
if (text == null) {
30+
return '';
31+
}
32+
// If the text contains a comma, a quote, or a newline, it needs to be quoted.
33+
if (text.contains(',') || text.contains('"') || text.contains('\n')) {
34+
// In a quoted field, any double-quote characters must be escaped by doubling them.
35+
final escapedText = text.replaceAll('"', '""');
36+
return '"$escapedText"';
37+
}
38+
// If no special characters are present, the text can be used as-is.
39+
return text;
40+
}
41+
2342
Future<ServiceResult> _getAndSetExportPath() async {
2443
final PermissionStatus status;
2544
if (Platform.isAndroid) {
@@ -146,22 +165,25 @@ class ExportService {
146165
// 3. CSV Rows
147166
for (final entry in newActions) {
148167
final action = entry.value;
149-
// Convert timestamp back to ISO string for the export file
150168
final timestampString =
151169
DateTime.fromMillisecondsSinceEpoch(action.timestamp)
152170
.toIso8601String();
153-
final row = [
154-
entry.key,
155-
action.actionType.name, // Use .name for the enum
156-
'"$timestampString"', // Quote the timestamp string
171+
172+
// Use a list to hold the properly escaped cell values for the current row
173+
final rowValues = <String>[
174+
// ID doesn't need escaping as it's a number.
175+
entry.key.toString(),
176+
// Escape all other string values to handle special characters.
177+
_csvEscape(action.actionType.name),
178+
_csvEscape(timestampString),
157179
];
158180

159-
// Add values for each dynamic answer column
181+
// Add values for each dynamic answer column, escaping each one.
160182
for (final key in sortedAnswerKeys) {
161-
// Append the answer if it exists, otherwise append an empty string
162-
row.add(action.answers?[key]?.toString() ?? '');
183+
final value = action.answers?[key]?.toString();
184+
rowValues.add(_csvEscape(value));
163185
}
164-
csvBuffer.writeln(row.join(','));
186+
csvBuffer.writeln(rowValues.join(','));
165187
}
166188

167189
// 4. Write file

0 commit comments

Comments
 (0)