Skip to content

fix: parse comma-separated entries in file (-l) and stdin input#936

Open
Deducer wants to merge 2 commits intoprojectdiscovery:mainfrom
Deducer:fix/comma-separated-list-input
Open

fix: parse comma-separated entries in file (-l) and stdin input#936
Deducer wants to merge 2 commits intoprojectdiscovery:mainfrom
Deducer:fix/comma-separated-list-input

Conversation

@Deducer
Copy link

@Deducer Deducer commented Mar 2, 2026

Summary

Fixes #859

The -u flag uses CommaSeparatedStringSliceOptions from goflags which auto-splits comma-separated values, but file (-l) and stdin input paths read lines via bufio.Scanner and pass them directly to processInputItem() without splitting on commas. This means a file like:

scanme.sh,example.com,test.com

gets treated as a single host scanme.sh,example.com,test.com instead of three separate hosts.

Changes

  • Added processCommaSeparatedInput() method that splits comma-delimited input before calling processInputItem() for each entry
  • Applied the fix to both file (-l) and stdin input paths in normalizeAndQueueInputs()
  • Handles whitespace around commas (e.g., foo.com , bar.com)
  • Falls through to processInputItem() directly when no comma is present (zero overhead for non-comma input)

Before

$ echo "scanme.sh,example.com" | tlsx
# → tries to connect to "scanme.sh,example.com" as a single host (fails)

After

$ echo "scanme.sh,example.com" | tlsx
# → scans scanme.sh and example.com separately

Same fix applies to -l file input.

Test plan

  • Added 4 new unit tests covering:
    • Basic comma-separated splitting (a,b,c → 3 hosts)
    • Spaces around commas (a , b , c → 3 hosts, trimmed)
    • Single input passthrough (no comma, no behavior change)
    • Comma-separated with multiple ports (cross-product)
  • All existing tests pass
  • go build -v ./... clean

Summary by CodeRabbit

  • New Features

    • Enabled parsing of comma-separated targets in input lines, allowing multiple hosts per line with robust whitespace handling and improved handling of read errors from input sources.
  • Tests

    • Added unit tests covering comma-separated input parsing across formatting scenarios (multiple hosts, surrounding spaces, single host, multiple ports).

@neo-by-projectdiscovery-dev
Copy link

neo-by-projectdiscovery-dev bot commented Mar 2, 2026

Neo - PR Security Review

No security issues found

Highlights

  • Adds comma-separated input parsing for file (-l) and stdin to match existing -u flag behavior
  • Includes comprehensive unit tests covering edge cases (spaces, single input, multiple ports)
  • Makes input handling consistent across all input methods

Comment @neo help for available commands. · Open in Neo

@coderabbitai
Copy link

coderabbitai bot commented Mar 2, 2026

Walkthrough

Adds Runner.processCommaSeparatedInput to split comma-separated input lines, trim and ignore empty entries, and queue each item. normalizeAndQueueInputs now calls this helper for lines from list files and stdin. New unit tests validate comma-separated parsing; some tests are duplicated in the diff.

Changes

Cohort / File(s) Summary
Core Input Processing
internal/runner/runner.go
Added func (r *Runner) processCommaSeparatedInput(input string, inputs chan taskInput); updated normalizeAndQueueInputs to call it for lines read from -l files and stdin, enabling multiple comma-separated targets per line and preserving per-item processing.
Unit Tests
internal/runner/runner_test.go
Added tests covering comma-separated inputs (with/without spaces), single input, and multiple ports combinations. The diff contains duplicated test declarations (repeated signatures).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nibble commas, one by one,
Splitting lines until the job is done.
Hosts trimmed neat in tidy rows,
Queued and ready — off it goes.
Hooray for parsing, hop and run!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding comma-separated entry parsing to file and stdin input sources.
Linked Issues check ✅ Passed All coding objectives from issue #859 are met: comma-separated parsing added to file/stdin input, individual processing of entries, existing behavior preserved, and parity with -u flag achieved.
Out of Scope Changes check ✅ Passed All changes are within scope: new processCommaSeparatedInput method, updated normalizeAndQueueInputs, corresponding unit tests, and error handling—all directly supporting the stated objective.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@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.

🧹 Nitpick comments (1)
internal/runner/runner_test.go (1)

234-237: Consider moving defer close(inputs) before the function call.

While functionally correct, placing defer after the main call is unconventional. The idiomatic pattern is to register defer statements at the beginning of the function scope. This applies to all four new tests.

