host_route_parse.go
1 // Copyright 2026 Alibaba Group Holding Ltd. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package proxy 16 17 import ( 18 "errors" 19 "fmt" 20 "strconv" 21 "strings" 22 23 "github.com/alibaba/opensandbox/ingress/pkg/signature" 24 ) 25 26 type parsedRoute struct { 27 sandboxID string 28 port int 29 expiresB36 string 30 signature string 31 requestURI string 32 uriParsedAsOSEP bool 33 } 34 35 func parseHostRoute(s string) (parsedRoute, error) { 36 domain := strings.Split(strings.TrimPrefix(strings.TrimPrefix(s, "https://"), "http://"), ".") 37 if len(domain) < 1 { 38 return parsedRoute{}, fmt.Errorf("invalid host: %s", s) 39 } 40 label := domain[0] 41 42 sandboxID, port, expires, routeSig, parseErr := signature.ParseRouteToken(label) 43 if parseErr == nil { 44 return parsedRoute{ 45 sandboxID: sandboxID, 46 port: port, 47 expiresB36: expires, 48 signature: routeSig, 49 requestURI: "", 50 }, nil 51 } 52 53 ingressAndPort := strings.Split(label, "-") 54 if len(ingressAndPort) <= 1 || ingressAndPort[0] == "" { 55 return parsedRoute{}, fmt.Errorf("invalid host: %s", s) 56 } 57 ingress := strings.Join(ingressAndPort[:len(ingressAndPort)-1], "-") 58 port, err := strconv.Atoi(ingressAndPort[len(ingressAndPort)-1]) 59 if err != nil { 60 return parsedRoute{}, fmt.Errorf("invalid port format: %w", err) 61 } 62 return parsedRoute{sandboxID: ingress, port: port, expiresB36: "", signature: "", requestURI: ""}, nil 63 } 64 65 func parseURIRoute(path string) (parsedRoute, error) { 66 if path == "" { 67 return parsedRoute{}, errors.New("missing URI path") 68 } 69 70 trimmed := strings.TrimPrefix(path, "/") 71 parts := strings.SplitN(trimmed, "/", 5) 72 if len(parts) >= 4 && parts[0] != "" { 73 port, perr := signature.ParsePortSegment(parts[1]) 74 if perr == nil { 75 if _, eerr := signature.ParseExpiresB36(parts[2]); eerr == nil { 76 if signature.ValidateSignatureFormat(parts[3]) == nil { 77 requestURI := "/" 78 if len(parts) == 5 && parts[4] != "" { 79 requestURI = "/" + parts[4] 80 } 81 return parsedRoute{ 82 sandboxID: parts[0], 83 port: port, 84 expiresB36: parts[2], 85 signature: parts[3], 86 requestURI: requestURI, 87 uriParsedAsOSEP: true, 88 }, nil 89 } 90 } 91 } 92 } 93 return parseURILegacy(path) 94 } 95 96 func parseURILegacy(path string) (parsedRoute, error) { 97 trimmed := strings.TrimPrefix(path, "/") 98 parts := strings.SplitN(trimmed, "/", 3) 99 if len(parts) < 2 { 100 return parsedRoute{}, fmt.Errorf("invalid URI path format: expected '/<sandbox-id>/<sandbox-port>/<path-to-request>', got: %s", path) 101 } 102 sandboxID := parts[0] 103 if sandboxID == "" { 104 return parsedRoute{}, errors.New("missing sandbox-id or sandbox-port in URI path") 105 } 106 port, err := signature.ParsePortSegment(parts[1]) 107 if err != nil { 108 return parsedRoute{}, fmt.Errorf("invalid port format: %w", err) 109 } 110 var requestURI string 111 if len(parts) >= 3 && parts[2] != "" { 112 requestURI = "/" + parts[2] 113 } else { 114 requestURI = "/" 115 } 116 return parsedRoute{ 117 sandboxID: sandboxID, 118 port: port, 119 expiresB36: "", 120 signature: "", 121 requestURI: requestURI, 122 uriParsedAsOSEP: false, 123 }, nil 124 }