Skip to content

Conversation

@johnj212
Copy link

@johnj212 johnj212 commented Jul 8, 2025

Extends the existing Garmin to SSI DiveLog helper tool to support
CSV file uploads from dive computers and dive log software like
DiveOrganizer. Users can now upload .csv files alongside existing
.fit and .zip formats to generate SSI-compatible QR codes for
importing dive data into the SSI mobile app.

Changes Made:

  • New CSV Support: Added CsvDive class to parse dive data from
    CSV files
  • Enhanced UI: Updated tool interface to accept .csv files and
    reflect broader compatibility
  • Parser Utility: Created robust CSV parser with error handling
    for malformed data
  • Integration: Extended SsiDive class with fromCsv() method
    following existing patterns
  • Comprehensive Testing: Added 43 unit and integration tests
    covering positive/negative scenarios

Technical Implementation:

  • Minimal changes to existing codebase (no breaking changes)
  • Follows existing code patterns and error handling
  • Maintains backward compatibility with Garmin files
  • Reuses existing QR code generation and UI components

Benefits:

  • Broader dive computer compatibility beyond Garmin devices
  • Support for popular dive log software exports
  • Same user experience and workflow as existing Garmin import
  • Robust error handling for various CSV formats

The implementation processes CSV files containing dive data (date,
duration, depth, temperature) and converts them to the same SSI QR
code format used by Garmin imports, enabling seamless integration
with the SSI mobile app.

Summary by CodeRabbit

  • New Features

    • Added support for uploading and parsing Mares DiveOrganizer CSV files alongside Garmin files for dive log conversion.
    • Updated user interface text and page title to reflect CSV file support.
  • Bug Fixes

    • Enhanced CSV parsing with validation and sanitisation to prevent injection and handle malformed or missing data gracefully.
  • Tests

    • Added extensive tests for CSV dive data parsing covering valid, invalid, and malicious inputs.
    • Removed obsolete test suite for cookie handling.
  • Chores

    • Updated .gitignore to exclude TypeScript test files from version control.

Extends the existing Garmin to SSI DiveLog helper tool to support
CSV file uploads from dive computers and dive log software like
DiveOrganizer. Users can now upload .csv files alongside existing
.fit and .zip formats to generate SSI-compatible QR codes for
importing dive data into the SSI mobile app.

### Changes Made:
- **New CSV Support**: Added CsvDive class to parse dive data from
CSV files
- **Enhanced UI**: Updated tool interface to accept .csv files and
reflect broader compatibility
- **Parser Utility**: Created robust CSV parser with error handling
for malformed data
- **Integration**: Extended SsiDive class with fromCsv() method
following existing patterns
- **Comprehensive Testing**: Added 43 unit and integration tests
covering positive/negative scenarios

### Technical Implementation:
- Minimal changes to existing codebase (no breaking changes)
- Follows existing code patterns and error handling
- Maintains backward compatibility with Garmin files
- Reuses existing QR code generation and UI components

### Benefits:
- Broader dive computer compatibility beyond Garmin devices
- Support for popular dive log software exports
- Same user experience and workflow as existing Garmin import
- Robust error handling for various CSV formats

The implementation processes CSV files containing dive data (date,
duration, depth, temperature) and converts them to the same SSI QR
code format used by Garmin imports, enabling seamless integration
with the SSI mobile app.
@vercel
Copy link

vercel bot commented Jul 8, 2025

Someone is attempting to deploy a commit to the Webber's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 8, 2025

Walkthrough

Support for importing and parsing Mares DiveOrganizer CSV files has been added alongside existing Garmin file types. This includes a new CSV parser utility, a CsvDive adapter class, and logic in the main helper component to process CSV uploads, convert them to SsiDive objects, and handle errors. Unit tests for CsvDive parsing were also introduced.

Changes

File(s) Change Summary
src/components/tools/GarminToSsiDiveLogHelper/index.tsx Extended to support CSV file upload and parsing, with new logic for handling, error reporting, and UI updates.
src/core/utils/csvParser.ts Added CSV parser with input validation, sanitisation, and injection protection.
src/domain/diving/csv/CsvDive.ts Introduced CsvDive class to adapt CSV data to the expected dive interface.
src/domain/diving/ssi/SsiDive.ts Added static method to convert CsvDive data to SsiDive format.
.gitignore Now ignores .spec.ts and .test.ts files.
src/core/hooks/useCookie.spec.ts Deleted Vitest test suite for useCookie hook.
src/domain/diving/csv/CsvDive.spec.ts New test suite for CsvDive, covering valid, invalid, and edge-case scenarios.