♻️ Suggested refactor
 	go func() {
+		defer close(inputs)
 		runner.processCommaSeparatedInput(line, inputs)
-		defer close(inputs)
 	}()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/runner/runner_test.go` around lines 234 - 237, Move the defer
close(inputs) to the start of each goroutine before calling
runner.processCommaSeparatedInput to follow idiomatic Go: in the anonymous
goroutine used in the tests (where inputs channel is closed), place defer
close(inputs) as the first statement in the goroutine body so the channel is
guaranteed to be closed at goroutine exit and the defer is registered before
invoking runner.processCommaSeparatedInput; update this in all four new tests
referencing the anonymous goroutine and runner.processCommaSeparatedInput.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/runner/runner_test.go`:
- Around line 234-237: Move the defer close(inputs) to the start of each
goroutine before calling runner.processCommaSeparatedInput to follow idiomatic
Go: in the anonymous goroutine used in the tests (where inputs channel is
closed), place defer close(inputs) as the first statement in the goroutine body
so the channel is guaranteed to be closed at goroutine exit and the defer is
registered before invoking runner.processCommaSeparatedInput; update this in all
four new tests referencing the anonymous goroutine and
runner.processCommaSeparatedInput.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d13b67f and d808bfa.

📒 Files selected for processing (2)
  • internal/runner/runner.go
  • internal/runner/runner_test.go

The -u flag uses CommaSeparatedStringSliceOptions which auto-splits
comma-separated values, but file (-l) and stdin input read lines as-is
without splitting on commas. This adds processCommaSeparatedInput() to
split comma-delimited entries before processing, making -l behavior
consistent with -u.

Fixes projectdiscovery#859
@Deducer Deducer force-pushed the fix/comma-separated-list-input branch from d808bfa to 658b8f7 Compare March 2, 2026 19:02
Copy link

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/runner/runner.go (1)

439-455: ⚠️ Potential issue | 🟠 Major

Check Scanner.Err() after both loops to prevent silent drops.

If scanning fails (including oversized tokens), processing stops silently and targets are skipped without feedback.

🔧 Proposed fix
 		scanner := bufio.NewScanner(file)
 		for scanner.Scan() {
 			text := scanner.Text()
 			if text != "" {
 				r.processCommaSeparatedInput(text, inputs)
 			}
 		}
