refact: tidy helpers

Kim committed Apr 1, 2026 at 14:41 UTC c8270f8d80207f53b93904cfa69f5c9db30d605d
16 files changed +157 -145
cmd/portal-tunnel/http_routes.go
+96 -81
@@ -16,9 +16,13 @@ import (
16 )
17
18 type httpRoute struct {
19 - prefix string
20 - upstream *url.URL
21 - proxy *httputil.ReverseProxy
19 + prefix string
20 + prefixSlash string
21 + upstream *url.URL
22 + upstreamPath string
23 + upstreamPathSlash string
24 + upstreamDomain string
25 + proxy *httputil.ReverseProxy
26 }
27
28 func newHTTPRouteHandler(rawRoutes []string) (http.Handler, error) {
@@ -55,7 +59,7 @@ func newHTTPRouteHandler(rawRoutes []string) (http.Handler, error) {
59 p = "/"
60 }
61 for _, route := range routes {
58 - if route.prefix == "/" || p == route.prefix || strings.HasPrefix(p, route.prefix+"/") {
62 + if route.prefix == "/" || p == route.prefix || strings.HasPrefix(p, route.prefixSlash) {
63 route.proxy.ServeHTTP(w, r)
64 return
65 }
@@ -109,22 +113,25 @@ func parseHTTPRoute(raw string) (*httpRoute, error) {
113 upstream.Fragment = ""
114 upstream.Path = utils.NormalizeURLPath(upstream.Path)
115
112 - return &httpRoute{prefix: prefix, upstream: upstream}, nil
116 + route := &httpRoute{
117 + prefix: prefix,
118 + upstream: upstream,
119 + upstreamPath: upstream.Path,
120 + upstreamDomain: utils.NormalizeHostname(upstream.Hostname()),
121 + }
122 + if prefix != "/" {
123 + route.prefixSlash = prefix + "/"
124 + }
125 + if upstream.Path != "/" {
126 + route.upstreamPathSlash = upstream.Path + "/"
127 + }
128 + return route, nil
129 }
130
131 func (r *httpRoute) newReverseProxy() *httputil.ReverseProxy {
132 return &httputil.ReverseProxy{
117 - Rewrite: r.rewriteRequest,
118 - ModifyResponse: func(resp *http.Response) error {
119 - if resp == nil || resp.Request == nil {
120 - return nil
121 - }
122 - publicHost := resp.Request.Header.Get("X-Forwarded-Host")
123 - publicScheme := resp.Request.Header.Get("X-Forwarded-Proto")
124 - r.rewriteLocation(resp.Header, publicHost, publicScheme)
125 - r.rewriteSetCookies(resp.Header, publicHost)
126 - return nil
127 - },
133 + Rewrite: r.rewriteRequest,
134 + ModifyResponse: r.rewriteResponse,
135 ErrorHandler: func(w http.ResponseWriter, req *http.Request, err error) {
136 log.Error().Err(err).
137 Str("route_prefix", r.prefix).
@@ -136,28 +143,7 @@ func (r *httpRoute) newReverseProxy() *httputil.ReverseProxy {
143 }
144
145 func (r *httpRoute) rewriteRequest(pr *httputil.ProxyRequest) {
139 - // strip route prefix from the path before forwarding
140 - reqPath := utils.NormalizeURLPath(pr.In.URL.Path)
141 - rawPath := pr.In.URL.RawPath
142 - if r.prefix != "/" {
143 - if reqPath == r.prefix {
144 - reqPath = "/"
145 - rawPath = ""
146 - } else {
147 - reqPath = strings.TrimPrefix(reqPath, r.prefix)
148 - if reqPath == "" {
149 - reqPath = "/"
150 - }
151 - if rawPath == r.prefix {
152 - rawPath = "/"
153 - } else if strings.HasPrefix(rawPath, r.prefix+"/") {
154 - rawPath = strings.TrimPrefix(rawPath, r.prefix)
155 - }
156 - }
157 - }
158 -
159 - pr.Out.URL.Path = reqPath
160 - pr.Out.URL.RawPath = rawPath
146 + pr.Out.URL.Path, pr.Out.URL.RawPath = r.publicRequestPathToUpstream(pr.In.URL.Path, pr.In.URL.RawPath)
147 pr.Out.URL.RawQuery = pr.In.URL.RawQuery
148 pr.SetURL(r.upstream)
149 pr.SetXForwarded()
@@ -176,50 +162,54 @@ func (r *httpRoute) rewriteRequest(pr *httputil.ProxyRequest) {
162 }
163 }
164
179 -func (r *httpRoute) rewriteLocation(header http.Header, publicHost, publicScheme string) {
180 - location := header.Get("Location")
181 - if location == "" {
182 - return
183 - }
184 - parsed, err := url.Parse(location)
185 - if err != nil {
186 - return
165 +func (r *httpRoute) rewriteResponse(resp *http.Response) error {
166 + if resp == nil || resp.Request == nil {
167 + return nil
168 }
169
189 - switch {
190 - case parsed.IsAbs():
191 - if !strings.EqualFold(parsed.Scheme, r.upstream.Scheme) || !strings.EqualFold(parsed.Host, r.upstream.Host) {
192 - return
193 - }
194 - parsed.Scheme = publicScheme
195 - parsed.Host = publicHost
196 - case strings.HasPrefix(location, "/") && parsed.Host == "" && (len(location) == 1 || (location[1] != '\\' && location[1] != '/')):
197 - // server-relative redirect
198 - default:
199 - return
200 - }
170 + header := resp.Header
171 + publicHost := resp.Request.Header.Get("X-Forwarded-Host")
172 + publicScheme := resp.Request.Header.Get("X-Forwarded-Proto")
173 +
174 + location := header.Get("Location")
175 + if location != "" {
176 + parsed, err := url.Parse(location)
177 + if err == nil {
178 + switch {
179 + case parsed.IsAbs():
180 + if strings.EqualFold(parsed.Scheme, r.upstream.Scheme) && strings.EqualFold(parsed.Host, r.upstream.Host) {
181 + parsed.Scheme = publicScheme
182 + parsed.Host = publicHost
183 + } else {
184 + parsed = nil
185 + }
186 + case strings.HasPrefix(location, "/") && parsed.Host == "" && (len(location) == 1 || (location[1] != '\\' && location[1] != '/')):
187 + // server-relative redirect
188 + default:
189 + parsed = nil
190 + }
191
202 - mapped := r.mapUpstreamPathToPublic(parsed.Path)
203 - if !strings.HasPrefix(mapped, "/") || (len(mapped) > 1 && (mapped[1] == '/' || mapped[1] == '\\')) {
204 - return
192 + if parsed != nil {
193 + mapped := r.upstreamPathToPublic(parsed.Path)
194 + if strings.HasPrefix(mapped, "/") && (len(mapped) == 1 || (mapped[1] != '/' && mapped[1] != '\\')) {
195 + parsed.Path = mapped
196 + parsed.RawPath = ""
197 + header.Set("Location", parsed.String())
198 + }
199 + }
200 + }
201 }
206 - parsed.Path = mapped
207 - parsed.RawPath = ""
208 - header.Set("Location", parsed.String())
209 -}
202
211 -func (r *httpRoute) rewriteSetCookies(header http.Header, publicHost string) {
203 values := header.Values("Set-Cookie")
204 if len(values) == 0 {
214 - return
205 + return nil
206 }
207
208 publicDomain := publicHost
209 if host, port, err := net.SplitHostPort(publicDomain); err == nil && port != "" {
210 publicDomain = host
211 }
221 - publicDomain = strings.ToLower(strings.Trim(publicDomain, "[]"))
222 - upstreamDomain := strings.ToLower(r.upstream.Hostname())
212 + publicDomain = utils.NormalizeHostname(strings.Trim(publicDomain, "[]"))
213
214 header.Del("Set-Cookie")
215 for _, value := range values {
@@ -231,40 +221,65 @@ func (r *httpRoute) rewriteSetCookies(header http.Header, publicHost string) {
221
222 changed := false
223 if cookie.Path != "" {
234 - if rewritten := r.mapUpstreamPathToPublic(cookie.Path); rewritten != cookie.Path {
224 + if rewritten := r.upstreamPathToPublic(cookie.Path); rewritten != cookie.Path {
225 cookie.Path = rewritten
226 changed = true
227 }
228 }
229
240 - domain := strings.ToLower(strings.TrimPrefix(cookie.Domain, "."))
230 + domain := utils.NormalizeHostname(strings.TrimPrefix(cookie.Domain, "."))
231 if domain != "" && domain != publicDomain &&
242 - (domain == upstreamDomain || utils.IsLocalRelayHost(domain)) {
232 + (domain == r.upstreamDomain || utils.IsLocalRelayHost(domain)) {
233 cookie.Domain = ""
234 changed = true
235 }
236
237 if changed {
238 header.Add("Set-Cookie", cookie.String())
249 - } else {
250 - header.Add("Set-Cookie", value)
239 + continue
240 + }
241 + header.Add("Set-Cookie", value)
242 + }
243 +
244 + return nil
245 +}
246 +
247 +func (r *httpRoute) publicRequestPathToUpstream(path, rawPath string) (string, string) {
248 + path = utils.NormalizeURLPath(path)
249 + if r.prefix == "/" {
250 + return path, rawPath
251 + }
252 + if path == r.prefix {
253 + return "/", ""
254 + }
255 + path = strings.TrimPrefix(path, r.prefix)
256 + if path == "" {
257 + path = "/"
258 + }
259 +
260 + if rawPath != "" {
261 + switch {
262 + case rawPath == r.prefix:
263 + rawPath = "/"
264 + case strings.HasPrefix(rawPath, r.prefixSlash):
265 + rawPath = strings.TrimPrefix(rawPath, r.prefix)
266 }
267 }
268 + return path, rawPath
269 }
270
255 -func (r *httpRoute) mapUpstreamPathToPublic(raw string) string {
271 +func (r *httpRoute) upstreamPathToPublic(raw string) string {
272 raw = utils.NormalizeURLPath(raw)
257 - if r.prefix != "/" && (raw == r.prefix || strings.HasPrefix(raw, r.prefix+"/")) {
273 + if r.prefix != "/" && (raw == r.prefix || strings.HasPrefix(raw, r.prefixSlash)) {
274 return raw
275 }
276
261 - base := utils.NormalizeURLPath(r.upstream.Path)
277 rest := raw
263 - if base != "/" {
264 - if raw == base {
278 + if r.upstreamPath != "/" {
279 + if raw == r.upstreamPath {
280 rest = "/"
266 - } else if strings.HasPrefix(raw, base+"/") {
267 - rest = strings.TrimPrefix(raw, base)
281 + } else if strings.HasPrefix(raw, r.upstreamPathSlash) {
282 + rest = strings.TrimPrefix(raw, r.upstreamPath)
283 }
284 }
285
cmd/relay-server/frontend.go
+1 -1
@@ -76,7 +76,7 @@ func (f *Frontend) Handler() *http.ServeMux {
76 f.ServeAppStatic(w, r, "")
77 })
78 mux.HandleFunc(types.PathAppPrefix, func(w http.ResponseWriter, r *http.Request) {
79 - f.ServeAppStatic(w, r, strings.TrimPrefix(strings.TrimSpace(r.URL.Path), types.PathAppPrefix))
79 + f.ServeAppStatic(w, r, strings.TrimPrefix(r.URL.Path, types.PathAppPrefix))
80 })
81 mux.HandleFunc(types.PathAssetsPrefix, func(w http.ResponseWriter, r *http.Request) {
82 f.ServeAsset(w, r, strings.TrimPrefix(r.URL.Path, "/"), "")
portal/acme/route53/provider.go
+4 -6
@@ -74,7 +74,7 @@ func (p *Provider) EnsureARecords(ctx context.Context, baseDomain, publicIPv4 st
74 if p == nil {
75 return errors.New("route53 provider is nil")
76 }
77 - baseDomain = utils.NormalizeHostname(baseDomain)
77 + baseDomain = strings.TrimPrefix(utils.NormalizeHostname(baseDomain), "*.")
78 if baseDomain == "" {
79 return errors.New("base domain is required")
80 }
@@ -211,17 +211,15 @@ func validateIPv4(raw string) error {
211 }
212
213 func domainCandidates(domain string) []string {
214 - parts := strings.Split(strings.TrimSpace(strings.TrimSuffix(domain, ".")), ".")
214 + normalized := utils.NormalizeHostname(domain)
215 + parts := strings.Split(normalized, ".")
216 if len(parts) < 2 {
217 return nil
218 }
219
220 candidates := make([]string, 0, len(parts)-1)
221 for i := range len(parts) - 1 {
221 - candidate := utils.NormalizeHostname(strings.Join(parts[i:], "."))
222 - if candidate != "" {
223 - candidates = append(candidates, candidate)
224 - }
222 + candidates = append(candidates, strings.Join(parts[i:], "."))
223 }
224 return candidates
225 }
portal/api_server.go
+6 -6
@@ -143,9 +143,9 @@ func (s *Server) handleRelayDiscovery(w http.ResponseWriter, r *http.Request) {
143 ingressAddr = fmt.Sprintf("%s:%d", ingressAddr, s.cfg.SNIPort)
144 }
145
146 - supportsOverlayPeer := strings.TrimSpace(s.wgConfig.PublicKey) != "" &&
147 - strings.TrimSpace(s.wgConfig.Endpoint) != "" &&
148 - strings.TrimSpace(s.wgConfig.OverlayIPv4) != ""
146 + supportsOverlayPeer := s.wgConfig.PublicKey != "" &&
147 + s.wgConfig.Endpoint != "" &&
148 + s.wgConfig.OverlayIPv4 != ""
149
150 self, err := discovery.NormalizeDescriptor(types.RelayDescriptor{
151 RelayID: s.cfg.PortalURL,
@@ -158,9 +158,9 @@ func (s *Server) handleRelayDiscovery(w http.ResponseWriter, r *http.Request) {
158 SupportsTCP: true,
159 SupportsUDP: s.cfg.UDPPortCount > 0,
160 SupportsOverlayPeer: supportsOverlayPeer,
161 - WireGuardPublicKey: strings.TrimSpace(s.wgConfig.PublicKey),
162 - WireGuardEndpoint: strings.TrimSpace(s.wgConfig.Endpoint),
163 - OverlayIPv4: strings.TrimSpace(s.wgConfig.OverlayIPv4),
161 + WireGuardPublicKey: s.wgConfig.PublicKey,
162 + WireGuardEndpoint: s.wgConfig.Endpoint,
163 + OverlayIPv4: s.wgConfig.OverlayIPv4,
164 OverlayCIDRs: append([]string(nil), s.wgConfig.OverlayCIDRs...),
165 })
166 if err != nil {
portal/auth/auth.go
+5 -8
@@ -43,7 +43,7 @@ type es256kOpaqueSigner struct {
43 }
44
45 func (s *es256kOpaqueSigner) Public() *jose.JSONWebKey {
46 - return &jose.JSONWebKey{KeyID: strings.TrimSpace(s.keyID)}
46 + return &jose.JSONWebKey{KeyID: s.keyID}
47 }
48
49 func (s *es256kOpaqueSigner) Algs() []jose.SignatureAlgorithm {
@@ -244,9 +244,7 @@ func VerifyLeaseAccessToken(token, publicKeyHex, issuer, leaseID string, now tim
244 if pubKeyText == "" {
245 return LeaseAccessTokenClaims{}, errors.New("public key is required")
246 }
247 - if strings.HasPrefix(strings.ToLower(pubKeyText), "0x") {
248 - pubKeyText = pubKeyText[2:]
249 - }
247 + pubKeyText = utils.TrimHexPrefix(pubKeyText)
248
249 pubKeyBytes, err := hex.DecodeString(pubKeyText)
250 if err != nil {
@@ -266,7 +264,8 @@ func VerifyLeaseAccessToken(token, publicKeyHex, issuer, leaseID string, now tim
264 if err := parsed.Claims(&es256kOpaqueVerifier{publicKey: publicKey}, &claims); err != nil {
265 return LeaseAccessTokenClaims{}, err
266 }
269 - if strings.TrimSpace(leaseID) != "" && claims.LeaseID != strings.TrimSpace(leaseID) {
267 + requestedLeaseID := strings.TrimSpace(leaseID)
268 + if requestedLeaseID != "" && claims.LeaseID != requestedLeaseID {
269 return LeaseAccessTokenClaims{}, errors.New("lease access token lease id does not match request")
270 }
271 if err := claims.ValidateWithLeeway(jwt.Expected{
@@ -281,9 +280,7 @@ func VerifyLeaseAccessToken(token, publicKeyHex, issuer, leaseID string, now tim
280
281 func decodePrivateKeyHex(privateKeyHex string) ([]byte, error) {
282 trimmed := strings.TrimSpace(privateKeyHex)
284 - if strings.HasPrefix(strings.ToLower(trimmed), "0x") {
285 - trimmed = trimmed[2:]
286 - }
283 + trimmed = utils.TrimHexPrefix(trimmed)
284 decoded, err := hex.DecodeString(trimmed)
285 if err != nil {
286 return nil, err
portal/discovery/discovery.go
+7 -6
@@ -98,8 +98,9 @@ func ValidateDescriptor(desc types.RelayDescriptor, now time.Time) (types.RelayD
98 }
99
100 func ValidateRelayDiscoveryResponse(resp types.DiscoveryResponse, now time.Time) (types.RelayDescriptor, []types.RelayDescriptor, error) {
101 - if strings.TrimSpace(resp.ProtocolVersion) != types.ProtocolVersion {
102 - return types.RelayDescriptor{}, nil, fmt.Errorf("relay protocol version mismatch: relay=%q client=%q", strings.TrimSpace(resp.ProtocolVersion), types.ProtocolVersion)
101 + protocolVersion := strings.TrimSpace(resp.ProtocolVersion)
102 + if protocolVersion != types.ProtocolVersion {
103 + return types.RelayDescriptor{}, nil, fmt.Errorf("relay protocol version mismatch: relay=%q client=%q", protocolVersion, types.ProtocolVersion)
104 }
105
106 self, err := ValidateDescriptor(resp.Self, now)
@@ -134,7 +135,7 @@ func ValidateDescriptorTarget(desc types.RelayDescriptor, targetRelayID, targetU
135 return err
136 }
137
137 - relayID := strings.TrimSpace(normalized.RelayID)
138 + relayID := normalized.RelayID
139 if targetRelayID != "" && relayID != targetRelayID {
140 return errors.New("descriptor relay_id does not match target relay")
141 }
@@ -205,13 +206,13 @@ func RequireOverlayRelayDescriptor(desc types.RelayDescriptor) error {
206 if !desc.SupportsOverlayPeer {
207 return errors.New("descriptor does not support overlay peer")
208 }
208 - if strings.TrimSpace(desc.WireGuardPublicKey) == "" {
209 + if desc.WireGuardPublicKey == "" {
210 return errors.New("descriptor wireguard public key is required")
211 }
211 - if strings.TrimSpace(desc.WireGuardEndpoint) == "" {
212 + if desc.WireGuardEndpoint == "" {
213 return errors.New("descriptor wireguard endpoint is required")
214 }
214 - if strings.TrimSpace(desc.OverlayIPv4) == "" {
215 + if desc.OverlayIPv4 == "" {
216 return errors.New("descriptor overlay ipv4 is required")
217 }
218 return nil
portal/discovery/relayset.go
+9 -8
@@ -88,7 +88,7 @@ func (s *RelaySet) trackedRelayURLs() []string {
88 urls = append(urls, relayURL)
89 }
90 for _, view := range s.relays {
91 - relayURL := strings.TrimSpace(view.Descriptor.APIHTTPSAddr)
91 + relayURL := view.Descriptor.APIHTTPSAddr
92 if relayURL == "" {
93 continue
94 }
@@ -237,7 +237,7 @@ func (s *RelaySet) BootstrapDescriptors() []types.RelayDescriptor {
237 continue
238 }
239 if relayID, ok := s.relayIDsByURL[relayURL]; ok {
240 - if view, ok := s.relays[relayID]; ok && strings.TrimSpace(view.Descriptor.APIHTTPSAddr) != "" {
240 + if view, ok := s.relays[relayID]; ok && view.Descriptor.APIHTTPSAddr != "" {
241 out = append(out, view.Descriptor)
242 continue
243 }
@@ -266,9 +266,10 @@ func (s *RelaySet) BanRelayURL(relayURL, reason string) bool {
266 }
267
268 state := s.localByURL[relayURL]
269 - changed := !state.Banned || strings.TrimSpace(state.BanReason) != strings.TrimSpace(reason)
269 + reason = strings.TrimSpace(reason)
270 + changed := !state.Banned || strings.TrimSpace(state.BanReason) != reason
271 state.Banned = true
271 - state.BanReason = strings.TrimSpace(reason)
272 + state.BanReason = reason
273 state.Reachable = false
274 s.localByURL[relayURL] = state
275 if changed {
@@ -387,7 +388,7 @@ func (s *RelaySet) AdvertisedDescriptors() []types.RelayDescriptor {
388 out := make([]types.RelayDescriptor, 0, len(s.relays))
389 for _, view := range s.relays {
390 state := s.localByURL[view.Descriptor.APIHTTPSAddr]
390 - if !state.Advertised || relayExpiredAt(view, state, now) || strings.TrimSpace(view.Descriptor.APIHTTPSAddr) == "" {
391 + if !state.Advertised || relayExpiredAt(view, state, now) || view.Descriptor.APIHTTPSAddr == "" {
392 continue
393 }
394 out = append(out, view.Descriptor)
@@ -492,7 +493,7 @@ func (s *RelaySet) registerDescriptor(desc types.RelayDescriptor, now time.Time)
493 return "", false, false, err
494 }
495 if current, ok := s.relays[normalized.RelayID]; ok {
495 - currentURL := strings.TrimSpace(current.Descriptor.APIHTTPSAddr)
496 + currentURL := current.Descriptor.APIHTTPSAddr
497 if currentURL != "" && currentURL != normalized.APIHTTPSAddr {
498 return "", false, false, errors.New("descriptor api_https_addr does not match known relay url")
499 }
@@ -523,11 +524,11 @@ func (s *RelaySet) registerDescriptor(desc types.RelayDescriptor, now time.Time)
524
525 func relayDiscoveryURLs(selfDescriptor types.RelayDescriptor, relayDescriptors []types.RelayDescriptor) []string {
526 relayURLs := make([]string, 0, 1+len(relayDescriptors))
526 - if apiURL := strings.TrimSpace(selfDescriptor.APIHTTPSAddr); apiURL != "" {
527 + if apiURL := selfDescriptor.APIHTTPSAddr; apiURL != "" {
528 relayURLs = append(relayURLs, apiURL)
529 }
530 for _, relayDescriptor := range relayDescriptors {
530 - if apiURL := strings.TrimSpace(relayDescriptor.APIHTTPSAddr); apiURL != "" {
531 + if apiURL := relayDescriptor.APIHTTPSAddr; apiURL != "" {
532 relayURLs = append(relayURLs, apiURL)
533 }
534 }
portal/keyless/http.go
+1 -2
@@ -6,7 +6,6 @@ import (
6 "errors"
7 "net/http"
8 "net/url"
9 - "strings"
9 "time"
10 )
11
@@ -15,7 +14,7 @@ func NewRelayHTTPClient(ctx context.Context, relayURL *url.URL, rootCAPEM []byte
14 return nil, nil, errors.New("relay url is required")
15 }
16
18 - serverName := strings.TrimSpace(relayURL.Hostname())
17 + serverName := relayURL.Hostname()
18 if serverName == "" {
19 return nil, nil, errors.New("relay hostname is required")
20 }
portal/server.go
+1 -1
@@ -258,7 +258,7 @@ func (s *Server) Start(ctx context.Context, apiMux *http.ServeMux) error {
258 Str("root_host", s.rootHost).
259 Str("acme_dns_provider", s.cfg.ACME.DNSProvider).
260 Bool("discovery_enabled", s.cfg.DiscoveryEnabled).
261 - Bool("wireguard_enabled", strings.TrimSpace(s.wgConfig.PrivateKey) != "").
261 + Bool("wireguard_enabled", s.wgConfig.PrivateKey != "").
262 Bool("udp_enabled", s.cfg.UDPPortCount > 0).
263 Bool("acme_enabled", !strings.HasSuffix(s.rootHost, "localhost") && s.rootHost != "127.0.0.1" && s.rootHost != "::1")
264 if s.quicTunnel != nil {
portal/wireguard/overlay.go
+1 -1
@@ -171,7 +171,7 @@ func peersForSnapshot(selfRelayID string, snapshot map[string]types.RelayState)
171 if desc.RelayID == selfRelayID || !desc.SupportsOverlayPeer {
172 continue
173 }
174 - if strings.TrimSpace(desc.WireGuardPublicKey) == "" || strings.TrimSpace(desc.WireGuardEndpoint) == "" || strings.TrimSpace(desc.OverlayIPv4) == "" {
174 + if desc.WireGuardPublicKey == "" || desc.WireGuardEndpoint == "" || desc.OverlayIPv4 == "" {
175 continue
176 }
177
portal/wireguard/stack.go
+2 -2
@@ -48,7 +48,7 @@ func newStack(cfg Config) (*stack, error) {
48 return nil, err
49 }
50
51 - overlayIP, err := netip.ParseAddr(strings.TrimSpace(cfg.OverlayIPv4))
51 + overlayIP, err := netip.ParseAddr(cfg.OverlayIPv4)
52 if err != nil || !overlayIP.Is4() {
53 return nil, errors.New("overlay ipv4 must be a valid IPv4 address")
54 }
@@ -138,7 +138,7 @@ func (s *stack) ApplyPeers(peers []types.DesiredPeer) error {
138 }
139
140 resolvedEndpoint := ""
141 - if endpoint := strings.TrimSpace(peer.WireGuardEndpoint); endpoint != "" {
141 + if endpoint := peer.WireGuardEndpoint; endpoint != "" {
142 resolvedEndpoint, err = resolvePeerEndpoint(endpoint)
143 if err != nil {
144 s.mu.Lock()
sdk/api_client.go
+3 -2
@@ -185,8 +185,9 @@ func (a *apiClient) ensureCompatible(ctx context.Context, httpClient *http.Clien
185 }
186 return fmt.Errorf("%w: %w", errRelayIncompatible, err)
187 }
188 - if strings.TrimSpace(resp.ProtocolVersion) != types.ProtocolVersion {
189 - return fmt.Errorf("%w: relay protocol version mismatch: relay=%q client=%q", errRelayIncompatible, strings.TrimSpace(resp.ProtocolVersion), types.ProtocolVersion)
188 + protocolVersion := strings.TrimSpace(resp.ProtocolVersion)
189 + if protocolVersion != types.ProtocolVersion {
190 + return fmt.Errorf("%w: relay protocol version mismatch: relay=%q client=%q", errRelayIncompatible, protocolVersion, types.ProtocolVersion)
191 }
192 return nil
193 }
sdk/listener.go
+2 -2
@@ -346,12 +346,12 @@ func (l *Listener) PublicURL() string {
346 return ""
347 }
348
349 - if strings.TrimSpace(l.api.baseURL.Scheme) == "" {
349 + if l.api.baseURL.Scheme == "" {
350 return "https://" + hostname
351 }
352
353 host := hostname
354 - if port := strings.TrimSpace(l.api.baseURL.Port()); port != "" {
354 + if port := l.api.baseURL.Port(); port != "" {
355 host = net.JoinHostPort(hostname, port)
356 }
357
types/api.go
+6 -4
@@ -27,11 +27,13 @@ func (e *APIRequestError) Error() string {
27 if e == nil {
28 return ""
29 }
30 - if strings.TrimSpace(e.Code) != "" {
31 - return e.Code + ": " + strings.TrimSpace(e.Message)
30 + code := strings.TrimSpace(e.Code)
31 + message := strings.TrimSpace(e.Message)
32 + if code != "" {
33 + return code + ": " + message
34 }
33 - if strings.TrimSpace(e.Message) != "" {
34 - return strings.TrimSpace(e.Message)
35 + if message != "" {
36 + return message
37 }
38 if e.StatusCode > 0 {
39 return fmt.Sprintf("api request failed with status %d", e.StatusCode)
utils/crypto.go
+6 -15
@@ -30,11 +30,10 @@ func NormalizeEVMAddress(raw string) (string, error) {
30 if trimmed == "" {
31 return "", errors.New("address is required")
32 }
33 - if !strings.HasPrefix(strings.ToLower(trimmed), "0x") {
33 + hexPart := TrimHexPrefix(trimmed)
34 + if hexPart == trimmed {
35 return "", errors.New("address must start with 0x")
36 }
36 -
37 - hexPart := trimmed[2:]
37 if len(hexPart) != 40 {
38 return "", errors.New("address must be 20 bytes")
39 }
@@ -80,9 +79,7 @@ func AddressFromCompressedPublicKeyHex(rawPublicKey string) (string, error) {
79 if publicKeyHex == "" {
80 return "", errors.New("public key is required")
81 }
83 - if strings.HasPrefix(strings.ToLower(publicKeyHex), "0x") {
84 - publicKeyHex = publicKeyHex[2:]
85 - }
82 + publicKeyHex = TrimHexPrefix(publicKeyHex)
83
84 decoded, err := hex.DecodeString(publicKeyHex)
85 if err != nil {
@@ -182,9 +179,7 @@ func VerifySHA256Secp256k1DER(payload []byte, publicKeyHex, signatureHex string)
179 if pubKeyText == "" {
180 return errors.New("public key is required")
181 }
185 - if strings.HasPrefix(strings.ToLower(pubKeyText), "0x") {
186 - pubKeyText = pubKeyText[2:]
187 - }
182 + pubKeyText = TrimHexPrefix(pubKeyText)
183
184 pubKeyBytes, err := hex.DecodeString(pubKeyText)
185 if err != nil {
@@ -199,9 +194,7 @@ func VerifySHA256Secp256k1DER(payload []byte, publicKeyHex, signatureHex string)
194 if sigText == "" {
195 return errors.New("signature is required")
196 }
202 - if strings.HasPrefix(strings.ToLower(sigText), "0x") {
203 - sigText = sigText[2:]
204 - }
197 + sigText = TrimHexPrefix(sigText)
198
199 sigBytes, err := hex.DecodeString(sigText)
200 if err != nil {
@@ -224,9 +217,7 @@ func decodeSecp256k1PrivateKeyHex(raw string, requireNonZero bool) ([]byte, stri
217 if privateKeyHex == "" {
218 return nil, "", errors.New("private key is required")
219 }
227 - if strings.HasPrefix(strings.ToLower(privateKeyHex), "0x") {
228 - privateKeyHex = privateKeyHex[2:]
229 - }
220 + privateKeyHex = TrimHexPrefix(privateKeyHex)
221
222 decoded, err := hex.DecodeString(privateKeyHex)
223 if err != nil {
utils/utils.go
+7
@@ -38,6 +38,13 @@ func SplitCSV(raw string) []string {
38 return out
39 }
40
41 +func TrimHexPrefix(raw string) string {
42 + if len(raw) >= 2 && raw[0] == '0' && (raw[1] == 'x' || raw[1] == 'X') {
43 + return raw[2:]
44 + }
45 + return raw
46 +}
47 +
48 func ParseCIDRs(raw string) ([]*net.IPNet, error) {
49 parts := SplitCSV(raw)
50 if len(parts) == 0 {