Skip to content

Commit 5d1d0ac

Browse files
authored
Improve port parsing to support combined specifications (#23)
1 parent f5a862f commit 5d1d0ac

File tree

2 files changed

+76
-28
lines changed

2 files changed

+76
-28
lines changed

scan/utils.go

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package scan
22

33
import (
44
"fmt"
5+
"slices"
56
"strconv"
67
"strings"
78
"time"
@@ -28,17 +29,17 @@ func getDuration(period string) (time.Duration, error) {
2829
return t, nil
2930
}
3031

31-
// readPortsRange transforms a range of ports given in conf to an array of
32-
// effective ports
32+
// readPortsRange transforms a comma-separated string of ports into a unique,
33+
// sorted slice of integers.
3334
func readPortsRange(ranges string) ([]int, error) {
3435
ports := []int{}
3536

3637
// Remove spaces
37-
ranges = strings.Replace(ranges, " ", "", -1)
38+
ranges = strings.ReplaceAll(ranges, " ", "")
3839

39-
parts := strings.Split(ranges, ",")
40+
parts := strings.SplitSeq(ranges, ",")
4041

41-
for _, spec := range parts {
42+
for spec := range parts {
4243
if spec == "" {
4344
continue
4445
}
@@ -54,34 +55,49 @@ func readPortsRange(ranges string) ([]int, error) {
5455
case "top1000":
5556
ports = append(ports, top1000Ports...)
5657
default:
57-
var decomposedRange []string
58+
if strings.Contains(spec, "-") {
59+
decomposedRange := strings.Split(spec, "-")
60+
if len(decomposedRange) != 2 || decomposedRange[0] == "" || decomposedRange[1] == "" {
61+
return nil, fmt.Errorf("invalid port range format: %q", spec)
62+
}
5863

59-
if !strings.Contains(spec, "-") {
60-
decomposedRange = []string{spec, spec}
64+
min, err := strconv.Atoi(decomposedRange[0])
65+
if err != nil {
66+
return nil, fmt.Errorf("invalid start port in range %q: %w", spec, err)
67+
}
68+
max, err := strconv.Atoi(decomposedRange[1])
69+
if err != nil {
70+
return nil, fmt.Errorf("invalid end port in range %q: %w", spec, err)
71+
}
72+
73+
if min > max {
74+
return nil, fmt.Errorf("start port %d is higher than end port %d in range %q", min, max, spec)
75+
}
76+
77+
if min < 1 || max > 65535 {
78+
return nil, fmt.Errorf("port range %q is out of the valid range (1-65535)", spec)
79+
}
80+
81+
for i := min; i <= max; i++ {
82+
ports = append(ports, i)
83+
}
6184
} else {
62-
decomposedRange = strings.Split(spec, "-")
63-
}
85+
port, err := strconv.Atoi(spec)
86+
if err != nil {
87+
return nil, fmt.Errorf("invalid port specification %q: %w", spec, err)
88+
}
6489

65-
min, err := strconv.Atoi(decomposedRange[0])
66-
if err != nil {
67-
return nil, err
68-
}
69-
max, err := strconv.Atoi(decomposedRange[len(decomposedRange)-1])
70-
if err != nil {
71-
return nil, err
72-
}
90+
if port < 1 || port > 65535 {
91+
return nil, fmt.Errorf("port %d is out of the valid range (1-65535)", port)
92+
}
7393

74-
if min > max {
75-
return nil, fmt.Errorf("lower port %d is higher than high port %d", min, max)
76-
}
77-
if max > 65535 {
78-
return nil, fmt.Errorf("port %d is higher than max port", max)
79-
}
80-
for i := min; i <= max; i++ {
81-
ports = append(ports, i)
94+
ports = append(ports, port)
8295
}
8396
}
8497
}
8598

86-
return ports, nil
99+
slices.Sort(ports)
100+
uniquePorts := slices.Compact(ports)
101+
102+
return uniquePorts, nil
87103
}

scan/utils_test.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ func Test_getDuration(t *testing.T) {
3434
}
3535

3636
func Test_readPortsRange(t *testing.T) {
37+
reservedPorts := make([]int, 1023)
38+
for i := range 1023 {
39+
reservedPorts[i] = i + 1
40+
}
41+
42+
allPorts := make([]int, 65535)
43+
for i := range 65535 {
44+
allPorts[i] = i + 1
45+
}
46+
3747
tests := []struct {
3848
name string
3949
ranges string
@@ -45,7 +55,29 @@ func Test_readPortsRange(t *testing.T) {
4555
{name: "hyphen", ranges: "22-25", want: []int{22, 23, 24, 25}, wantErr: false},
4656
{name: "comma and hyphen", ranges: "22,30-32", want: []int{22, 30, 31, 32}, wantErr: false},
4757
{name: "comma, hyphen, comma", ranges: "22,30-32,50", want: []int{22, 30, 31, 32, 50}, wantErr: false},
48-
{name: "unknown", ranges: "foobar", wantErr: true},
58+
59+
// Tests for keywords
60+
{name: "keyword all", ranges: "all", want: allPorts, wantErr: false},
61+
{name: "keyword reserved", ranges: "reserved", want: reservedPorts, wantErr: false},
62+
{name: "keyword top1000", ranges: "top1000", want: top1000Ports, wantErr: false},
63+
64+
// Tests for combinations and uniqueness
65+
{name: "duplicates", ranges: "80,81,443,79-88", want: []int{79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 443}, wantErr: false},
66+
{name: "reserved with duplicates", ranges: "1,2,reserved", want: reservedPorts, wantErr: false},
67+
{name: "all with others", ranges: "80,all,9000", want: allPorts, wantErr: false},
68+
69+
// Tests for edge cases
70+
{name: "empty string", ranges: "", want: []int{}, wantErr: false},
71+
{name: "whitespace and commas", ranges: " , ", want: []int{}, wantErr: false},
72+
73+
// Tests for error conditions
74+
{name: "unknown keyword", ranges: "foobar", wantErr: true},
75+
{name: "invalid range min > max", ranges: "100-20", wantErr: true},
76+
{name: "port > 65535", ranges: "65536", wantErr: true},
77+
{name: "port < 1", ranges: "0", wantErr: true},
78+
{name: "range end > 65535", ranges: "65530-65536", wantErr: true},
79+
{name: "malformed range end", ranges: "100-", wantErr: true},
80+
{name: "malformed range start", ranges: "-100", wantErr: true},
4981
}
5082
for _, tt := range tests {
5183
t.Run(tt.name, func(t *testing.T) {

0 commit comments

Comments
 (0)