| //go:build !go1.19 |
| // +build !go1.19 |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package triple |
| |
| import ( |
| "net/url" |
| "path" |
| "strings" |
| ) |
| |
| func joinProcedure(interfaceName, methodName string) string { |
| procedure, _ := joinPath("", interfaceName, methodName) |
| return "/" + procedure |
| } |
| |
| func joinPath(base string, elem ...string) (result string, err error) { |
| u, err := url.Parse(base) |
| if err != nil { |
| return |
| } |
| |
| result = joinPathFunc(u, elem...).String() |
| return |
| } |
| |
| // ---------- For golang version lower than go 1.19 ---------- |
| // The code below is copied from go 1.19. Add Func suffix to tell the difference easily. |
| |
| const upperhex = "0123456789ABCDEF" |
| |
| type encoding int |
| |
| const ( |
| encodePath encoding = 1 + iota |
| encodePathSegment |
| encodeHost |
| encodeZone |
| encodeUserPassword |
| encodeQueryComponent |
| encodeFragment |
| ) |
| |
| func isHexFunc(c byte) bool { |
| switch { |
| case '0' <= c && c <= '9': |
| return true |
| case 'a' <= c && c <= 'f': |
| return true |
| case 'A' <= c && c <= 'F': |
| return true |
| } |
| return false |
| } |
| |
| func unHexFunc(c byte) byte { |
| switch { |
| case '0' <= c && c <= '9': |
| return c - '0' |
| case 'a' <= c && c <= 'f': |
| return c - 'a' + 10 |
| case 'A' <= c && c <= 'F': |
| return c - 'A' + 10 |
| } |
| return 0 |
| } |
| |
| func shouldEscapeFunc(c byte, mode encoding) bool { |
| // §2.3 Unreserved characters (alphanum) |
| if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' { |
| return false |
| } |
| |
| if mode == encodeHost || mode == encodeZone { |
| // §3.2.2 Host allows |
| // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" |
| // as part of reg-name. |
| // We add : because we include :port as part of host. |
| // We add [ ] because we include [ipv6]:port as part of host. |
| // We add < > because they're the only characters left that |
| // we could possibly allow, and Parse will reject them if we |
| // escape them (because hosts can't use %-encoding for |
| // ASCII bytes). |
| switch c { |
| case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '[', ']', '<', '>', '"': |
| return false |
| } |
| } |
| |
| switch c { |
| case '-', '_', '.', '~': // §2.3 Unreserved characters (mark) |
| return false |
| |
| case '$', '&', '+', ',', '/', ':', ';', '=', '?', '@': // §2.2 Reserved characters (reserved) |
| // Different sections of the URL allow a few of |
| // the reserved characters to appear unescaped. |
| switch mode { |
| case encodePath: // §3.3 |
| // The RFC allows : @ & = + $ but saves / ; , for assigning |
| // meaning to individual path segments. This package |
| // only manipulates the path as a whole, so we allow those |
| // last three as well. That leaves only ? to escape. |
| return c == '?' |
| |
| case encodePathSegment: // §3.3 |
| // The RFC allows : @ & = + $ but saves / ; , for assigning |
| // meaning to individual path segments. |
| return c == '/' || c == ';' || c == ',' || c == '?' |
| |
| case encodeUserPassword: // §3.2.1 |
| // The RFC allows ';', ':', '&', '=', '+', '$', and ',' in |
| // userinfo, so we must escape only '@', '/', and '?'. |
| // The parsing of userinfo treats ':' as special so we must escape |
| // that too. |
| return c == '@' || c == '/' || c == '?' || c == ':' |
| |
| case encodeQueryComponent: // §3.4 |
| // The RFC reserves (so we must escape) everything. |
| return true |
| |
| case encodeFragment: // §4.1 |
| // The RFC text is silent but the grammar allows |
| // everything, so escape nothing. |
| return false |
| } |
| } |
| |
| if mode == encodeFragment { |
| // RFC 3986 §2.2 allows not escaping sub-delims. A subset of sub-delims are |
| // included in reserved from RFC 2396 §2.2. The remaining sub-delims do not |
| // need to be escaped. To minimize potential breakage, we apply two restrictions: |
| // (1) we always escape sub-delims outside of the fragment, and (2) we always |
| // escape single quote to avoid breaking callers that had previously assumed that |
| // single quotes would be escaped. See issue #19917. |
| switch c { |
| case '!', '(', ')', '*': |
| return false |
| } |
| } |
| |
| // Everything else must be escaped. |
| return true |
| } |
| |
| func joinPathFunc(u *url.URL, elem ...string) *url.URL { |
| elem = append([]string{u.EscapedPath()}, elem...) |
| var p string |
| if !strings.HasPrefix(elem[0], "/") { |
| // Return a relative path if u is relative, |
| // but ensure that it contains no ../ elements. |
| elem[0] = "/" + elem[0] |
| p = path.Join(elem...)[1:] |
| } else { |
| p = path.Join(elem...) |
| } |
| // path.Join will remove any trailing slashes. |
| // Preserve at least one. |
| if strings.HasSuffix(elem[len(elem)-1], "/") && !strings.HasSuffix(p, "/") { |
| p += "/" |
| } |
| newURL := *u |
| setPathFunc(&newURL, p) |
| return &newURL |
| } |
| |
| func setPathFunc(u *url.URL, p string) error { |
| pathUnescape, err := unescapeFunc(p, encodePath) |
| if err != nil { |
| return err |
| } |
| u.Path = pathUnescape |
| if pathEscape := escapeFunc(pathUnescape, encodePath); p == pathEscape { |
| // Default encoding is fine. |
| u.RawPath = "" |
| } else { |
| u.RawPath = p |
| } |
| return nil |
| } |
| |
| func unescapeFunc(s string, mode encoding) (string, error) { |
| // Count %, check that they're well-formed. |
| n := 0 |
| hasPlus := false |
| for i := 0; i < len(s); { |
| switch s[i] { |
| case '%': |
| n++ |
| if i+2 >= len(s) || !isHexFunc(s[i+1]) || !isHexFunc(s[i+2]) { |
| s = s[i:] |
| if len(s) > 3 { |
| s = s[:3] |
| } |
| return "", url.EscapeError(s) |
| } |
| // Per https://tools.ietf.org/html/rfc3986#page-21 |
| // in the host component %-encoding can only be used |
| // for non-ASCII bytes. |
| // But https://tools.ietf.org/html/rfc6874#section-2 |
| // introduces %25 being allowed to escape a percent sign |
| // in IPv6 scoped-address literals. Yay. |
| if mode == encodeHost && unHexFunc(s[i+1]) < 8 && s[i:i+3] != "%25" { |
| return "", url.EscapeError(s[i : i+3]) |
| } |
| if mode == encodeZone { |
| // RFC 6874 says basically "anything goes" for zone identifiers |
| // and that even non-ASCII can be redundantly escaped, |
| // but it seems prudent to restrict %-escaped bytes here to those |
| // that are valid host name bytes in their unescaped form. |
| // That is, you can use escaping in the zone identifier but not |
| // to introduce bytes you couldn't just write directly. |
| // But Windows puts spaces here! Yay. |
| v := unHexFunc(s[i+1])<<4 | unHexFunc(s[i+2]) |
| if s[i:i+3] != "%25" && v != ' ' && shouldEscapeFunc(v, encodeHost) { |
| return "", url.EscapeError(s[i : i+3]) |
| } |
| } |
| i += 3 |
| case '+': |
| hasPlus = mode == encodeQueryComponent |
| i++ |
| default: |
| if (mode == encodeHost || mode == encodeZone) && s[i] < 0x80 && shouldEscapeFunc(s[i], mode) { |
| return "", url.InvalidHostError(s[i : i+1]) |
| } |
| i++ |
| } |
| } |
| |
| if n == 0 && !hasPlus { |
| return s, nil |
| } |
| |
| var t strings.Builder |
| t.Grow(len(s) - 2*n) |
| for i := 0; i < len(s); i++ { |
| switch s[i] { |
| case '%': |
| t.WriteByte(unHexFunc(s[i+1])<<4 | unHexFunc(s[i+2])) |
| i += 2 |
| case '+': |
| if mode == encodeQueryComponent { |
| t.WriteByte(' ') |
| } else { |
| t.WriteByte('+') |
| } |
| default: |
| t.WriteByte(s[i]) |
| } |
| } |
| return t.String(), nil |
| } |
| |
| func escapeFunc(s string, mode encoding) string { |
| spaceCount, hexCount := 0, 0 |
| for i := 0; i < len(s); i++ { |
| c := s[i] |
| if shouldEscapeFunc(c, mode) { |
| if c == ' ' && mode == encodeQueryComponent { |
| spaceCount++ |
| } else { |
| hexCount++ |
| } |
| } |
| } |
| |
| if spaceCount == 0 && hexCount == 0 { |
| return s |
| } |
| |
| var buf [64]byte |
| var t []byte |
| |
| required := len(s) + 2*hexCount |
| if required <= len(buf) { |
| t = buf[:required] |
| } else { |
| t = make([]byte, required) |
| } |
| |
| if hexCount == 0 { |
| copy(t, s) |
| for i := 0; i < len(s); i++ { |
| if s[i] == ' ' { |
| t[i] = '+' |
| } |
| } |
| return string(t) |
| } |
| |
| j := 0 |
| for i := 0; i < len(s); i++ { |
| switch c := s[i]; { |
| case c == ' ' && mode == encodeQueryComponent: |
| t[j] = '+' |
| j++ |
| case shouldEscapeFunc(c, mode): |
| t[j] = '%' |
| t[j+1] = upperhex[c>>4] |
| t[j+2] = upperhex[c&15] |
| j += 3 |
| default: |
| t[j] = s[i] |
| j++ |
| } |
| } |
| return string(t) |
| } |