Skip to content

Commit 549bb55

Browse files
committed
wip
1 parent dcb444b commit 549bb55

File tree

2 files changed

+42
-8
lines changed

2 files changed

+42
-8
lines changed

TablePro/Core/Services/ExportService.swift

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,22 @@ final class ExportService: ObservableObject {
238238
}
239239
}
240240

241+
/// Sanitize a name for use in SQL comments to prevent comment injection
242+
///
243+
/// Removes characters that could break out of SQL comments:
244+
/// - Newlines (could start new SQL statements)
245+
/// - Comment terminators (* /)
246+
private func sanitizeForSQLComment(_ name: String) -> String {
247+
var result = name
248+
// Replace newlines with spaces
249+
result = result.replacingOccurrences(of: "\n", with: " ")
250+
result = result.replacingOccurrences(of: "\r", with: " ")
251+
// Remove comment terminators (remove the asterisk-slash sequence)
252+
result = result.replacingOccurrences(of: "*/", with: "")
253+
result = result.replacingOccurrences(of: "--", with: "")
254+
return result
255+
}
256+
241257
// MARK: - File Helpers
242258

243259
/// Create a file at the given URL and return a FileHandle for writing
@@ -268,8 +284,10 @@ final class ExportService: ObservableObject {
268284
currentTable = table.qualifiedName
269285

270286
// Add table header comment if multiple tables
287+
// Sanitize name to prevent newlines from breaking the comment line
271288
if tables.count > 1 {
272-
try fileHandle.write(contentsOf: "# Table: \(table.qualifiedName)\n".toUTF8Data())
289+
let sanitizedName = sanitizeForSQLComment(table.qualifiedName)
290+
try fileHandle.write(contentsOf: "# Table: \(sanitizedName)\n".toUTF8Data())
273291
}
274292

275293
// Fetch all data from table
@@ -477,17 +495,31 @@ final class ExportService: ObservableObject {
477495
progress = 1.0
478496
}
479497

480-
/// Escape a string for JSON output
498+
/// Escape a string for JSON output per RFC 8259
499+
///
500+
/// Escapes:
501+
/// - Quotation mark, backslash (required)
502+
/// - Control characters U+0000 to U+001F (required by spec)
481503
private func escapeJSONString(_ string: String) -> String {
482504
var result = ""
505+
result.reserveCapacity(string.count)
483506
for char in string {
484507
switch char {
485508
case "\"": result += "\\\""
486509
case "\\": result += "\\\\"
487510
case "\n": result += "\\n"
488511
case "\r": result += "\\r"
489512
case "\t": result += "\\t"
490-
default: result.append(char)
513+
case "\u{08}": result += "\\b" // Backspace
514+
case "\u{0C}": result += "\\f" // Form feed
515+
default:
516+
// Escape other control characters (U+0000 to U+001F) as \uXXXX
517+
if let scalar = char.unicodeScalars.first,
518+
scalar.value < 0x20 {
519+
result += String(format: "\\u%04X", scalar.value)
520+
} else {
521+
result.append(char)
522+
}
491523
}
492524
}
493525
return result
@@ -578,8 +610,9 @@ final class ExportService: ObservableObject {
578610
let sqlOptions = table.sqlOptions
579611
let tableRef = qualifiedTableRef(for: table)
580612

613+
let sanitizedName = sanitizeForSQLComment(table.qualifiedName)
581614
try fileHandle.write(contentsOf: "-- --------------------------------------------------------\n".toUTF8Data())
582-
try fileHandle.write(contentsOf: "-- Table: \(table.qualifiedName)\n".toUTF8Data())
615+
try fileHandle.write(contentsOf: "-- Table: \(sanitizedName)\n".toUTF8Data())
583616
try fileHandle.write(contentsOf: "-- --------------------------------------------------------\n\n".toUTF8Data())
584617

585618
// DROP statement
@@ -597,9 +630,10 @@ final class ExportService: ObservableObject {
597630
}
598631
try fileHandle.write(contentsOf: "\n\n".toUTF8Data())
599632
} catch {
600-
let warningMessage = "Warning: failed to fetch DDL for table \(table.qualifiedName): \(error)"
633+
// Use sanitizedName (already defined above) for safe comment output
634+
let warningMessage = "Warning: failed to fetch DDL for table \(sanitizedName): \(error)"
601635
print(warningMessage)
602-
try fileHandle.write(contentsOf: "-- \(warningMessage)\n\n".toUTF8Data())
636+
try fileHandle.write(contentsOf: "-- \(sanitizeForSQLComment(warningMessage))\n\n".toUTF8Data())
603637
}
604638
}
605639

TablePro/Views/Export/ExportDialog.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,9 +341,9 @@ struct ExportDialog: View {
341341
let invalidChars = CharacterSet(charactersIn: "/\\:*?\"<>|")
342342
guard name.rangeOfCharacter(from: invalidChars) == nil else { return false }
343343

344-
// Prevent path traversal attempts where ".." is used as a path component
344+
// Prevent path traversal attempts and special directory names
345345
let isPathTraversalPattern =
346-
name == ".." ||
346+
name == "." || name == ".." || // Current/parent directory
347347
name.hasPrefix("../") || name.hasPrefix("..\\") ||
348348
name.hasSuffix("/..") || name.hasSuffix("\\..") ||
349349
name.contains("/../") || name.contains("\\..\\")

0 commit comments

Comments
 (0)