* feat(codex): support custom base URL * fix(codex): require HTTPS for custom base URL * docs: document changed subscription functions * fix(groups): 展示并区分详情中的默认与自定义地址 * fix(groups): 统一上游地址配置交互与图标位置 * fix(subscription): 绑定额度重置操作的上游地址 * fix(gateway): 隔离旧上游请求的健康状态更新 --------- Co-authored-by: tbphp <tangb7420@gmail.com>
113 lines
3.6 KiB
Go
113 lines
3.6 KiB
Go
package spec
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
// NormalizeNonEmpty trims and validates a required text or secret field.
|
|
func NormalizeNonEmpty(value string) (string, error) {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
return "", fmt.Errorf("must not be empty")
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
// NormalizeBaseURL canonicalizes a configured HTTP(S) endpoint.
|
|
func NormalizeBaseURL(value string) (string, error) {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
return "", fmt.Errorf("must not be empty")
|
|
}
|
|
parsed, err := url.Parse(value)
|
|
if err != nil || parsed == nil || parsed.Opaque != "" || parsed.User != nil || parsed.Host == "" || parsed.Fragment != "" {
|
|
return "", fmt.Errorf("must be an absolute HTTP(S) URL without credentials or fragment")
|
|
}
|
|
parsed.Scheme = strings.ToLower(parsed.Scheme)
|
|
if parsed.Scheme != "http" && parsed.Scheme != "https" {
|
|
return "", fmt.Errorf("must be an absolute HTTP(S) URL without credentials or fragment")
|
|
}
|
|
if parsed.RawQuery != "" || parsed.ForceQuery {
|
|
return "", fmt.Errorf("must not contain query parameters")
|
|
}
|
|
parsed.Host = strings.ToLower(parsed.Host)
|
|
if (parsed.Scheme == "https" && parsed.Port() == "443") ||
|
|
(parsed.Scheme == "http" && parsed.Port() == "80") {
|
|
hostname := parsed.Hostname()
|
|
if strings.Contains(hostname, ":") {
|
|
hostname = "[" + hostname + "]"
|
|
}
|
|
parsed.Host = hostname
|
|
}
|
|
parsed.Path = strings.TrimRight(parsed.Path, "/")
|
|
parsed.RawPath = strings.TrimRight(parsed.RawPath, "/")
|
|
parsed.ForceQuery = false
|
|
return parsed.String(), nil
|
|
}
|
|
|
|
// NormalizeHTTPSBaseURL canonicalizes an endpoint that will receive sensitive
|
|
// credentials and rejects cleartext HTTP targets.
|
|
func NormalizeHTTPSBaseURL(value string) (string, error) {
|
|
normalized, err := NormalizeBaseURL(value)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
parsed, err := url.Parse(normalized)
|
|
if err != nil || parsed == nil || parsed.Scheme != "https" {
|
|
return "", fmt.Errorf("must use HTTPS")
|
|
}
|
|
return normalized, nil
|
|
}
|
|
|
|
// NormalizeOptionalHTTPSBaseURL 允许清空可选的订阅代理地址。
|
|
func NormalizeOptionalHTTPSBaseURL(value string) (string, error) {
|
|
if strings.TrimSpace(value) == "" {
|
|
return "", nil
|
|
}
|
|
return NormalizeHTTPSBaseURL(value)
|
|
}
|
|
|
|
// NormalizeCloudIdentifier rejects whitespace and control characters in a
|
|
// provider-owned cloud configuration value.
|
|
func NormalizeCloudIdentifier(value string) (string, error) {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" || len(value) > 255 {
|
|
return "", fmt.Errorf("must be between 1 and 255 bytes")
|
|
}
|
|
for _, character := range value {
|
|
if character <= ' ' || character == 0x7f {
|
|
return "", fmt.Errorf("must not contain whitespace or control characters")
|
|
}
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
// NormalizeServiceAccountJSON validates and canonicalizes a Google service
|
|
// account credential without retaining fields outside the credential object.
|
|
func NormalizeServiceAccountJSON(value string) (string, error) {
|
|
var object map[string]json.RawMessage
|
|
decoder := json.NewDecoder(strings.NewReader(strings.TrimSpace(value)))
|
|
decoder.DisallowUnknownFields()
|
|
if err := decoder.Decode(&object); err != nil || object == nil {
|
|
return "", fmt.Errorf("must be a valid JSON object")
|
|
}
|
|
for _, key := range []string{"type", "project_id", "client_email", "private_key"} {
|
|
raw, exists := object[key]
|
|
if !exists {
|
|
return "", fmt.Errorf("must contain %s", key)
|
|
}
|
|
var field string
|
|
if json.Unmarshal(raw, &field) != nil || strings.TrimSpace(field) == "" {
|
|
return "", fmt.Errorf("must contain non-empty %s", key)
|
|
}
|
|
}
|
|
encoded, err := json.Marshal(object)
|
|
if err != nil {
|
|
return "", fmt.Errorf("must be a valid JSON object")
|
|
}
|
|
return string(encoded), nil
|
|
}
|