fetchSubscriptionFromURL fetches and parses a subscription. allowInsecure must be true to accept http:// URLs; when set, we also suppress the x-hwid header because sending a stable device identifier in plaintext is exactly the leak the warning is opted into.
(subURL string, allowInsecure bool)
| 2034 | // x-hwid header because sending a stable device identifier in plaintext is |
| 2035 | // exactly the leak the warning is opted into. |
| 2036 | func (a *App) fetchSubscriptionFromURL(subURL string, allowInsecure bool) ([]config.ProxyEntry, int64, int64, int64, int64, string, string, error) { |
| 2037 | if resolved, err := resolveEncryptedSubscriptionURL(subURL); err != nil { |
| 2038 | return nil, 0, 0, 0, 0, "", "", err |
| 2039 | } else if resolved != "" { |
| 2040 | subURL = resolved |
| 2041 | } |
| 2042 | insecure := isInsecureSubURL(subURL) |
| 2043 | if insecure && !allowInsecure { |
| 2044 | return nil, 0, 0, 0, 0, "", "", ErrInsecureSubscription |
| 2045 | } |
| 2046 | jar, _ := cookiejar.New(nil) |
| 2047 | client := &http.Client{Timeout: 15 * time.Second, Jar: jar} |
| 2048 | metadata := a.subscriptionRequestMetadata() |
| 2049 | |
| 2050 | doFetch := func(userAgent string) ([]config.ProxyEntry, int64, int64, int64, int64, string, string, bool, error) { |
| 2051 | req, err := http.NewRequest(http.MethodGet, subURL, nil) |
| 2052 | if err != nil { |
| 2053 | return nil, 0, 0, 0, 0, "", "", false, fmt.Errorf("creating subscription request: %w", err) |
| 2054 | } |
| 2055 | req.Header.Set("User-Agent", userAgent) |
| 2056 | // Remnawave HWID device identification headers. |
| 2057 | req.Header.Set("x-device-os", metadata.Platform) |
| 2058 | req.Header.Set("x-ver-os", metadata.OSVersion) |
| 2059 | req.Header.Set("x-device-model", metadata.Model) |
| 2060 | // Only attach HWID to HTTPS requests. On plaintext http:// the HWID |
| 2061 | // would be sniffable end-to-end and would link the user's device |
| 2062 | // across every network hop and intermediary — the privacy cost |
| 2063 | // outweighs any HWID-based device-limit check the provider does. |
| 2064 | if !insecure && metadata.SendHWID { |
| 2065 | if hwid := a.subscriptionHWID(subURL); hwid != "" { |
| 2066 | req.Header.Set("x-hwid", hwid) |
| 2067 | } |
| 2068 | } |
| 2069 | |
| 2070 | resp, err := client.Do(req) |
| 2071 | if err != nil { |
| 2072 | return nil, 0, 0, 0, 0, "", "", false, fmt.Errorf("fetching subscription: %w", err) |
| 2073 | } |
| 2074 | defer resp.Body.Close() |
| 2075 | |
| 2076 | if resp.StatusCode != http.StatusOK { |
| 2077 | return nil, 0, 0, 0, 0, "", "", false, fmt.Errorf("subscription returned HTTP %d", resp.StatusCode) |
| 2078 | } |
| 2079 | |
| 2080 | profileTitle := parseSubscriptionHeaderText(resp.Header.Get("Profile-Title")) |
| 2081 | up, down, tot, exp := parseSubscriptionUserInfoHeader(resp.Header.Get("Subscription-Userinfo")) |
| 2082 | iconURL := resolveSubscriptionIcon(client, subURL, resp.Header) |
| 2083 | |
| 2084 | bodyBytes, err := io.ReadAll(resp.Body) |
| 2085 | if err != nil { |
| 2086 | return nil, up, down, tot, exp, iconURL, profileTitle, false, fmt.Errorf("reading subscription body: %w", err) |
| 2087 | } |
| 2088 | bodyStr := string(bodyBytes) |
| 2089 | |
| 2090 | if iconURL == "" && strings.Contains(bodyStr, "<link") { |
| 2091 | if fromBody := pickIconFromSubscriptionHTML(client, subURL, bodyStr); fromBody != "" { |
| 2092 | iconURL = fromBody |
| 2093 | } |