| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- package sub
- import (
- "net/http"
- "net/http/httptest"
- "strings"
- "testing"
- "github.com/gin-gonic/gin"
- )
- func fullIncyConfig() IncyConfig {
- return IncyConfig{
- AutoDetect: true,
- ProfileDescription: "Fast and stable network",
- SortOrder: "ping",
- SupportEmail: "[email protected]",
- AnnounceUrl: "https://t.me/incy_news",
- PremiumUrl: "https://example.com/buy",
- BannerText: "Summer sale",
- BannerButtonText: "Buy now",
- BannerButtonUrl: "https://example.com/sale",
- BannerBgColor: "#E53E3E",
- BannerButtonColor: "#38A169",
- HideUrl: "1",
- HideCheck: "true",
- NoLimitEnabled: "0",
- PerAppProxyEnable: "1",
- PerAppProxyMode: "bypass",
- PerAppProxyList: "com.google.chrome,org.telegram.messenger",
- FragmentationEnable: "1",
- FragmentationLength: "10-30",
- FragmentationInterval: "20-40",
- FragmentationPackets: "tlshello",
- NoisesEnable: "1",
- NoisesType: "rand",
- NoisesPacket: "10-20",
- NoisesDelay: "10-50",
- ServerAddressResolveEnable: "1",
- ServerAddressResolveDnsDomain: "https://common.dot.dns.yandex.net/dns-query",
- ServerAddressResolveDnsIp: "77.88.8.8",
- }
- }
- func applyIncyToHeaders(t *testing.T, cfg IncyConfig, userAgent string) http.Header {
- t.Helper()
- gin.SetMode(gin.TestMode)
- recorder := httptest.NewRecorder()
- ctx, _ := gin.CreateTestContext(recorder)
- ctx.Request = httptest.NewRequest(http.MethodGet, "/sub/test", nil)
- if userAgent != "" {
- ctx.Request.Header.Set("User-Agent", userAgent)
- }
- ApplyIncyHeaders(ctx, cfg, cfg.AutoDetect && IsIncyClient(ctx.GetHeader("User-Agent")))
- return recorder.Header()
- }
- func TestIsIncyClient(t *testing.T) {
- for _, tc := range []struct {
- userAgent string
- want bool
- }{
- {"INCY/1.0.0/ios", true},
- {"incy/2.4/android", true},
- {"Mozilla/5.0 (Linux; Android 14) INCY/1.2.3/android", true},
- {"Happ/1.2.0 (iPhone; iOS 17.5)", false},
- {"v2rayNG/1.8.5", false},
- {"", false},
- } {
- if got := IsIncyClient(tc.userAgent); got != tc.want {
- t.Errorf("IsIncyClient(%q) = %v, want %v", tc.userAgent, got, tc.want)
- }
- }
- }
- func TestApplyIncyHeaders_AllDocumentedHeaders(t *testing.T) {
- cfg := fullIncyConfig()
- h := applyIncyToHeaders(t, cfg, "INCY/1.0.0/android")
- want := map[string]string{
- "Profile-Description": "Fast and stable network",
- "Sort-Order": "ping",
- "Support-Email": "[email protected]",
- "Announce-Url": "https://t.me/incy_news",
- "Premium-Url": "https://example.com/buy",
- "Banner-Text": "Summer sale",
- "Banner-Button-Text": "Buy now",
- "Banner-Button-Url": "https://example.com/sale",
- "Banner-Bg-Color": "#E53E3E",
- "Banner-Button-Color": "#38A169",
- "Hide-Url": "1",
- "Hide-Check": "1",
- "No-Limit-Enabled": "0",
- "Per-App-Proxy-Enable": "1",
- "Per-App-Proxy-Mode": "bypass",
- "Per-App-Proxy-List": "com.google.chrome,org.telegram.messenger",
- "Fragmentation-Enable": "1",
- "Fragmentation-Length": "10-30",
- "Fragmentation-Interval": "20-40",
- "Fragmentation-Packets": "tlshello",
- "Noises-Enable": "1",
- "Noises-Type": "rand",
- "Noises-Packet": "10-20",
- "Noises-Delay": "10-50",
- "Server-Address-Resolve-Enable": "1",
- "Server-Address-Resolve-Dns-Domain": "https://common.dot.dns.yandex.net/dns-query",
- "Server-Address-Resolve-Dns-Ip": "77.88.8.8",
- }
- for name, value := range want {
- if got := h.Get(name); got != value {
- t.Errorf("header %s = %q, want %q", name, got, value)
- }
- }
- }
- func TestApplyIncyHeaders_SkipsUnsetValues(t *testing.T) {
- cfg := IncyConfig{AutoDetect: true}
- cfg.HideUrl = ""
- cfg.SortOrder = ""
- h := applyIncyToHeaders(t, cfg, "INCY/1.0.0/ios")
- for _, name := range []string{"Hide-Url", "Sort-Order", "Fragmentation-Enable", "Banner-Text"} {
- if got := h.Get(name); got != "" {
- t.Errorf("unset header %s = %q, want omitted", name, got)
- }
- }
- }
- func TestApplyIncyHeaders_NotAppliedWithoutIncyClient(t *testing.T) {
- cfg := fullIncyConfig()
- for _, userAgent := range []string{"Happ/1.2.0 (iPhone)", "v2rayNG/1.8.5", ""} {
- h := applyIncyToHeaders(t, cfg, userAgent)
- if got := h.Get("Hide-Url"); got != "" {
- t.Errorf("user-agent %q: Hide-Url = %q, want omitted", userAgent, got)
- }
- }
- }
- func TestApplyIncyHeaders_NotAppliedWhenAutoDetectOff(t *testing.T) {
- cfg := fullIncyConfig()
- cfg.AutoDetect = false
- h := applyIncyToHeaders(t, cfg, "INCY/1.0.0/android")
- if got := h.Get("Hide-Url"); got != "" {
- t.Errorf("AutoDetect off: Hide-Url = %q, want omitted", got)
- }
- }
- func TestIncyHeaderText_Base64ForNonASCII(t *testing.T) {
- ascii := incyHeaderText("Plain banner")
- if ascii != "Plain banner" {
- t.Errorf("ASCII text = %q, want unchanged", ascii)
- }
- cyrillic := incyHeaderText("Здравствуйте")
- if !strings.HasPrefix(cyrillic, "base64:") {
- t.Fatalf("Cyrillic text = %q, want base64: prefix", cyrillic)
- }
- // The docs require base64 for anything outside the ASCII range, so the
- // raw value must not survive on the wire.
- if strings.Contains(cyrillic, "Здравствуйте") {
- t.Errorf("Cyrillic text = %q, want the raw value base64-encoded", cyrillic)
- }
- }
- func TestApplyIncyHeaders_RejectsUndocumentedValues(t *testing.T) {
- cfg := IncyConfig{
- AutoDetect: true,
- SortOrder: "alphabetical", // not none|ping|name
- PerAppProxyMode: "include", // Happ spelling, not bypass|proxy
- NoisesType: "uuid", // not rand|str|hex
- FragmentationLength: "10..30", // not min-max
- FragmentationPackets: "3", // documented only as tlshello|1-3|1|all
- BannerBgColor: "red", // not #RRGGBB
- HideUrl: "maybe", // not 1|0
- }
- h := applyIncyToHeaders(t, cfg, "INCY/1.0.0/android")
- for _, name := range []string{
- "Sort-Order", "Per-App-Proxy-Mode", "Noises-Type",
- "Fragmentation-Length", "Fragmentation-Packets", "Banner-Bg-Color", "Hide-Url",
- } {
- if got := h.Get(name); got != "" {
- t.Errorf("undocumented value accepted for %s: %q", name, got)
- }
- }
- }
- func TestApplyIncyHeaders_PerAppListKeepsSeparators(t *testing.T) {
- // The settings textarea takes one package per line, and a header cannot
- // carry a newline, so each line must stay a separate list entry.
- for _, tc := range []struct {
- name, list, want string
- }{
- {"newline", "com.google.chrome\norg.telegram.messenger", "com.google.chrome,org.telegram.messenger"},
- {"crlf and blank lines", "com.google.chrome\r\n\r\norg.telegram.messenger\r\n", "com.google.chrome,org.telegram.messenger"},
- {"url", " https://example.com/apps.txt ", "https://example.com/apps.txt"},
- } {
- t.Run(tc.name, func(t *testing.T) {
- cfg := IncyConfig{AutoDetect: true, PerAppProxyList: tc.list}
- h := applyIncyToHeaders(t, cfg, "INCY/1.0.0/android")
- if got := h.Get("Per-App-Proxy-List"); got != tc.want {
- t.Errorf("Per-App-Proxy-List = %q, want %q", got, tc.want)
- }
- })
- }
- }
|