// Copyright 2023 Harness, Inc. // // Licensed 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 request import ( "fmt" "net/http" "net/url" ) // ReplacePrefix replaces the path of the request. // IMPORTANT: // - both prefix are unescaped for path, and used as is for RawPath! // - only called by top level handler!! func ReplacePrefix(r *http.Request, oldPrefix string, newPrefix string) error { /* * According to official documentation, we can change anything in the request but the body: * https://pkg.go.dev/net/http#Handler * * ASSUMPTION: * This is called by a top level handler (no router or middleware above it) * Therefore, we don't have to worry about getting any routing metadata out of sync. * * This is different to returning a shallow clone with updated URL, which is what * http.StripPrefix or earlier versions of request.WithContext are doing: * https://cs.opensource.google/go/go/+/refs/tags/go1.19:src/net/http/server.go;l=2138 * https://cs.opensource.google/go/go/+/refs/tags/go1.18:src/net/http/request.go;l=355 * * http.StripPrefix initially changed the path only, but that was updated because of official recommendations: * https://github.com/golang/go/issues/18952 */ unOldPrefix, err := url.PathUnescape(oldPrefix) if err != nil { return fmt.Errorf("failed to unescape old prefix '%s'", oldPrefix) } unNewPrefix, err := url.PathUnescape(newPrefix) if err != nil { return fmt.Errorf("failed to unescape new prefix '%s'", newPrefix) } unl := len(unOldPrefix) if len(r.URL.Path) < unl || r.URL.Path[0:unl] != unOldPrefix { return fmt.Errorf("path '%s' doesn't contain prefix '%s'", r.URL.Path, unOldPrefix) } // only change RawPath if it exists if r.URL.RawPath != "" { l := len(oldPrefix) if len(r.URL.RawPath) < l || r.URL.RawPath[0:l] != oldPrefix { return fmt.Errorf("raw path '%s' doesn't contain prefix '%s'", r.URL.RawPath, oldPrefix) } r.URL.RawPath = newPrefix + r.URL.RawPath[l:] } r.URL.Path = unNewPrefix + r.URL.Path[unl:] return nil }