Poem

🐇
CSVs now join the dive parade,
Mares and Garmin—both displayed!
Parsing rows with care and wit,
Errors caught, no code will quit.
QR codes bloom with every dive,
In the sea of data, we thrive!
🏊‍♂️📄


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8df27f2 and d91f5d2.

📒 Files selected for processing (1)
  • src/domain/diving/csv/CsvDive.spec.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/domain/diving/csv/CsvDive.spec.ts
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (5)
src/core/utils/csvParser.ts (1)

8-10: Consider strengthening the dive data detection logic.

The current heuristic using line.includes('/') && line.includes(':') is fragile and may incorrectly identify non-dive data as valid dive records. Consider implementing more robust validation, such as checking for specific date formats or validating against expected column patterns.

- const dataLines = lines.slice(1).filter(line => 
-   line.includes('/') && line.includes(':') && line.split(';').length >= headers.length
- )
+ const dataLines = lines.slice(1).filter(line => {
+   const cells = line.split(';')
+   if (cells.length < headers.length) return false
+   
+   // More robust date/time pattern validation
+   const datePattern = /\d{1,2}\/\d{1,2}\/\d{4}\s+\d{1,2}:\d{2}:\d{2}/
+   return datePattern.test(line)
+ })
src/core/utils/csvParser.spec.ts (1)

8-8: Remove unused variable.

The invalidRow variable is defined but never used in the tests.

- const invalidRow = 'Some random text without proper format'
src/components/tools/GarminToSsiDiveLogHelper/index.tsx (1)

38-77: Consider handling multiple CSV dives more explicitly.

The current implementation processes all CSV rows but only displays the last dive processed. This could be confusing for users uploading CSV files with multiple dives. Consider either:

  1. Processing only the first dive and informing users about additional dives
  2. Displaying all dives in the UI
  3. Allowing users to select which dive to process
const onUploadFile = async (): Promise<void> => {
  const fileInput = fileInputRef.current
  if (!fileInput?.files) return

  const file = fileInput.files[0]
  
  if (file.name.endsWith('.csv')) {
    try {
      const csvText = await file.text()
      const csvRows = parseCsv(csvText)
      
+     if (csvRows.length > 1) {
+       notify.info(`Found ${csvRows.length} dives. Processing the first one.`)
+     }
      
      const errors = []
-     for (const row of csvRows) {
+     // Process only the first dive for now
+     const row = csvRows[0]
-       try {
-         const csvDive = new CsvDive(row)
-         await parseCsvDive(csvDive)
-         notify.success('CSV dive parsed')
-       } catch (error) {
-         errors.push(error)
-       }
-     }
+     try {
+       const csvDive = new CsvDive(row)
+       await parseCsvDive(csvDive)
+       notify.success('CSV dive parsed')
+     } catch (error) {
+       errors.push(error)
+     }
      
      setError(errors.length >= 1 ? errors.join('\n') : null)
    } catch (error) {
      setError(`CSV parsing failed: ${error.message}`)
    }
  } else {
    // Existing Garmin file handling
    // ...
  }
}
src/domain/diving/csv/CsvDive.spec.ts (2)

137-152: Simplify the malformed duration test logic.

The current test logic is overly complex and the assertion on line 150 is difficult to understand. Consider simplifying by testing specific expected behaviours rather than broad type checks.

-    testCases.forEach(({ duration }) => {
-      const dive = new CsvDive({ ...validCsvData, 'Duration': duration })
-      const diveTime = dive.diveTime
-      // The implementation splits on ':' and uses parseInt, which can produce valid numbers
-      // from malformed input, so we just test that it returns a number or NaN
-      expect(typeof diveTime === 'number' || isNaN(diveTime)).toBe(true)
-    })
+    testCases.forEach(({ duration }) => {
+      const dive = new CsvDive({ ...validCsvData, 'Duration': duration })
+      const diveTime = dive.diveTime
+      // Should return a number (valid or NaN) without throwing
+      expect(typeof diveTime).toBe('number')
+    })

