parse_test.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. package proxyurl
  2. import (
  3. "strings"
  4. "testing"
  5. )
  6. func TestParse_空字符串直连(t *testing.T) {
  7. trimmed, parsed, err := Parse("")
  8. if err != nil {
  9. t.Fatalf("空字符串应直连: %v", err)
  10. }
  11. if trimmed != "" {
  12. t.Errorf("trimmed 应为空: got %q", trimmed)
  13. }
  14. if parsed != nil {
  15. t.Errorf("parsed 应为 nil: got %v", parsed)
  16. }
  17. }
  18. func TestParse_空白字符串直连(t *testing.T) {
  19. trimmed, parsed, err := Parse(" ")
  20. if err != nil {
  21. t.Fatalf("空白字符串应直连: %v", err)
  22. }
  23. if trimmed != "" {
  24. t.Errorf("trimmed 应为空: got %q", trimmed)
  25. }
  26. if parsed != nil {
  27. t.Errorf("parsed 应为 nil: got %v", parsed)
  28. }
  29. }
  30. func TestParse_有效HTTP代理(t *testing.T) {
  31. trimmed, parsed, err := Parse("http://proxy.example.com:8080")
  32. if err != nil {
  33. t.Fatalf("有效 HTTP 代理应成功: %v", err)
  34. }
  35. if trimmed != "http://proxy.example.com:8080" {
  36. t.Errorf("trimmed 不匹配: got %q", trimmed)
  37. }
  38. if parsed == nil {
  39. t.Fatal("parsed 不应为 nil")
  40. }
  41. if parsed.Host != "proxy.example.com:8080" {
  42. t.Errorf("Host 不匹配: got %q", parsed.Host)
  43. }
  44. }
  45. func TestParse_有效HTTPS代理(t *testing.T) {
  46. _, parsed, err := Parse("https://proxy.example.com:443")
  47. if err != nil {
  48. t.Fatalf("有效 HTTPS 代理应成功: %v", err)
  49. }
  50. if parsed.Scheme != "https" {
  51. t.Errorf("Scheme 不匹配: got %q", parsed.Scheme)
  52. }
  53. }
  54. func TestParse_有效SOCKS5代理_自动升级为SOCKS5H(t *testing.T) {
  55. trimmed, parsed, err := Parse("socks5://127.0.0.1:1080")
  56. if err != nil {
  57. t.Fatalf("有效 SOCKS5 代理应成功: %v", err)
  58. }
  59. // socks5 自动升级为 socks5h,确保 DNS 由代理端解析
  60. if trimmed != "socks5h://127.0.0.1:1080" {
  61. t.Errorf("trimmed 应升级为 socks5h: got %q", trimmed)
  62. }
  63. if parsed.Scheme != "socks5h" {
  64. t.Errorf("Scheme 应升级为 socks5h: got %q", parsed.Scheme)
  65. }
  66. }
  67. func TestParse_无效URL(t *testing.T) {
  68. _, _, err := Parse("://invalid")
  69. if err == nil {
  70. t.Fatal("无效 URL 应返回错误")
  71. }
  72. if !strings.Contains(err.Error(), "invalid proxy URL") {
  73. t.Errorf("错误信息应包含 'invalid proxy URL': got %s", err.Error())
  74. }
  75. }
  76. func TestParse_缺少Host(t *testing.T) {
  77. _, _, err := Parse("http://")
  78. if err == nil {
  79. t.Fatal("缺少 host 应返回错误")
  80. }
  81. if !strings.Contains(err.Error(), "missing host") {
  82. t.Errorf("错误信息应包含 'missing host': got %s", err.Error())
  83. }
  84. }
  85. func TestParse_不支持的Scheme(t *testing.T) {
  86. _, _, err := Parse("ftp://proxy.example.com:21")
  87. if err == nil {
  88. t.Fatal("不支持的 scheme 应返回错误")
  89. }
  90. if !strings.Contains(err.Error(), "unsupported proxy scheme") {
  91. t.Errorf("错误信息应包含 'unsupported proxy scheme': got %s", err.Error())
  92. }
  93. }
  94. func TestParse_含密码URL脱敏(t *testing.T) {
  95. // 场景 1: 带密码的 socks5 URL 应成功解析并升级为 socks5h
  96. trimmed, parsed, err := Parse("socks5://user:secret_password@proxy.local:1080")
  97. if err != nil {
  98. t.Fatalf("含密码的有效 URL 应成功: %v", err)
  99. }
  100. if trimmed == "" || parsed == nil {
  101. t.Fatal("应返回非空结果")
  102. }
  103. if parsed.Scheme != "socks5h" {
  104. t.Errorf("Scheme 应升级为 socks5h: got %q", parsed.Scheme)
  105. }
  106. if !strings.HasPrefix(trimmed, "socks5h://") {
  107. t.Errorf("trimmed 应以 socks5h:// 开头: got %q", trimmed)
  108. }
  109. if parsed.User == nil {
  110. t.Error("升级后应保留 UserInfo")
  111. }
  112. // 场景 2: 带密码但缺少 host(触发 Redacted 脱敏路径)
  113. _, _, err = Parse("http://user:secret_password@:0/")
  114. if err == nil {
  115. t.Fatal("缺少 host 应返回错误")
  116. }
  117. if strings.Contains(err.Error(), "secret_password") {
  118. t.Error("错误信息不应包含明文密码")
  119. }
  120. if !strings.Contains(err.Error(), "missing host") {
  121. t.Errorf("错误信息应包含 'missing host': got %s", err.Error())
  122. }
  123. }
  124. func TestParse_带空白的有效URL(t *testing.T) {
  125. trimmed, parsed, err := Parse(" http://proxy.example.com:8080 ")
  126. if err != nil {
  127. t.Fatalf("带空白的有效 URL 应成功: %v", err)
  128. }
  129. if trimmed != "http://proxy.example.com:8080" {
  130. t.Errorf("trimmed 应去除空白: got %q", trimmed)
  131. }
  132. if parsed == nil {
  133. t.Fatal("parsed 不应为 nil")
  134. }
  135. }
  136. func TestParse_Scheme大小写不敏感(t *testing.T) {
  137. // 大写 SOCKS5 应被接受并升级为 socks5h
  138. trimmed, parsed, err := Parse("SOCKS5://proxy.example.com:1080")
  139. if err != nil {
  140. t.Fatalf("大写 SOCKS5 应被接受: %v", err)
  141. }
  142. if parsed.Scheme != "socks5h" {
  143. t.Errorf("大写 SOCKS5 Scheme 应升级为 socks5h: got %q", parsed.Scheme)
  144. }
  145. if !strings.HasPrefix(trimmed, "socks5h://") {
  146. t.Errorf("大写 SOCKS5 trimmed 应升级为 socks5h://: got %q", trimmed)
  147. }
  148. // 大写 HTTP 应被接受(不变)
  149. _, _, err = Parse("HTTP://proxy.example.com:8080")
  150. if err != nil {
  151. t.Fatalf("大写 HTTP 应被接受: %v", err)
  152. }
  153. }
  154. func TestParse_带认证的有效代理(t *testing.T) {
  155. trimmed, parsed, err := Parse("http://user:pass@proxy.example.com:8080")
  156. if err != nil {
  157. t.Fatalf("带认证的代理 URL 应成功: %v", err)
  158. }
  159. if parsed.User == nil {
  160. t.Error("应保留 UserInfo")
  161. }
  162. if trimmed != "http://user:pass@proxy.example.com:8080" {
  163. t.Errorf("trimmed 不匹配: got %q", trimmed)
  164. }
  165. }
  166. func TestParse_IPv6地址(t *testing.T) {
  167. trimmed, parsed, err := Parse("http://[::1]:8080")
  168. if err != nil {
  169. t.Fatalf("IPv6 代理 URL 应成功: %v", err)
  170. }
  171. if parsed.Hostname() != "::1" {
  172. t.Errorf("Hostname 不匹配: got %q", parsed.Hostname())
  173. }
  174. if trimmed != "http://[::1]:8080" {
  175. t.Errorf("trimmed 不匹配: got %q", trimmed)
  176. }
  177. }
  178. func TestParse_SOCKS5H保持不变(t *testing.T) {
  179. trimmed, parsed, err := Parse("socks5h://proxy.local:1080")
  180. if err != nil {
  181. t.Fatalf("有效 SOCKS5H 代理应成功: %v", err)
  182. }
  183. // socks5h 不需要升级,应保持原样
  184. if trimmed != "socks5h://proxy.local:1080" {
  185. t.Errorf("trimmed 不应变化: got %q", trimmed)
  186. }
  187. if parsed.Scheme != "socks5h" {
  188. t.Errorf("Scheme 应保持 socks5h: got %q", parsed.Scheme)
  189. }
  190. }
  191. func TestParse_无Scheme裸地址(t *testing.T) {
  192. // 无 scheme 的裸地址,Go url.Parse 将其视为 path,Host 为空
  193. _, _, err := Parse("proxy.example.com:8080")
  194. if err == nil {
  195. t.Fatal("无 scheme 的裸地址应返回错误")
  196. }
  197. }