package wsutil import ( "net/url" "sort" "strings" ) // AppendQuery returns rawURL with the given query parameters added, preserving // any existing query string. Parameters with an empty value are omitted. It is // used by stateful gateway clients to splice resume parameters (resume/sn/ // session_id) and access tokens onto a gateway URL before reconnecting. Keys are // appended in sorted order so the result is deterministic for tests. func AppendQuery(rawURL string, params map[string]string) string { if len(params) == 0 { return rawURL } u, err := url.Parse(rawURL) if err != nil { return appendNaive(rawURL, params) } keys := make([]string, 0, len(params)) for k := range params { keys = append(keys, k) } sort.Strings(keys) pairs := existingPairs(u.Query()) for _, k := range keys { if params[k] == "" { continue } pairs = append(pairs, url.QueryEscape(k)+"="+url.QueryEscape(params[k])) } u.RawQuery = strings.Join(pairs, "&") return u.String() } // existingPairs flattens url.Values into ordered "k=v" pairs so that appending // new parameters produces a deterministic query string. Multi-valued keys keep // their original value order. func existingPairs(v url.Values) []string { keys := make([]string, 0, len(v)) for k := range v { keys = append(keys, k) } sort.Strings(keys) pairs := make([]string, 0, len(v)) for _, k := range keys { for _, val := range v[k] { pairs = append(pairs, url.QueryEscape(k)+"="+url.QueryEscape(val)) } } return pairs } // appendNaive appends query parameters to a URL that could not be parsed, using // '?' or '&' based on what is already present. func appendNaive(rawURL string, params map[string]string) string { sep := "?" if strings.Contains(rawURL, "?") { sep = "&" } var b strings.Builder b.WriteString(rawURL) b.WriteString(sep) keys := make([]string, 0, len(params)) for k := range params { keys = append(keys, k) } sort.Strings(keys) first := true for _, k := range keys { if params[k] == "" { continue } if !first { b.WriteString("&") } first = false b.WriteString(url.QueryEscape(k) + "=" + url.QueryEscape(params[k])) } return b.String() }