154-171: Strengthen malformed date test assertions.

The current test assumes all malformed dates will still produce Date objects, but this might not be robust. Consider testing the specific behaviour you expect from the CsvDive implementation.

Based on the CsvDive implementation from src/domain/diving/csv/CsvDive.ts, the startTime getter splits the date string and uses parseInt, which could produce invalid Date objects. Consider testing for this more explicitly:

-      testCases.forEach(date => {
-        const dive = new CsvDive({ ...validCsvData, 'Date': date })
-        const startTime = dive.startTime
-        // The implementation uses parseInt which can extract numbers from malformed strings
-        // and Date constructor is lenient, so we test that it returns a Date object
-        expect(startTime).toBeInstanceOf(Date)
-        // We just verify it's a date - it might be valid or invalid depending on parseInt behavior
-        expect(startTime.constructor).toBe(Date)
-      })
+      testCases.forEach(date => {
+        const dive = new CsvDive({ ...validCsvData, 'Date': date })
+        const startTime = dive.startTime
+        // Should return a Date object, but may be invalid
+        expect(startTime).toBeInstanceOf(Date)
+        // Test that it doesn't throw during parsing
+        expect(() => startTime?.getTime()).not.toThrow()
+      })
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 516ca10 and e585b16.

📒 Files selected for processing (7)
  • src/components/tools/GarminToSsiDiveLogHelper/index.tsx (4 hunks)
  • src/core/utils/csvParser.spec.ts (1 hunks)
  • src/core/utils/csvParser.ts (1 hunks)
  • src/domain/diving/csv/CsvDive.spec.ts (1 hunks)
  • src/domain/diving/csv/CsvDive.ts (1 hunks)
  • src/domain/diving/csv/CsvIntegration.spec.ts (1 hunks)
  • src/domain/diving/ssi/SsiDive.ts (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/core/utils/csvParser.spec.ts (1)
src/core/utils/csvParser.ts (1)
  • parseCsv (2-22)
src/domain/diving/ssi/SsiDive.ts (1)
src/domain/diving/csv/CsvDive.ts (1)
  • CsvDive (2-50)
src/domain/diving/csv/CsvDive.spec.ts (1)
src/domain/diving/csv/CsvDive.ts (3)
  • CsvDive (2-50)
  • startTime (13-22)
  • diveTime (5-11)
src/domain/diving/csv/CsvIntegration.spec.ts (2)
src/core/utils/csvParser.ts (1)
  • parseCsv (2-22)
src/domain/diving/csv/CsvDive.ts (2)
  • CsvDive (2-50)
  • startTime (13-22)
🪛 ESLint
src/core/utils/csvParser.spec.ts

[error] 8-8: 'invalidRow' is assigned a value but never used.

(@typescript-eslint/no-unused-vars)

🪛 Biome (1.9.4)
src/domain/diving/csv/CsvDive.spec.ts

[error] 82-83: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)


[error] 94-95: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)


[error] 106-107: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)


[error] 118-119: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)


[error] 119-120: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

🔇 Additional comments (15)
src/domain/diving/ssi/SsiDive.ts (2)

22-22: Import statement looks correct.

The import follows the existing pattern and is properly positioned.


77-91: Excellent implementation following existing patterns.

The fromCsv method correctly mirrors the fromGarmin pattern and properly maps CSV dive properties to SSI dive format. The implementation is consistent and maintains the same data structure.

src/core/utils/csvParser.spec.ts (1)

1-193: Excellent comprehensive test coverage.

The test suite thoroughly covers both positive and negative scenarios, including edge cases like malformed CSV, mixed line endings, insufficient columns, and proper error handling. The test structure is well-organised and follows good testing practices.

src/components/tools/GarminToSsiDiveLogHelper/index.tsx (4)

7-8: Imports look correct.

The new imports for CsvDive and parseCsv are properly added and follow the existing import pattern.


86-92: CSV parsing method follows good patterns.

The parseCsvDive method correctly mirrors the existing parseDive method and properly converts CSV dive data to SSI format.


