elliotwutingfeng/go-fasttld

GitHub: elliotwutingfeng/go-fasttld

一个基于 Mozilla Public Suffix List 的高性能 Go 语言 eTLD 提取库,用于从 URL 中精确解析各层级域名组件。

Stars: 39 | Forks: 8

# go-fasttld [![Go 参考](https://img.shields.io/badge/go-reference-blue?logo=go&logoColor=white&style=for-the-badge)](https://pkg.go.dev/github.com/elliotwutingfeng/go-fasttld) [![Go 报告卡](https://goreportcard.com/badge/github.com/elliotwutingfeng/go-fasttld?style=for-the-badge)](https://goreportcard.com/report/github.com/elliotwutingfeng/go-fasttld) [![Coveralls](https://img.shields.io/coverallsCoverage/github/elliotwutingfeng/go-fasttld?logo=coveralls&style=for-the-badge)](https://coveralls.io/github/elliotwutingfeng/go-fasttld?branch=main) [![在 Awesome Go 中被提及](https://img.shields.io/static/v1?logo=awesomelists&label=&labelColor=CCA6C4&logoColor=261120&message=Mentioned%20in%20awesome&color=494368&style=for-the-badge)](https://github.com/avelino/awesome-go) [![GitHub 许可证](https://img.shields.io/badge/LICENSE-BSD--3--CLAUSE-GREEN?style=for-the-badge)](LICENSE) ## 简介 **go-fasttld** 是一个高性能的[有效顶级域名 (eTLD)](https://wiki.mozilla.org/Public_Suffix_List)提取模块,可从 [URL](https://en.wikipedia.org/wiki/URL)中提取子组件。 URL 可以包含主机名、IPv4 地址或 IPv6 地址。eTLD 的提取基于 [Mozilla Public Suffix List](http://www.publicsuffix.org)。[Mozilla Public Suffix List](http://www.publicsuffix.org) 中列出的私有域名(如 'blogspot.co.uk' 和 'sinaapp.com')也受支持。 ![演示](https://raw.githubusercontent.com/elliotwutingfeng/go-fasttld/main/demo.gif) 发现了任何 Bug?请[在此报告](https://github.com/elliotwutingfeng/go-fasttld/issues)。 ## 安装 ``` go get github.com/elliotwutingfeng/go-fasttld ``` ## 尝试 CLI 首先,构建 CLI 应用程序。 ``` # 首先 `git clone` 并 `cd` 到 go-fasttld repository 文件夹 make build_cli ``` 然后,尝试从 URL 中提取子组件。 ``` # 首先 `git clone` 并 `cd` 到 go-fasttld repository 文件夹 ./dist/fasttld extract https://user@a.subdomain.example.a%63.uk:5000/a/b\?id\=42 ``` ## 尝试示例代码 以下所有示例均可在 `examples/demo.go` 中找到。要运行演示,请执行以下命令: ``` # 首先 `git clone` 并 `cd` 到 go-fasttld repository 文件夹 make demo ``` ### 主机名 ``` // Initialise fasttld extractor extractor, _ := fasttld.New(fasttld.SuffixListParams{}) // Extract URL subcomponents url := "https://user@a.subdomain.example.a%63.uk:5000/a/b?id=42" res, _ := extractor.Extract(fasttld.URLParams{URL: url}) // Display results fasttld.PrintRes(url, res) // Pretty-prints res.Scheme, res.UserInfo, res.SubDomain etc. ``` | Scheme | UserInfo | SubDomain | Domain | Suffix | RegisteredDomain | Port | Path | HostType | |----------|----------|-------------|---------|---------|------------------|------|------------|----------| | https:// | user | a.subdomain | example | a%63.uk | example.a%63.uk | 5000 | /a/b?id=42 | hostname | ### IPv4 地址 ``` extractor, _ := fasttld.New(fasttld.SuffixListParams{}) url := "https://127.0.0.1:5000" res, _ := extractor.Extract(fasttld.URLParams{URL: url}) ``` | Scheme | UserInfo | SubDomain | Domain | Suffix | RegisteredDomain | Port | Path | HostType | |----------|----------|-----------|-----------|--------|------------------|------|------|--------------| | https:// | | | 127.0.0.1 | | 127.0.0.1 | 5000 | | ipv4 address | ### IPv6 地址 ``` extractor, _ := fasttld.New(fasttld.SuffixListParams{}) url := "https://[aBcD:ef01:2345:6789:aBcD:ef01:2345:6789]:5000" res, _ := extractor.Extract(fasttld.URLParams{URL: url}) ``` | Scheme | UserInfo | SubDomain | Domain | Suffix | RegisteredDomain | Port | Path | HostType | |----------|----------|-----------|-----------------------------------------|--------|-----------------------------------------|------|------|--------------| | https:// | | | aBcD:ef01:2345:6789:aBcD:ef01:2345:6789 | | aBcD:ef01:2345:6789:aBcD:ef01:2345:6789 | 5000 | | ipv6 address | ### 国际化标签分隔符 **go-fasttld** 支持以下国际化标签分隔符 (IETF RFC 3490) | Full Stop | Ideographic Full Stop | Fullwidth Full Stop | Halfwidth Ideographic Full Stop | |------------|-----------------------|---------------------|---------------------------------| | U+002E `.` | U+3002 `。` | U+FF0E `.` | U+FF61 `。` | ``` extractor, _ := fasttld.New(fasttld.SuffixListParams{}) url := "https://brb\u002ei\u3002am\uff0egoing\uff61to\uff0ebe\u3002a\uff61fk" res, _ := extractor.Extract(fasttld.URLParams{URL: url}) ``` | Scheme | UserInfo | SubDomain | Domain | Suffix | RegisteredDomain | Port | Path | HostType | |----------|----------|---------------------------------------|--------|-----------|-------------------|------|------|----------| | https:// | | brb\u002ei\u3002am\uff0egoing\uff61to | be | a\uff61fk | be\u3002a\uff61fk | | | hostname | ## Public Suffix List 选项 ### 指定自定义 Public Suffix List 文件 您可以通过将 `fasttld.SuffixListParams{}` 中的 `CacheFilePath` 设置为其绝对路径,来使用自定义的 Public Suffix List 文件。 ``` cacheFilePath := "/absolute/path/to/file.dat" extractor, err := fasttld.New(fasttld.SuffixListParams{CacheFilePath: cacheFilePath}) ``` ### 更新默认的 Public Suffix List 缓存 每当调用 `fasttld.New` 且未在 `fasttld.SuffixListParams{}` 中指定 `CacheFilePath` 时,如果默认的 Public Suffix List 本地缓存超过 3 天,就会自动更新。您也可以使用 `Update()` 手动更新缓存。 ``` // Automatic update performed if `CacheFilePath` is not specified // and local cache is more than 3 days old extractor, _ := fasttld.New(fasttld.SuffixListParams{}) // Manually update local cache if err := extractor.Update(); err != nil { log.Println(err) } ``` ### 私有域名 根据 [Mozilla.org wiki](https://wiki.mozilla.org/Public_Suffix_List/Uses),Mozilla Public Suffix List 包含私有域名,例如 `blogspot.com` 和 `sinaapp.com`。 默认情况下,这些私有域名会被排除(即 `IncludePrivateSuffix = false`) ``` extractor, _ := fasttld.New(fasttld.SuffixListParams{}) url := "https://google.blogspot.com" res, _ := extractor.Extract(fasttld.URLParams{URL: url}) ``` | Scheme | UserInfo | SubDomain | Domain | Suffix | RegisteredDomain | Port | Path | HostType | |----------|----------|-----------|----------|--------|------------------|------|------|----------| | https:// | | google | blogspot | com | blogspot.com | | | hostname | 您可以通过设置 `IncludePrivateSuffix = true` 来_包含_私有域名 ``` extractor, _ := fasttld.New(fasttld.SuffixListParams{IncludePrivateSuffix: true}) url := "https://google.blogspot.com" res, _ := extractor.Extract(fasttld.URLParams{URL: url}) ``` | Scheme | UserInfo | SubDomain | Domain | Suffix | RegisteredDomain | Port | Path | HostType | |----------|----------|-----------|--------|--------------|---------------------|------|------|----------| | https:// | | | google | blogspot.com | google.blogspot.com | | | hostname | ## 提取选项 ### 忽略子域名 您可以通过设置 `IgnoreSubDomains = true` 来忽略子域名。默认情况下,会提取子域名。 ``` extractor, _ := fasttld.New(fasttld.SuffixListParams{}) url := "https://maps.google.com" res, _ := extractor.Extract(fasttld.URLParams{URL: url, IgnoreSubDomains: true}) ``` | Scheme | UserInfo | SubDomain | Domain | Suffix | RegisteredDomain | Port | Path | HostType | |----------|----------|-----------|--------|--------|------------------|------|------|----------| | https:// | | | google | com | google.com | | | hostname | ### Punycode 默认情况下,国际化 URL 在提取之前不会转换为 Punycode。 ``` extractor, _ := fasttld.New(fasttld.SuffixListParams{}) url := "https://hello.世界.com" res, _ := extractor.Extract(fasttld.URLParams{URL: url}) ``` | Scheme | UserInfo | SubDomain | Domain | Suffix | RegisteredDomain | Port | Path | HostType | |----------|----------|-----------|--------|--------|------------------|------|------|----------| | https:// | | hello | 世界 | com | 世界.com | | | hostname | 您可以通过设置 `ConvertURLToPunyCode = true` 在提取之前将国际化 URL 转换为 [Punycode](https://en.wikipedia.org/wiki/Punycode)。 ``` extractor, _ := fasttld.New(fasttld.SuffixListParams{}) url := "https://hello.世界.com" res, _ := extractor.Extract(fasttld.URLParams{URL: url, ConvertURLToPunyCode: true}) ``` | Scheme | UserInfo | SubDomain | Domain | Suffix | RegisteredDomain | Port | Path | HostType | |----------|----------|-----------|-------------|--------|------------------|------|------|----------| | https:// | | hello | xn--rhqv96g | com | xn--rhqv96g.com | | | hostname | ## 解析错误 如果 URL 无效,`Extract()` 返回的第二个值 **error** 将不为 nil。仍然可以从返回的第一个值 **ExtractResult** 中获取部分提取的子组件。 ``` extractor, _ := fasttld.New(fasttld.SuffixListParams{}) url := "https://example!.com" // invalid characters in hostname color.New().Println("The following line should be an error message") if res, err := extractor.Extract(fasttld.URLParams{URL: url}); err != nil { color.New(color.FgHiRed, color.Bold).Print("Error: ") color.New(color.FgHiWhite).Println(err) } fasttld.PrintRes(url, res) // Partially extracted subcomponents can still be retrieved ``` | Scheme | UserInfo | SubDomain | Domain | Suffix | RegisteredDomain | Port | Path | HostType | |----------|----------|-----------|--------|--------|------------------|------|------|----------| | https:// | | | | | | | | | ## 测试 ``` # 首先 `git clone` 并 `cd` 到 go-fasttld repository 文件夹 make tests # 或者,在没有 race detection 的情况下运行测试 # 适用于不支持 -race flag 的系统,例如 windows/386 # 参见 https://tip.golang.org/src/cmd/dist/test.go make tests_without_race ``` ## 基准测试 ``` # 首先 `git clone` 并 `cd` 到 go-fasttld repository 文件夹 make bench ``` ### 使用的模块 | Benchmark Name | Source | |----------------------|----------------------------------| | GoFastTld | go-fasttld(本模块) | | JPilloraGoTld | github.com/jpillora/go-tld | | JoeGuoTldExtract | github.com/joeguo/tldextract | | Mjd2021USATldExtract | github.com/mjd2021usa/tldextract | ### 结果 基准测试在 AMD Ryzen 7 5800X、Manjaro Linux 上进行。 **go-fasttld** 在处理较长的 URL 时表现尤为出色。 #### #1 https://iupac.org/iupac-announces-the-2021-top-ten-emerging-technologies-in-chemistry/ | Benchmark Name | Iterations | ns/op | B/op | allocs/op | Fastest | |----------------------|------------|-------------|----------|-------------|--------------------| | GoFastTld | 8037906 | 150.8 ns/op | 0 B/op | 0 allocs/op | :heavy_check_mark: | | JPilloraGoTld | 1675113 | 716.1 ns/op | 224 B/op | 2 allocs/op | | | JoeGuoTldExtract | 2204854 | 515.1 ns/op | 272 B/op | 5 allocs/op | | | Mjd2021USATldExtract | 1676722 | 712.0 ns/op | 288 B/op | 6 allocs/op | | #### #2 https://www.google.com/maps/dir/Parliament+Place,+Parliament+House+Of+Singapore,+Singapore/Parliament+St,+London,+UK/@25.2440033,33.6721455,4z/data=!3m1!4b1!4m14!4m13!1m5!1m1!1s0x31da19a0abd4d71d:0xeda26636dc4ea1dc!2m2!1d103.8504863!2d1.2891543!1m5!1m1!1s0x487604c5aaa7da5b:0xf13a2197d7e7dd26!2m2!1d-0.1260826!2d51.5017061!3e4 | Benchmark Name | Iterations | ns/op | B/op | allocs/op | Fastest | |----------------------|------------|-------------|-----------|-------------|--------------------| | GoFastTld | 6381516 | 181.9 ns/op | 0 B/op | 0 allocs/op | :heavy_check_mark: | | JPilloraGoTld | 431671 | 2603 ns/op | 928 B/op | 4 allocs/op | | | JoeGuoTldExtract | 893347 | 1176 ns/op | 1120 B/op | 6 allocs/op | | | Mjd2021USATldExtract | 1030250 | 1165 ns/op | 1120 B/op | 6 allocs/op | | #### #3 https://a.b.c.d.e.f.g.h.i.j.k.l.m.n.oo.pp.qqq.rrrr.ssssss.tttttttt.uuuuuuuuuuu.vvvvvvvvvvvvvvv.wwwwwwwwwwwwwwwwwwwwww.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.cc | Benchmark Name | Iterations | ns/op | B/op | allocs/op | Fastest | |----------------------|------------|------------|-----------|-------------|--------------------| | GoFastTld | 833682 | 1424 ns/op | 0 B/op | 0 allocs/op | :heavy_check_mark: | | JPilloraGoTld | 734790 | 1640 ns/op | 304 B/op | 3 allocs/op | | | JoeGuoTldExtract | 695475 | 1452 ns/op | 1040 B/op | 5 allocs/op | | | Mjd2021USATldExtract | 330717 | 3628 ns/op | 1904 B/op | 8 allocs/op | | ## 实现细节 ### 为什么不按“.”分割并直接取最后一个元素呢? 按“.”分割并取最后一个元素的方法仅适用于像 `com` 这样简单的 eTLD,但不适用于像 `oseto.nagasaki.jp` 这样更复杂的 eTLD。 ### eTLD 字典树 ![Trie](https://raw.githubusercontent.com/elliotwutingfeng/go-fasttld/main/Trie_example.svg) **go-fasttld** 将 eTLD 存储在[压缩字典树](https://en.wikipedia.org/wiki/Trie)中。 来自 [Mozilla Publicuffix List](http://www.publicsuffix.org) 的有效 eTLD 会以倒序添加到压缩字典树中。 ``` Given the following eTLDs au nsw.edu.au com.ac edu.ac gov.ac and the example URL host `example.nsw.edu.au` The compressed trie will be structured as follows: START ╠═ au 🚩 ✅ ║ ╚═ edu ✅ ║ ╚═ nsw 🚩 ✅ ╚═ ac ╠═ com 🚩 ╠═ edu 🚩 ╚═ gov 🚩 === Symbol meanings === 🚩 : path to this node is a valid eTLD ✅ : path to this node found in example URL host `example.nsw.edu.au` ``` URL 主机子组件会从右向左解析,直到找不到匹配的节点为止。在此示例中,匹配节点的路径为 `au -> edu -> nsw`。将节点倒序即可得到提取出的 eTLD `nsw.edu.au`。 ## 致谢 本模块是 Python [fasttld](https://github.com/jophy/fasttld) 模块的移植版本,并进行了额外的修改,以支持从完整 URL、IPv4 地址和 IPv6 地址中提取子组件。 - [fasttld (Python)](https://github.com/jophy/fasttld) - [tldextract (Python)](https://github.com/john-kurkowski/tldextract) - [ICANN IDN 字符验证指南](https://www.icann.org/resources/pages/idna-protocol-2012-02-25-en) - [IETF RFC 2396](https://www.ietf.org/rfc/rfc2396.txt) - [IETF RFC 3490](https://www.ietf.org/rfc/rfc3490.txt) - [IETF RFC 3986](https://www.ietf.org/rfc/rfc3986.txt) - [IETF RFC 6874](https://www.ietf.org/rfc/rfc6874.txt)
标签:eTLD提取, EVTX分析, Go, Ruby工具, SYN扫描, URL解析, 域名解析, 文档结构分析, 日志审计, 网络处理