From e09411f871c5c6ffadbddeb1096b80e2d7bd7b87 Mon Sep 17 00:00:00 2001 From: Leonid Nikitin Date: Wed, 18 Mar 2026 21:04:33 +0500 Subject: [PATCH] Add method to parse IPs by version (IPv4/IPv6) with validation and limit support --- parser/json_lines.go | 47 +++++++++++++++++++++++++++++++++++++++++ parser/rss.go | 50 ++++++++++++++++++++++++++++++++++++++++++++ parser/text.go | 46 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+) diff --git a/parser/json_lines.go b/parser/json_lines.go index eca1f55..442fc01 100644 --- a/parser/json_lines.go +++ b/parser/json_lines.go @@ -63,3 +63,50 @@ func (p *jsonLinesParser) Parse(body io.Reader, validator IPValidator, limit uin return ips, nil } + +// ParseIPsByVersion parses the JSON Lines data from the given reader +// and returns a slice of IP addresses for each IP version. +// It also returns any errors that occurred during the process. +func (p *jsonLinesParser) ParseIPsByVersion(body io.Reader, validator IPValidator, limit uint) (ipV4 IPs, ipV6 IPs, err error) { + decoder := json.NewDecoder(body) + ipV4 = make(IPs, 0) + ipV6 = make(IPs, 0) + for { + var item json.RawMessage + if err := decoder.Decode(&item); err != nil { + if err == io.EOF { + break + } + return nil, nil, fmt.Errorf("decode json item: %w", err) + } + + if item == nil { + continue + } + + ip, err := p.extract(item) + if err != nil { + return nil, nil, fmt.Errorf("extract ip: %w", err) + } + + ip = strings.TrimSpace(ip) + isValid, ipVersion := validator.IsValidAndReturnVersion(ip) + if !isValid { + continue + } + + if ipVersion == IPVersion4 { + ipV4 = append(ipV4, ip) + } else if ipVersion == IPVersion6 { + ipV6 = append(ipV6, ip) + } else { + continue + } + + if limit > 0 && uint(len(ipV4))+uint(len(ipV6)) >= limit { + break + } + } + + return ipV4, ipV6, nil +} diff --git a/parser/rss.go b/parser/rss.go index c17c150..02c125b 100644 --- a/parser/rss.go +++ b/parser/rss.go @@ -66,3 +66,53 @@ func (p *rssParser) Parse(body io.Reader, validator IPValidator, limit uint) (IP return ips, nil } + +// ParseIPsByVersion parses the RSS data from the given reader +// and returns a slice of IP addresses for each IP version. +// It also returns any errors that occurred during the process. +func (p *rssParser) ParseIPsByVersion(body io.Reader, validator IPValidator, limit uint) (ipV4 IPs, ipV6 IPs, err error) { + decoder := xml.NewDecoder(body) + ipV4 = make(IPs, 0) + ipV6 = make(IPs, 0) + + for { + token, err := decoder.Token() + if err != nil { + if err == io.EOF { + break + } + + return nil, nil, fmt.Errorf("parse rss: %w", err) + } + + start, ok := token.(xml.StartElement) + if !ok { + continue + } + + ip, err := p.extract(decoder, start) + if err != nil { + return nil, nil, fmt.Errorf("extract rss ip: %w", err) + } + + ip = strings.TrimSpace(ip) + isValid, ipVersion := validator.IsValidAndReturnVersion(ip) + if !isValid { + continue + } + + if ipVersion == IPVersion4 { + ipV4 = append(ipV4, ip) + } else if ipVersion == IPVersion6 { + ipV6 = append(ipV6, ip) + } else { + continue + } + + if limit > 0 && uint(len(ipV4))+uint(len(ipV6)) >= limit { + break + } + } + + return ipV4, ipV6, nil +} diff --git a/parser/text.go b/parser/text.go index 528eb5c..80be696 100644 --- a/parser/text.go +++ b/parser/text.go @@ -158,3 +158,49 @@ func (p *textParser) Parse(body io.Reader, validator IPValidator, limit uint) (I return ips, nil } + +func (p *textParser) ParseIPsByVersion(body io.Reader, validator IPValidator, limit uint) (ipV4 IPs, ipV6 IPs, err error) { + ipV4 = make(IPs, 0) + ipV6 = make(IPs, 0) + + scanner := bufio.NewScanner(body) + + buf := make([]byte, 0, 64*1024) + scanner.Buffer(buf, 1024*1024) + + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" || strings.HasPrefix(line, ";") || strings.HasPrefix(line, "#") { + continue + } + + ip, isFound := p.textExtract.Extract(line) + if !isFound { + continue + } + + ip = strings.TrimSpace(ip) + isValid, ipVersion := validator.IsValidAndReturnVersion(ip) + if !isValid { + continue + } + + if ipVersion == IPVersion4 { + ipV4 = append(ipV4, ip) + } else if ipVersion == IPVersion6 { + ipV6 = append(ipV6, ip) + } else { + continue + } + + if limit > 0 && uint(len(ipV4))+uint(len(ipV6)) >= limit { + break + } + } + + if err := scanner.Err(); err != nil { + return nil, nil, fmt.Errorf("read response: %w", err) + } + + return ipV4, ipV6, nil +}