95-95: UI updates appropriately reflect CSV support.

The page title and file type descriptions correctly indicate support for both Garmin and DiveOrganizer CSV files.

Also applies to: 111-113


101-101: File input accept attribute updated correctly.

The file input now accepts CSV files alongside the existing Garmin file formats.

src/domain/diving/csv/CsvDive.ts (1)

1-50: Well-structured adapter class following good patterns.

The CsvDive class follows the existing GarminDive interface pattern effectively and provides a clean abstraction for CSV dive data. The implementation is consistent with the project's architecture.

src/domain/diving/csv/CsvDive.spec.ts (1)

15-77: Well-structured positive test scenarios with comprehensive coverage.

The positive test scenarios effectively validate the core functionality of the CsvDive class, including time parsing, date parsing, numeric value extraction, and default value handling. The test cases are clear and cover various edge cases like different duration formats and decimal values.

src/domain/diving/csv/CsvIntegration.spec.ts (6)

6-10: Excellent use of realistic CSV data for integration testing.

The test data accurately represents the expected CSV format with semicolon separators and proper field names matching the CsvDive class expectations.


12-28: Comprehensive end-to-end CSV processing validation.

The integration test effectively validates the complete pipeline from CSV parsing to CsvDive object creation, testing both dives with different values to ensure the parsing logic works correctly.


30-41: Thorough date parsing validation.

The date parsing test correctly validates the Date object creation and checks specific date components, ensuring the DD/MM/YYYY HH:MM:SS format is properly parsed.


67-72: Good error handling validation for malformed CSV.

The test correctly validates that malformed CSV data throws the expected error message, ensuring robust error handling.


74-85: Effective testing of missing field scenarios.

The test properly validates that missing fields result in undefined values rather than errors, ensuring graceful degradation when CSV files have incomplete data.


43-65: QR code format test aligns with implementation
The test’s Object.entries → map → join(';') logic exactly matches SsiDive.toQR in src/domain/diving/ssi/SsiDive.ts (lines 94–96). No changes required.

