16 Commits

Author SHA1 Message Date
70fb7a3aa8 Merge pull request 'v1.0.0' (#1) from develop into main
Reviewed-on: #1
2026-03-15 20:35:59 +05:00
cfbf4ce504 Add StopForumSpam example link to README 2026-03-15 20:28:36 +05:00
17115d97c6 Add StopForumSpam example for fetching and parsing IP blocklist data 2026-03-15 20:27:58 +05:00
1dc7a8b22c Add ZIP archive handling support for fetching and parsing blocklist data 2026-03-15 20:27:31 +05:00
e4fc07871b Add GreenSnow example link to README 2026-03-15 19:06:26 +05:00
faedb345df Add Greensnow example for fetching and parsing IP blocklist data 2026-03-15 19:05:16 +05:00
ee0e36b40d Add Blocklist.de example link to README 2026-03-15 18:59:25 +05:00
790073aa95 Add Blocklist.de example for fetching and parsing IP blocklist data 2026-03-15 18:58:19 +05:00
e18859e78f Add a link to Daniel Gerzo's BruteforceBlocker list usage example to your README file. 2026-03-15 18:18:05 +05:00
aeb855c0b8 Add BruteforceBlocker example for fetching and parsing IP blocklist data 2026-03-15 18:07:40 +05:00
5b8974101e Add CIArmy example link to README 2026-03-15 17:48:00 +05:00
dd4e5619c7 Add CIArmy example for fetching and parsing IP blocklist data 2026-03-15 17:47:14 +05:00
172c32237b Normalize capitalization of "HONEYPOT" in README example link. 2026-03-15 17:40:42 +05:00
364e657641 Fix typo in README by removing unnecessary hyphen in "IP адресов" 2026-03-15 16:48:58 +05:00
e4b0ed6668 Clarify README description by generalizing "bad IP addresses" to "IP addresses" 2026-03-15 16:47:49 +05:00
f295c402af Add README with installation steps, usage examples, and license information 2026-03-15 16:45:31 +05:00
7 changed files with 343 additions and 0 deletions

23
README.md Normal file
View File

@@ -0,0 +1,23 @@
# blocklist
Пакет Go для получения IP адресов от различных сервисов.
## Установка
```sh
go get git.kor-elf.net/kor-elf-shield/blocklist
```
## Примеры использования
- [Пример получения списка IP адресов от Spamhaus](/examples/spamhaus.go)
- [Пример получения списка IP адресов от DShield](/examples/dshield.go)
- [Пример получения списка IP адресов от HONEYPOT](/examples/honeypot.go)
- [Пример получения списка IP адресов от Tor](/examples/tor.go)
- [Пример получения списка IP адресов от CIARMY](/examples/ciarmy.go)
- [Пример получения списка IP адресов от Daniel Gerzo (BruteforceBlocker)](/examples/bruteforceblocker.go)
- [Пример получения списка IP адресов от Blocklist.de](/examples/blocklist.go)
- [Пример получения списка IP адресов от GreenSnow](/examples/greensnow.go)
- [Пример получения списка IP адресов от StopForumSpam](/examples/stopforumspam.go)
## Лицензия
[MIT](https://git.kor-elf.net/kor-elf-shield/blocklist/src/branch/main/LICENSE)

View File

@@ -1,10 +1,14 @@
package blocklist package blocklist
import ( import (
"archive/zip"
"bytes"
"context" "context"
"fmt" "fmt"
"io"
"net/http" "net/http"
"net/url" "net/url"
"strings"
"time" "time"
"git.kor-elf.net/kor-elf-shield/blocklist/parser" "git.kor-elf.net/kor-elf-shield/blocklist/parser"
@@ -16,6 +20,12 @@ const (
// requestTimeout defines the maximum duration for request operations before timing out. // requestTimeout defines the maximum duration for request operations before timing out.
requestTimeout = 20 * time.Second requestTimeout = 20 * time.Second
// maxDownloadSize defines the maximum allowed size of the downloaded file in bytes.
maxDownloadSize int64 = 20 << 20 // 20 MiB
// maxArchiveFileSize defines the maximum allowed size of the extracted file from ZIP in bytes.
maxArchiveFileSize uint64 = 50 << 20 // 50 MiB
) )
// Config defines the configuration for the blocklist. // Config defines the configuration for the blocklist.
@@ -34,6 +44,17 @@ type Config struct {
RequestTimeout time.Duration RequestTimeout time.Duration
} }
type ConfigZip struct {
// Config is the configuration for the blocklist.
Config Config
// MaxDownloadSize defines the maximum allowed size of the downloaded file in bytes.
MaxDownloadSize int64
// MaxArchiveFileSize defines the maximum allowed size of the extracted file from ZIP in bytes.
MaxArchiveFileSize uint64
}
// NewConfig creates a new Config with default values. // NewConfig creates a new Config with default values.
// limit is the maximum number of items to process or validate. 0 means no limit. // limit is the maximum number of items to process or validate. 0 means no limit.
func NewConfig(limit uint) Config { func NewConfig(limit uint) Config {
@@ -57,6 +78,14 @@ func NewConfigWithValidator(limit uint, validator parser.IPValidator) Config {
} }
} }
func NewConfigZip(c Config) ConfigZip {
return ConfigZip{
Config: c,
MaxDownloadSize: maxDownloadSize,
MaxArchiveFileSize: maxArchiveFileSize,
}
}
// 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) {
@@ -94,3 +123,126 @@ 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)
} }
// 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.
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)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fileUrl, nil)
if err != nil {
return nil, fmt.Errorf("create request: %w", 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() {
_ = res.Body.Close()
}()
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code: %d", res.StatusCode)
}
if c.MaxDownloadSize > 0 && res.ContentLength > c.MaxDownloadSize {
return 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, fmt.Errorf("read response body: %w", err)
}
if c.MaxDownloadSize > 0 && int64(len(body)) > c.MaxDownloadSize {
return nil, fmt.Errorf("downloaded file exceeds limit %d bytes", c.MaxDownloadSize)
}
if !isZip(body) {
return nil, fmt.Errorf("invalid zip archive")
}
return parseZip(body, parser, c)
}
func isZip(body []byte) bool {
return len(body) >= 4 &&
body[0] == 'P' &&
body[1] == 'K' &&
body[2] == 0x03 &&
body[3] == 0x04
}
func parseZip(body []byte, p parser.Parser, c ConfigZip) (parser.IPs, error) {
reader, err := zip.NewReader(bytes.NewReader(body), int64(len(body)))
if err != nil {
return nil, fmt.Errorf("open zip archive: %w", err)
}
file := findArchiveFile(reader.File)
if file == nil {
return nil, fmt.Errorf("zip archive does not contain a supported file")
}
if c.MaxArchiveFileSize > 0 && file.UncompressedSize64 > c.MaxArchiveFileSize {
return 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, 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.Parse(zipReader, c.Config.Validator, c.Config.Limit)
}
func findArchiveFile(files []*zip.File) *zip.File {
var fallback *zip.File
for _, file := range files {
if file.FileInfo().IsDir() {
continue
}
if fallback == nil {
fallback = file
}
name := strings.ToLower(file.Name)
if strings.HasSuffix(name, ".txt") ||
strings.HasSuffix(name, ".json") ||
strings.HasSuffix(name, ".xml") ||
strings.HasSuffix(name, ".rss") {
return file
}
}
return fallback
}

50
examples/blocklist.go Normal file
View File

@@ -0,0 +1,50 @@
package main
import (
"fmt"
"git.kor-elf.net/kor-elf-shield/blocklist"
"git.kor-elf.net/kor-elf-shield/blocklist/parser"
)
/**
* An example of how to get a list of IP addresses from a service https://www.blocklist.de/en/export.html
*/
func main() {
// Getting a list of IP addresses that were entered in the last hour
// time=seconds
url := "https://api.blocklist.de/getlast.php?time=3600"
extract := parser.NewDefaultTextExtract(0, "\t")
pars, err := parser.NewText(extract)
if err != nil {
panic(err)
}
// limit 0 - no limit
limit := uint(0)
config := blocklist.NewConfig(limit)
ips, err := blocklist.Get(url, pars, config)
if err != nil {
panic(err)
}
fmt.Println(ips)
/*
// 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
url := "http://lists.blocklist.de/lists/all.txt"
extract := parser.NewDefaultTextExtract(0, "\t")
pars, err := parser.NewText(extract)
if err != nil {
panic(err)
}
// limit 0 - no limit
limit := uint(0)
config := blocklist.NewConfig(limit)
ips, err := blocklist.Get(url, pars, config)
if err != nil {
panic(err)
}
fmt.Println(ips)
*/
}

View File

@@ -0,0 +1,29 @@
package main
import (
"fmt"
"git.kor-elf.net/kor-elf-shield/blocklist"
"git.kor-elf.net/kor-elf-shield/blocklist/parser"
)
/**
* An example of how to get a list of IP addresses from a service https://danger.rulez.sk/index.php/bruteforceblocker/
*/
func main() {
url := "https://danger.rulez.sk/projects/bruteforceblocker/blist.php"
extract := parser.NewDefaultTextExtract(0, "\t")
pars, err := parser.NewText(extract)
if err != nil {
panic(err)
}
// limit 0 - no limit
limit := uint(0)
config := blocklist.NewConfig(limit)
ips, err := blocklist.Get(url, pars, config)
if err != nil {
panic(err)
}
fmt.Println(ips)
}

29
examples/ciarmy.go Normal file
View File

@@ -0,0 +1,29 @@
package main
import (
"fmt"
"git.kor-elf.net/kor-elf-shield/blocklist"
"git.kor-elf.net/kor-elf-shield/blocklist/parser"
)
/**
* An example of how to get a list of IP addresses from a service https://www.ciarmy.com/#list
*/
func main() {
url := "https://www.ciarmy.com/list/ci-badguys.txt"
extract := parser.NewDefaultTextExtract(0, " ")
pars, err := parser.NewText(extract)
if err != nil {
panic(err)
}
// limit 0 - no limit
limit := uint(0)
config := blocklist.NewConfig(limit)
ips, err := blocklist.Get(url, pars, config)
if err != nil {
panic(err)
}
fmt.Println(ips)
}

29
examples/greensnow.go Normal file
View File

@@ -0,0 +1,29 @@
package main
import (
"fmt"
"git.kor-elf.net/kor-elf-shield/blocklist"
"git.kor-elf.net/kor-elf-shield/blocklist/parser"
)
/**
* An example of how to get a list of IP addresses from a service https://greensnow.co/
*/
func main() {
url := "https://blocklist.greensnow.co/greensnow.txt"
extract := parser.NewDefaultTextExtract(0, " ")
pars, err := parser.NewText(extract)
if err != nil {
panic(err)
}
// limit 0 - no limit
limit := uint(0)
config := blocklist.NewConfig(limit)
ips, err := blocklist.Get(url, pars, config)
if err != nil {
panic(err)
}
fmt.Println(ips)
}

31
examples/stopforumspam.go Normal file
View File

@@ -0,0 +1,31 @@
package main
import (
"fmt"
"git.kor-elf.net/kor-elf-shield/blocklist"
"git.kor-elf.net/kor-elf-shield/blocklist/parser"
)
/**
* An example of how to get a list of IP addresses from a service https://www.stopforumspam.com/downloads
*/
func main() {
url := "https://www.stopforumspam.com/downloads/listed_ip_1.zip"
//url := "https://www.stopforumspam.com/downloads/listed_ip_1_ipv6.zip"
extract := parser.NewDefaultTextExtract(0, " ")
pars, err := parser.NewText(extract)
if err != nil {
panic(err)
}
// limit 0 - no limit
limit := uint(0)
config := blocklist.NewConfig(limit)
configZip := blocklist.NewConfigZip(config)
ips, err := blocklist.GetZip(url, pars, configZip)
if err != nil {
panic(err)
}
fmt.Println(ips)
}