5 Commits

14 changed files with 492 additions and 38 deletions

View File

@@ -89,29 +89,12 @@ func NewConfigZip(c Config) ConfigZip {
// Get fetches data from the given URL, parses the response using the provided parser, and applies the given configuration. // Get fetches data from the given URL, parses the response using the provided parser, and applies the given configuration.
// It returns the parsed IPs and any errors that occurred during the process. // It returns the parsed IPs and any errors that occurred during the process.
func Get(fileUrl string, parser parser.Parser, c Config) (parser.IPs, error) { func Get(fileUrl string, parser parser.Parser, c Config) (parser.IPs, error) {
parsedURL, err := url.Parse(fileUrl)
if err != nil {
return nil, fmt.Errorf("invalid url: %w", err)
}
if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
return nil, fmt.Errorf("invalid url scheme: %s", parsedURL.Scheme)
}
ctx, cancel := context.WithTimeout(context.Background(), c.ContextTimeout) ctx, cancel := context.WithTimeout(context.Background(), c.ContextTimeout)
defer cancel() defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fileUrl, nil) res, err := fetch(fileUrl, ctx, c.RequestTimeout)
if err != nil { if err != nil {
return nil, fmt.Errorf("create request: %w", err) return nil, err
}
client := &http.Client{
Timeout: c.RequestTimeout,
}
res, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
} }
defer func() { defer func() {
_ = res.Body.Close() _ = res.Body.Close()
@@ -124,32 +107,36 @@ func Get(fileUrl string, parser parser.Parser, c Config) (parser.IPs, error) {
return parser.Parse(res.Body, c.Validator, c.Limit) return parser.Parse(res.Body, c.Validator, c.Limit)
} }
// GetSeparatedIPs fetches data from the given URL, parses the response using the provided parser, and applies the given configuration.
// It returns the parsed IPs and any errors that occurred during the process.
func GetSeparatedIPs(fileUrl string, parser parser.Parser, c Config) (ipV4 parser.IPs, ipV6 parser.IPs, err error) {
ctx, cancel := context.WithTimeout(context.Background(), c.ContextTimeout)
defer cancel()
res, err := fetch(fileUrl, ctx, c.RequestTimeout)
if err != nil {
return nil, nil, err
}
defer func() {
_ = res.Body.Close()
}()
if res.StatusCode != http.StatusOK {
return nil, nil, fmt.Errorf("unexpected status code: %d", res.StatusCode)
}
return parser.ParseIPsByVersion(res.Body, c.Validator, c.Limit)
}
// GetZip fetches data from the given URL, parses the response using the provided parser, and applies the given configuration. // GetZip fetches data from the given URL, parses the response using the provided parser, and applies the given configuration.
// It returns the parsed IPs and any errors that occurred during the process. // It returns the parsed IPs and any errors that occurred during the process.
func GetZip(fileUrl string, parser parser.Parser, c ConfigZip) (parser.IPs, error) { func GetZip(fileUrl string, parser parser.Parser, c ConfigZip) (parser.IPs, error) {
parsedURL, err := url.Parse(fileUrl)
if err != nil {
return nil, fmt.Errorf("invalid url: %w", err)
}
if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
return nil, fmt.Errorf("invalid url scheme: %s", parsedURL.Scheme)
}
ctx, cancel := context.WithTimeout(context.Background(), c.Config.ContextTimeout) ctx, cancel := context.WithTimeout(context.Background(), c.Config.ContextTimeout)
defer cancel() defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fileUrl, nil) res, err := fetch(fileUrl, ctx, c.Config.RequestTimeout)
if err != nil { if err != nil {
return nil, fmt.Errorf("create request: %w", err) return nil, err
}
client := &http.Client{
Timeout: c.Config.RequestTimeout,
}
res, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
} }
defer func() { defer func() {
_ = res.Body.Close() _ = res.Body.Close()
@@ -184,6 +171,49 @@ func GetZip(fileUrl string, parser parser.Parser, c ConfigZip) (parser.IPs, erro
return parseZip(body, parser, c) return parseZip(body, parser, c)
} }
// GetZipSeparatedIPs fetches data from the given URL, parses the response using the provided parser, and applies the given configuration.
// It returns the parsed IPs and any errors that occurred during the process.
func GetZipSeparatedIPs(fileUrl string, parser parser.Parser, c ConfigZip) (ipV4 parser.IPs, ipV6 parser.IPs, err error) {
ctx, cancel := context.WithTimeout(context.Background(), c.Config.ContextTimeout)
defer cancel()
res, err := fetch(fileUrl, ctx, c.Config.RequestTimeout)
if err != nil {
return nil, nil, err
}
defer func() {
_ = res.Body.Close()
}()
if res.StatusCode != http.StatusOK {
return nil, nil, fmt.Errorf("unexpected status code: %d", res.StatusCode)
}
if c.MaxDownloadSize > 0 && res.ContentLength > c.MaxDownloadSize {
return nil, nil, fmt.Errorf("downloaded file is too large: content-length %d exceeds limit %d", res.ContentLength, c.MaxDownloadSize)
}
reader := res.Body
if c.MaxDownloadSize > 0 {
reader = io.NopCloser(io.LimitReader(res.Body, c.MaxDownloadSize+1))
}
body, err := io.ReadAll(reader)
if err != nil {
return nil, nil, fmt.Errorf("read response body: %w", err)
}
if c.MaxDownloadSize > 0 && int64(len(body)) > c.MaxDownloadSize {
return nil, nil, fmt.Errorf("downloaded file exceeds limit %d bytes", c.MaxDownloadSize)
}
if !isZip(body) {
return nil, nil, fmt.Errorf("invalid zip archive")
}
return parseZipSeparatedIPs(body, parser, c)
}
func isZip(body []byte) bool { func isZip(body []byte) bool {
return len(body) >= 4 && return len(body) >= 4 &&
body[0] == 'P' && body[0] == 'P' &&
@@ -223,6 +253,37 @@ func parseZip(body []byte, p parser.Parser, c ConfigZip) (parser.IPs, error) {
return p.Parse(zipReader, c.Config.Validator, c.Config.Limit) return p.Parse(zipReader, c.Config.Validator, c.Config.Limit)
} }
func parseZipSeparatedIPs(body []byte, p parser.Parser, c ConfigZip) (ipV4 parser.IPs, ipV6 parser.IPs, err error) {
reader, err := zip.NewReader(bytes.NewReader(body), int64(len(body)))
if err != nil {
return nil, nil, fmt.Errorf("open zip archive: %w", err)
}
file := findArchiveFile(reader.File)
if file == nil {
return nil, nil, fmt.Errorf("zip archive does not contain a supported file")
}
if c.MaxArchiveFileSize > 0 && file.UncompressedSize64 > c.MaxArchiveFileSize {
return nil, nil, fmt.Errorf("file %q in zip is too large: %d exceeds limit %d", file.Name, file.UncompressedSize64, c.MaxArchiveFileSize)
}
rc, err := file.Open()
if err != nil {
return nil, nil, fmt.Errorf("open file %q from zip: %w", file.Name, err)
}
defer func() {
_ = rc.Close()
}()
var zipReader io.Reader = rc
if c.MaxArchiveFileSize > 0 {
zipReader = io.LimitReader(rc, int64(c.MaxArchiveFileSize)+1)
}
return p.ParseIPsByVersion(zipReader, c.Config.Validator, c.Config.Limit)
}
func findArchiveFile(files []*zip.File) *zip.File { func findArchiveFile(files []*zip.File) *zip.File {
var fallback *zip.File var fallback *zip.File
@@ -246,3 +307,28 @@ func findArchiveFile(files []*zip.File) *zip.File {
return fallback return fallback
} }
func fetch(fileUrl string, ctx context.Context, requestTimeout time.Duration) (*http.Response, error) {
parsedURL, err := url.Parse(fileUrl)
if err != nil {
return nil, fmt.Errorf("invalid url: %w", err)
}
if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
return nil, fmt.Errorf("invalid url scheme: %s", parsedURL.Scheme)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fileUrl, nil)
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
client := &http.Client{
Timeout: requestTimeout,
}
res, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
return res, nil
}

View File

@@ -23,12 +23,24 @@ func main() {
// limit 0 - no limit // limit 0 - no limit
limit := uint(0) limit := uint(0)
config := blocklist.NewConfig(limit) config := blocklist.NewConfig(limit)
// Get IPv4 and IPv6 addresses in one list
ips, err := blocklist.Get(url, pars, config) ips, err := blocklist.Get(url, pars, config)
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Println(ips) fmt.Println(ips)
// Get IPv4 and IPv6 addresses in two lists
ipsV4, ipsV6, err := blocklist.GetSeparatedIPs(url, pars, config)
if err != nil {
panic(err)
}
fmt.Println("IPv4")
fmt.Println(ipsV4)
fmt.Println("IPv6")
fmt.Println(ipsV6)
/* /*
// This second list retrieves all the IP addresses added in the last 48 hours and is usually a // This second list retrieves all the IP addresses added in the last 48 hours and is usually a
// very large list (over 10000 entries), so be sure that you have the resources available to use it // very large list (over 10000 entries), so be sure that you have the resources available to use it
@@ -41,10 +53,22 @@ func main() {
// limit 0 - no limit // limit 0 - no limit
limit := uint(0) limit := uint(0)
config := blocklist.NewConfig(limit) config := blocklist.NewConfig(limit)
// Get IPv4 and IPv6 addresses in one list
ips, err := blocklist.Get(url, pars, config) ips, err := blocklist.Get(url, pars, config)
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Println(ips) fmt.Println(ips)
// Get IPv4 and IPv6 addresses in two lists
ipsV4, ipsV6, err := blocklist.GetSeparatedIPs(url, pars, config)
if err != nil {
panic(err)
}
fmt.Println("IPv4")
fmt.Println(ipsV4)
fmt.Println("IPv6")
fmt.Println(ipsV6)
*/ */
} }

View File

@@ -21,9 +21,21 @@ func main() {
// limit 0 - no limit // limit 0 - no limit
limit := uint(0) limit := uint(0)
config := blocklist.NewConfig(limit) config := blocklist.NewConfig(limit)
// Get IPv4 and IPv6 addresses in one list
ips, err := blocklist.Get(url, pars, config) ips, err := blocklist.Get(url, pars, config)
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Println(ips) fmt.Println(ips)
// Get IPv4 and IPv6 addresses in two lists
ipsV4, ipsV6, err := blocklist.GetSeparatedIPs(url, pars, config)
if err != nil {
panic(err)
}
fmt.Println("IPv4")
fmt.Println(ipsV4)
fmt.Println("IPv6")
fmt.Println(ipsV6)
} }