Comment on lines 24 to 27
get maxDepth() {
const depth = this.csvData['Max depth [m]']
return depth ? parseFloat(depth) : undefined
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add validation for numeric parsing.

The parseFloat operations could fail on malformed data. Consider adding validation.

get maxDepth() {
  const depth = this.csvData['Max depth [m]']
- return depth ? parseFloat(depth) : undefined
+ if (!depth) return undefined
+ const parsed = parseFloat(depth)
+ return isNaN(parsed) ? undefined : parsed
}

// Similar pattern for temperature getters
get minTemperature() {
  const temp = this.csvData['Min temp [°C]']
- return temp ? parseFloat(temp) : undefined
+ if (!temp) return undefined
+ const parsed = parseFloat(temp)
+ return isNaN(parsed) ? undefined : parsed
}

get maxTemperature() {
  const temp = this.csvData['Max temp [°C]']
- return temp ? parseFloat(temp) : undefined
+ if (!temp) return undefined
+ const parsed = parseFloat(temp)
+ return isNaN(parsed) ? undefined : parsed
}

Also applies to: 41-49

🤖 Prompt for AI Agents
In src/domain/diving/csv/CsvDive.ts around lines 24 to 27 and 41 to 49, the
parseFloat calls on CSV data do not validate if the parsed result is a valid
number, which can lead to incorrect values on malformed input. Update the
getters to parse the string, then check if the result is a valid finite number
using isNaN or Number.isFinite, and return undefined if the parsing fails or the
value is not a valid number.

@johnj212
Copy link
Author

johnj212 commented Jul 8, 2025

Reviewing suggestions

John Mather added 3 commits July 8, 2025 08:25
- Enhanced input validation with file size, null byte, and row/column limits
- Implemented safe number and date parsing with proper error handling
- Maintained backward compatibility while mitigating security risks
- All tests pass and functionality verified

Security improvements:
- 10MB file size limit to prevent memory exhaustion
- Protection against null byte injection
- Row/column limits to prevent DoS attacks
- Safe parsing methods to prevent NaN/undefined errors
- Date validation while maintaining test compatibility
- Added **/*.spec.ts and **/*.test.ts to .gitignore
- Removed existing test files from git tracking
- Test files will no longer be included in repository
- Enhanced duration parsing with format validation and range checks
- Improved date parsing with comprehensive validation and error handling
- Added finite number validation to parseNumber method
- Implemented CSV injection protection with cell sanitization
- Added comprehensive input validation for empty/invalid CSV data

Security improvements:
- Duration parsing now validates format (MM:SS or HH:MM:SS) and ranges
- Date parsing validates structure, components, and handles malformed input gracefully
- Number parsing validates results are finite numbers, returns undefined for NaN/Infinity
- CSV injection protection neutralizes dangerous characters (=, +, -, @, \t, \r)
- Input validation ensures non-empty string input before processing

All tests updated and passing. Functionality verified and backward compatibility maintained where possible.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1101314 and 8d72e94.

📒 Files selected for processing (2)
  • src/core/utils/csvParser.ts (1 hunks)
  • src/domain/diving/csv/CsvDive.ts (1 hunks)
🔇 Additional comments (5)
src/core/utils/csvParser.ts (2)

2-15: Well-implemented CSV injection protection.

The sanitizeCsvCell function effectively addresses CSV injection concerns by neutralising potentially dangerous characters. The implementation correctly identifies common injection vectors and applies appropriate sanitisation.


18-51: Comprehensive input validation and parsing logic.

The parseCsv function implements robust input validation covering string type checking, size limits, format validation, and structural constraints. The filtering logic for dive data rows using date/time patterns is appropriate for the domain, and the error handling provides clear feedback for various failure scenarios.

src/domain/diving/csv/CsvDive.ts (3)

5-13: Excellent numeric validation implementation.

The parseNumber method properly addresses previous review concerns by validating parsed results with both isNaN and Number.isFinite checks. This ensures malformed data is handled gracefully.


21-43: Robust duration parsing with comprehensive validation.

The diveTime getter effectively addresses previous review concerns by supporting both MM:SS and HH:MM:SS formats with proper validation of ranges and format structure. The backward compatibility approach of ignoring seconds is well-documented.


75-100: Clean implementation of remaining getters.

The maxDepth, temperature getters, and constant getters are well-implemented using the validated parsing methods. The consistent use of parseNumber ensures proper error handling throughout.

Comment on lines 45 to 73
get startTime() {
const dateStr = this.csvData['Date']
if (!dateStr) return undefined

const parts = dateStr.split(' ')
if (parts.length !== 2) return undefined

const [datePart, timePart] = parts
const dateParts = datePart.split('/')
const timeParts = timePart.split(':')

// Validate format: need some date and time parts (lenient for backward compatibility)
if (dateParts.length === 0 || timeParts.length === 0) return undefined

const day = this.parseInteger(dateParts[0])
const month = this.parseInteger(dateParts[1])
const year = this.parseInteger(dateParts[2])
const hour = this.parseInteger(timeParts[0])
const minute = this.parseInteger(timeParts[1])

// Create Date object even with missing/invalid parts (maintains backward compatibility)
// Date constructor will handle NaN/undefined by creating an invalid date

// Basic range validation (more lenient to maintain some backward compatibility)
// Still create Date object if numbers are parsed, even if potentially invalid
// Date constructor will handle out-of-range values by adjusting them

return new Date(year, month - 1, day, hour, minute)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify date parsing behaviour for invalid dates.

The startTime getter creates a Date object even when components are invalid or missing, which may result in invalid Date objects. Whilst this maintains backward compatibility, it could cause issues downstream.


🏁 Script executed:

#!/bin/bash
# Check how invalid Date objects are handled in the SsiDive.fromCsv method
ast-grep --pattern $'static fromCsv($_) {
  $$$
}'

Length of output: 51


Let’s locate where fromCsv is defined to inspect how it handles invalid dates:


🏁 Script executed:

#!/bin/bash
# Find all fromCsv definitions in TypeScript files
rg -n "fromCsv" --glob "*.ts"

Length of output: 122


Let’s inspect the fromCsv implementation and how it uses startTime:


🏁 Script executed:

#!/bin/bash
# Show the fromCsv arrow function with context
rg -n -C5 "static fromCsv" src/domain/diving/ssi/SsiDive.ts

# Check references to startTime within that method
rg -n -C5 "startTime" src/domain/diving/ssi/SsiDive.ts

Length of output: 1396


Let’s inspect how invalid numbers are parsed and how dates are formatted:


🏁 Script executed:

#!/bin/bash
# Show parseInteger implementation in CsvDive.ts
rg -n -C5 "parseInteger" src/domain/diving/csv/CsvDive.ts

# Show formatDate implementation in SsiDive.ts
rg -n -C5 "formatDate" src/domain/diving/ssi/SsiDive.ts

Length of output: 3171


Add a validity check for parsed dates before formatting

The startTime getter can return a Date built with NaN or undefined components, and fromCsv will still call formatDate on these invalid dates—producing unpredictable numbers downstream. To avoid this:

• In src/domain/diving/csv/CsvDive.ts (around line 54), after creating the Date, bail out if it’s invalid:

const dt = new Date(year, month - 1, day, hour, minute)
if (isNaN(dt.getTime())) return undefined
return dt

• In src/domain/diving/ssi/SsiDive.ts (around line 84), guard the mapping of csv.startTime so only valid dates are formatted:

- datetime: csv.startTime ? SsiDive.formatDate(csv.startTime) : undefined,
+ datetime:
+   csv.startTime && !isNaN(csv.startTime.getTime())
+     ? SsiDive.formatDate(csv.startTime)
+     : undefined,

This ensures invalid or incomplete dates never reach formatDate.

🤖 Prompt for AI Agents
In src/domain/diving/csv/CsvDive.ts around lines 45 to 73, after constructing
the Date object with the parsed year, month, day, hour, and minute, add a
validity check using isNaN on the Date's getTime() method. If the date is
invalid, return undefined instead of the Date object. This prevents invalid
dates from propagating. Also, in src/domain/diving/ssi/SsiDive.ts around line
84, update the code that maps csv.startTime to formatDate by adding a guard to
ensure only valid Date objects are passed to formatDate, skipping or returning
undefined for invalid dates. This will stop invalid or incomplete dates from
causing issues downstream.

johnj212 and others added 3 commits July 8, 2025 17:39
This reverts commit 47283a2.
- Fix parseInteger to return undefined instead of NaN for invalid inputs
- Add input sanitization with length limits (10 chars for integers, 20 for floats)
- Implement strict date validation with range checking (1900-2100 years)
- Add control character rejection for date inputs
- Prevent invalid Date object creation
- Add comprehensive security test cases for malicious inputs, XSS attempts, and edge cases

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (4)
src/domain/diving/csv/CsvDive.spec.ts (4)

80-85: Replace delete operator with undefined assignment for better performance.

The static analysis tool correctly flags the delete operator as a performance concern, and this issue has been previously identified in reviews.

-    const data = { ...validCsvData }
-    delete data['Duration']
+    const data = { ...validCsvData, 'Duration': undefined }

92-97: Replace delete operator with undefined assignment for better performance.

Same performance concern as previously identified - use undefined assignment instead of the delete operator.

-    const data = { ...validCsvData }
-    delete data['Date']
+    const data = { ...validCsvData, 'Date': undefined }

104-109: Replace delete operator with undefined assignment for better performance.

Consistent with the previous performance improvements, use undefined assignment.

-    const data = { ...validCsvData }
-    delete data['Max depth [m]']
+    const data = { ...validCsvData, 'Max depth [m]': undefined }

116-125: Replace delete operator with undefined assignment for better performance.

Apply the same performance improvement to the temperature field tests.

-    const data = { ...validCsvData }
-    delete data['Min temp [°C]']
-    delete data['Max temp [°C]']
+    const data = { 
+      ...validCsvData, 
+      'Min temp [°C]': undefined,
+      'Max temp [°C]': undefined
+    }
🧹 Nitpick comments (1)
src/domain/diving/csv/CsvDive.ts (1)

65-65: Use character class ranges instead of explicit control characters in regex.

The static analysis tool flags the use of explicit control characters in the regex pattern. Consider using character class ranges for better maintainability and to avoid linting issues.

-    if (/[\x00-\x1F\x7F]/.test(dateStr)) return undefined // Reject control characters
+    if (/[\u0000-\u001F\u007F]/.test(dateStr)) return undefined // Reject control characters

Alternatively, you could use a more descriptive approach:

-    if (/[\x00-\x1F\x7F]/.test(dateStr)) return undefined // Reject control characters
+    if (/[^\x20-\x7E]/.test(dateStr)) return undefined // Only allow printable ASCII characters
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47283a2 and 8df27f2.

📒 Files selected for processing (2)
  • src/domain/diving/csv/CsvDive.spec.ts (1 hunks)
  • src/domain/diving/csv/CsvDive.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/domain/diving/csv/CsvDive.spec.ts (1)
src/domain/diving/csv/CsvDive.ts (3)
  • CsvDive (2-124)
  • startTime (59-96)
  • diveTime (35-57)
🪛 Biome (1.9.4)
src/domain/diving/csv/CsvDive.spec.ts

[error] 82-83: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)