+		if err := scanner.Err(); err != nil {
+			return errkit.Wrap(err, "could not read input file")
+		}
 	}
 	if r.hasStdin {
 		scanner := bufio.NewScanner(os.Stdin)
 		for scanner.Scan() {
 			text := scanner.Text()
 			if text != "" {
 				r.processCommaSeparatedInput(text, inputs)
 			}
 		}
+		if err := scanner.Err(); err != nil {
+			return errkit.Wrap(err, "could not read stdin")
+		}
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/runner/runner.go` around lines 439 - 455, Both bufio.Scanner loops
can fail silently; after each loop (the scanner created for file and the one for
os.Stdin when r.hasStdin) check scanner.Err() and handle any error instead of
ignoring it. Update the code surrounding the scanner usage that invokes
r.processCommaSeparatedInput to call scanner.Err() after the for scanner.Scan()
loop and propagate or log the error (e.g., return it from the enclosing function
or call the runner's logger) so oversized tokens or IO errors don't cause silent
drops; ensure you reference the same scanner variables and preserve existing
behavior when err == nil.
🧹 Nitpick comments (1)
internal/runner/runner.go (1)

488-499: Normalize single-item input with TrimSpace() before passthrough.

Right now only comma-split entries are trimmed. Trimming upfront makes single-line and comma-line behavior consistent.

♻️ Proposed refactor
 func (r *Runner) processCommaSeparatedInput(input string, inputs chan taskInput) {
+	input = strings.TrimSpace(input)
+	if input == "" {
+		return
+	}
 	if strings.Contains(input, ",") {
 		for _, item := range strings.Split(input, ",") {
 			item = strings.TrimSpace(item)
 			if item != "" {
 				r.processInputItem(item, inputs)
 			}
 		}
 		return
 	}
 	r.processInputItem(input, inputs)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/runner/runner.go` around lines 488 - 499, The function
processCommaSeparatedInput trims only comma-split items, so a single-item input
with surrounding whitespace bypasses trimming; update processCommaSeparatedInput
to call strings.TrimSpace(input) at the start, return early if the trimmed input
is empty, then proceed to split on commas and call
r.processInputItem(trimmedItem, inputs) for each non-empty trimmed piece (and
for the single-item path use the already-trimmed input) to make single-line and
comma-separated behavior consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@internal/runner/runner.go`:
- Around line 439-455: Both bufio.Scanner loops can fail silently; after each
loop (the scanner created for file and the one for os.Stdin when r.hasStdin)
check scanner.Err() and handle any error instead of ignoring it. Update the code
surrounding the scanner usage that invokes r.processCommaSeparatedInput to call
scanner.Err() after the for scanner.Scan() loop and propagate or log the error
(e.g., return it from the enclosing function or call the runner's logger) so
oversized tokens or IO errors don't cause silent drops; ensure you reference the
same scanner variables and preserve existing behavior when err == nil.

---

Nitpick comments:
In `@internal/runner/runner.go`:
- Around line 488-499: The function processCommaSeparatedInput trims only
comma-split items, so a single-item input with surrounding whitespace bypasses
trimming; update processCommaSeparatedInput to call strings.TrimSpace(input) at
the start, return early if the trimmed input is empty, then proceed to split on
commas and call r.processInputItem(trimmedItem, inputs) for each non-empty
trimmed piece (and for the single-item path use the already-trimmed input) to
make single-line and comma-separated behavior consistent.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d808bfa and 658b8f7.

📒 Files selected for processing (2)
  • internal/runner/runner.go
  • internal/runner/runner_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/runner/runner_test.go

- Move defer close(inputs) before function call in tests (idiomatic Go)
- Add TrimSpace on single-item input path for consistency
- Add scanner.Err() checks after file and stdin scanner loops
Copy link

@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.

🧹 Nitpick comments (2)
internal/runner/runner_test.go (2)

30-33: Optional: Consider aligning defer placement across all tests for consistency.

The new tests correctly place defer close(inputs) before the function call (idiomatic Go), but existing tests like this one place it after. Aligning the pattern across all tests would improve consistency.

 go func() {
+	defer close(inputs)
 	runner.processInputItem(domain, inputs)
-	defer close(inputs)
 }()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/runner/runner_test.go` around lines 30 - 33, The test goroutine
calls runner.processInputItem(domain, inputs) before deferring close(inputs),
which is inconsistent with other tests; move the defer close(inputs) to
immediately after starting the goroutine (i.e., place defer close(inputs)
directly inside the goroutine before calling runner.processInputItem) so the
input channel is guaranteed to be closed regardless of early returns in
runner.processInputItem; update the goroutine in runner_test.go that references
runner.processInputItem and inputs to follow the same defer placement pattern as
other tests.

305-341: LGTM!

Thorough test validating the cross-product behavior between comma-separated hosts and multiple ports.

Optional: Consider adding edge case tests for malformed input such as empty strings, consecutive commas ("a,,b"), or trailing/leading commas (",a,b,") to ensure the implementation handles these gracefully.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/runner/runner_test.go` around lines 305 - 341, Add edge-case unit
tests for Runner.processCommaSeparatedInput mirroring
Test_CommaSeparatedInputMultiplePorts_processCommaSeparatedInput: create tests
(e.g., Test_CommaSeparatedInput_EdgeCases) that feed lines with empty strings,
consecutive commas ("a,,b"), and leading/trailing commas (",a,b,") into
processCommaSeparatedInput and assert the produced taskInput channel behavior
(decide and assert whether empty entries are skipped or produce empty-host tasks
consistent with existing implementation). Locate the logic via the
Runner.processCommaSeparatedInput function and the existing test
Test_CommaSeparatedInputMultiplePorts_processCommaSeparatedInput to structure
the new cases and expected taskInput slices accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/runner/runner_test.go`:
- Around line 30-33: The test goroutine calls runner.processInputItem(domain,
inputs) before deferring close(inputs), which is inconsistent with other tests;
move the defer close(inputs) to immediately after starting the goroutine (i.e.,
place defer close(inputs) directly inside the goroutine before calling
runner.processInputItem) so the input channel is guaranteed to be closed
regardless of early returns in runner.processInputItem; update the goroutine in
runner_test.go that references runner.processInputItem and inputs to follow the
same defer placement pattern as other tests.
- Around line 305-341: Add edge-case unit tests for
Runner.processCommaSeparatedInput mirroring
Test_CommaSeparatedInputMultiplePorts_processCommaSeparatedInput: create tests
(e.g., Test_CommaSeparatedInput_EdgeCases) that feed lines with empty strings,
consecutive commas ("a,,b"), and leading/trailing commas (",a,b,") into
processCommaSeparatedInput and assert the produced taskInput channel behavior
(decide and assert whether empty entries are skipped or produce empty-host tasks
consistent with existing implementation). Locate the logic via the
Runner.processCommaSeparatedInput function and the existing test
Test_CommaSeparatedInputMultiplePorts_processCommaSeparatedInput to structure
the new cases and expected taskInput slices accordingly.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 658b8f7 and 255f05f.

📒 Files selected for processing (2)
  • internal/runner/runner.go
  • internal/runner/runner_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/runner/runner.go

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.

-l -list option does not understand multiple prefixes, comma-separated in a single line

1 participant