View File

@@ -21,9 +21,21 @@ func main() {
// limit 0 - no limit // limit 0 - no limit
limit := uint(0) limit := uint(0)
config := blocklist.NewConfig(limit) config := blocklist.NewConfig(limit)
// Get IPv4 and IPv6 addresses in one list
ips, err := blocklist.Get(url, pars, config) ips, err := blocklist.Get(url, pars, config)
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Println(ips) fmt.Println(ips)
// Get IPv4 and IPv6 addresses in two lists
ipsV4, ipsV6, err := blocklist.GetSeparatedIPs(url, pars, config)
if err != nil {
panic(err)
}
fmt.Println("IPv4")
fmt.Println(ipsV4)
fmt.Println("IPv6")
fmt.Println(ipsV6)
} }

View File

@@ -21,12 +21,24 @@ func main() {
// limit 0 - no limit // limit 0 - no limit
limit := uint(0) limit := uint(0)
config := blocklist.NewConfig(limit) config := blocklist.NewConfig(limit)
// Get IPv4 and IPv6 addresses in one list
ips, err := blocklist.Get(url, pars, config) ips, err := blocklist.Get(url, pars, config)
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Println(ips) fmt.Println(ips)
// Get IPv4 and IPv6 addresses in two lists
ipsV4, ipsV6, err := blocklist.GetSeparatedIPs(url, pars, config)
if err != nil {
panic(err)
}
fmt.Println("IPv4")
fmt.Println(ipsV4)
fmt.Println("IPv6")
fmt.Println(ipsV6)
/* /*
// You can also get a range of IP addresses from this service (from to) // You can also get a range of IP addresses from this service (from to)
url := "https://www.dshield.org/block.txt" url := "https://www.dshield.org/block.txt"
@@ -38,10 +50,22 @@ func main() {
// limit 0 - no limit // limit 0 - no limit
limit := uint(0) limit := uint(0)
config := blocklist.NewConfigWithValidator(limit, &parser.IPRangeValidator{}) config := blocklist.NewConfigWithValidator(limit, &parser.IPRangeValidator{})
// Get IPv4 and IPv6 addresses in one list
ips, err := blocklist.Get(url, pars, config) ips, err := blocklist.Get(url, pars, config)
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Println(ips) fmt.Println(ips)
// Get IPv4 and IPv6 addresses in two lists
ipsV4, ipsV6, err := blocklist.GetSeparatedIPs(url, pars, config)
if err != nil {
panic(err)
}
fmt.Println("IPv4")
fmt.Println(ipsV4)
fmt.Println("IPv6")
fmt.Println(ipsV6)
*/ */
} }

View File

@@ -21,9 +21,21 @@ func main() {
// limit 0 - no limit // limit 0 - no limit
limit := uint(0) limit := uint(0)
config := blocklist.NewConfig(limit) config := blocklist.NewConfig(limit)
// Get IPv4 and IPv6 addresses in one list
ips, err := blocklist.Get(url, pars, config) ips, err := blocklist.Get(url, pars, config)
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Println(ips) fmt.Println(ips)
// Get IPv4 and IPv6 addresses in two lists
ipsV4, ipsV6, err := blocklist.GetSeparatedIPs(url, pars, config)
if err != nil {
panic(err)
}
fmt.Println("IPv4")
fmt.Println(ipsV4)
fmt.Println("IPv6")
fmt.Println(ipsV6)
} }

View File

@@ -45,9 +45,21 @@ func main() {
// limit 0 - no limit // limit 0 - no limit
limit := uint(0) limit := uint(0)
config := blocklist.NewConfig(limit) config := blocklist.NewConfig(limit)
// Get IPv4 and IPv6 addresses in one list
ips, err := blocklist.Get(url, pars, config) ips, err := blocklist.Get(url, pars, config)
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Println(ips) fmt.Println(ips)
// Get IPv4 and IPv6 addresses in two lists
ipsV4, ipsV6, err := blocklist.GetSeparatedIPs(url, pars, config)
if err != nil {
panic(err)
}
fmt.Println("IPv4")
fmt.Println(ipsV4)
fmt.Println("IPv6")
fmt.Println(ipsV6)
} }

View File

@@ -32,9 +32,21 @@ func main() {
// limit 0 - no limit // limit 0 - no limit
limit := uint(0) limit := uint(0)
config := blocklist.NewConfig(limit) config := blocklist.NewConfig(limit)
// Get IPv4 and IPv6 addresses in one list
ips, err := blocklist.Get(url, pars, config) ips, err := blocklist.Get(url, pars, config)
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Println(ips) fmt.Println(ips)
// Get IPv4 and IPv6 addresses in two lists
ipsV4, ipsV6, err := blocklist.GetSeparatedIPs(url, pars, config)
if err != nil {
panic(err)
}
fmt.Println("IPv4")
fmt.Println(ipsV4)
fmt.Println("IPv6")
fmt.Println(ipsV6)
} }

View File

@@ -23,9 +23,21 @@ func main() {
limit := uint(0) limit := uint(0)
config := blocklist.NewConfig(limit) config := blocklist.NewConfig(limit)
configZip := blocklist.NewConfigZip(config) configZip := blocklist.NewConfigZip(config)
// Get IPv4 and IPv6 addresses in one list
ips, err := blocklist.GetZip(url, pars, configZip) ips, err := blocklist.GetZip(url, pars, configZip)
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Println(ips) fmt.Println(ips)
// Get IPv4 and IPv6 addresses in two lists
ipsV4, ipsV6, err := blocklist.GetZipSeparatedIPs(url, pars, configZip)
if err != nil {
panic(err)
}
fmt.Println("IPv4")
fmt.Println(ipsV4)
fmt.Println("IPv6")
fmt.Println(ipsV6)
} }

View File

@@ -21,9 +21,21 @@ func main() {
// limit 0 - no limit // limit 0 - no limit
limit := uint(0) limit := uint(0)
config := blocklist.NewConfig(limit) config := blocklist.NewConfig(limit)
// Get IPv4 and IPv6 addresses in one list
ips, err := blocklist.Get(url, pars, config) ips, err := blocklist.Get(url, pars, config)
if err != nil { if err != nil {
panic(err) panic(err)
} }
fmt.Println(ips) fmt.Println(ips)
// Get IPv4 and IPv6 addresses in two lists
ipsV4, ipsV6, err := blocklist.GetSeparatedIPs(url, pars, config)
if err != nil {
panic(err)
}
fmt.Println("IPv4")
fmt.Println(ipsV4)
fmt.Println("IPv6")
fmt.Println(ipsV6)
} }

View File

@@ -63,3 +63,50 @@ func (p *jsonLinesParser) Parse(body io.Reader, validator IPValidator, limit uin
return ips, nil 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
}

View File

@@ -11,14 +11,25 @@ import (
type Parser interface { type Parser interface {
// Parse reads the body and returns a slice of IP addresses. // Parse reads the body and returns a slice of IP addresses.
Parse(body io.Reader, validator IPValidator, limit uint) (IPs, error) Parse(body io.Reader, validator IPValidator, limit uint) (IPs, error)
ParseIPsByVersion(body io.Reader, validator IPValidator, limit uint) (ipV4 IPs, ipV6 IPs, err error)
} }
// IPValidator interface defines the contract for validating IP addresses. // IPValidator interface defines the contract for validating IP addresses.
type IPValidator interface { type IPValidator interface {
// IsValid checks if the given IP address is valid. // IsValid checks if the given IP address is valid.
IsValid(ip string) bool IsValid(ip string) bool
IsValidAndReturnVersion(ip string) (bool, IPVersion)
} }
type IPVersion int
const (
IPVersion4 IPVersion = iota
IPVersion6
)
// IPs is a slice of IP addresses. // IPs is a slice of IP addresses.
type IPs []string type IPs []string
@@ -50,6 +61,48 @@ func (v *DefaultIPValidator) IsValid(value string) bool {
return false return false
} }
// IsValidAndReturnVersion checks if the given IP address is valid and returns the IP version.
// It returns true if the IP address is not a loopback address and the IP version is either IPv4 or IPv6.
func (v *DefaultIPValidator) IsValidAndReturnVersion(value string) (bool, IPVersion) {
if value == "" {
return false, IPVersion4
}
if ip := net.ParseIP(value); ip != nil {
if ip.IsLoopback() {
return false, IPVersion4
}
if ip.To4() != nil {
return true, IPVersion4
}
if ip.To16() != nil {
return true, IPVersion6
}
return false, IPVersion4
}
if ip, _, err := net.ParseCIDR(value); err == nil {
if ip.IsLoopback() {
return false, IPVersion4
}
if ip.To4() != nil {
return true, IPVersion4
}
if ip.To16() != nil {
return true, IPVersion6
}
return false, IPVersion4
}
return false, IPVersion4
}
// IPRangeValidator implements IPValidator interface. // IPRangeValidator implements IPValidator interface.
// It validates IP ranges by parsing them using net.ParseIP and checking if the start and end IPs are in the same network. // It validates IP ranges by parsing them using net.ParseIP and checking if the start and end IPs are in the same network.
type IPRangeValidator struct{} type IPRangeValidator struct{}
@@ -89,3 +142,43 @@ func (v *IPRangeValidator) IsValid(value string) bool {
return false return false
} }
} }
func (v *IPRangeValidator) IsValidAndReturnVersion(value string) (bool, IPVersion) {
if value == "" {
return false, IPVersion4
}
parts := strings.Split(value, "-")
if len(parts) != 2 {
return false, IPVersion4
}
start := net.ParseIP(strings.TrimSpace(parts[0]))
end := net.ParseIP(strings.TrimSpace(parts[1]))
if start == nil || end == nil {
return false, IPVersion4
}
start4 := start.To4()
end4 := end.To4()
switch {
case start4 != nil && end4 != nil:
if bytes.Compare(start4, end4) <= 0 {
return true, IPVersion4
}
return false, IPVersion4
case start4 == nil && end4 == nil:
start16 := start.To16()
end16 := end.To16()
if start16 == nil || end16 == nil {
return false, IPVersion4
}
if bytes.Compare(start16, end16) <= 0 {
return true, IPVersion6
}
return false, IPVersion4
default:
return false, IPVersion4
}
}

View File

@@ -66,3 +66,53 @@ func (p *rssParser) Parse(body io.Reader, validator IPValidator, limit uint) (IP
return ips, nil 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
}

View File

@@ -158,3 +158,49 @@ func (p *textParser) Parse(body io.Reader, validator IPValidator, limit uint) (I
return ips, nil 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
}