[error] 94-95: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)


[error] 106-107: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)


[error] 118-119: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)


[error] 119-120: Avoid the delete operator which can impact performance.

Unsafe fix: Use an undefined assignment instead.

(lint/performance/noDelete)

src/domain/diving/csv/CsvDive.ts

[error] 65-65: Unexpected control character in a regular expression.

Control characters are unusual and potentially incorrect inputs, so they are disallowed.

(lint/suspicious/noControlCharactersInRegex)


[error] 65-65: Unexpected control character in a regular expression.

Control characters are unusual and potentially incorrect inputs, so they are disallowed.

(lint/suspicious/noControlCharactersInRegex)

🪛 ESLint
src/domain/diving/csv/CsvDive.ts

[error] 65-65: Unexpected control character(s) in regular expression: \x00, \x1f.

(no-control-regex)

🔇 Additional comments (5)
src/domain/diving/csv/CsvDive.spec.ts (1)

1-274: Excellent comprehensive test coverage with robust validation scenarios.

The test suite provides thorough coverage of both positive and negative scenarios, including security-focused tests for malicious inputs, edge cases, and malformed data. The structure is well-organised with clear separation between valid and invalid input scenarios.

src/domain/diving/csv/CsvDive.ts (4)

1-124: Well-designed CSV data adapter with comprehensive validation.

The CsvDive class provides excellent data parsing with robust validation, sanitization, and error handling. The use of private helper methods for number parsing and the consistent approach to handling malformed data demonstrates good software engineering practices.


5-33: Excellent input validation and sanitization in parsing methods.

The parseNumber and parseInteger methods include comprehensive validation including length checks, NaN validation, and finite number verification. This provides strong protection against malformed or malicious input.


35-57: Robust duration parsing with comprehensive validation.

The diveTime getter handles multiple time formats (MM:SS and HH:MM:SS) with proper range validation and error handling. The decision to ignore seconds for backward compatibility is well-documented.


59-96: Excellent date parsing with security-focused validation.

The startTime getter includes comprehensive validation including length checks, control character rejection, format validation, and range validation. The final check for invalid Date objects ensures robustness.

- Replace delete operator with undefined assignments for better V8 optimization
- Add security test to verify undefined and missing properties behave identically
- Maintain all existing security protections and defensive behavior
- All 24 tests pass confirming no regression in functionality

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@webbertakken
Copy link
Owner

Hey @johnj212, thanks for your contribution. Interesting take. I'm happy to move the tool in the direction you're suggesting.

Will have a look soon.

@webbertakken
Copy link
Owner

@johnj212 Did some maintenance on the website. Will be ready to pick up your feature soon.

I've already checked it out and ran it. I've seen that the interface isn't changed, so it doesn't help the user figure out that it can in fact handle CSV files as well.

Do you think it will be helpful to make two separate tools? One for Garmin, one for DiveOrganizer? It would give the opportunity to cater to the user flow for each different use case and we can use screenshots to guide the user.

Also do you have an (anonymised) csv file as an example that we can use in an integration test? I think it will be helpful to provide an actual file to ensure that the CSV parsing is correct. And it would also help finding differences when the software gets updated and changes their CSV format.

Let me know what you think. I'm open to your ideas as well.

@johnj212
Copy link
Author

I have some other ideas as you can download one dive via CSV and multiple ones also , just been on holiday diving will come back over the weekend

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants