Files
wehub-resource-sync 5357c39144
Fuzzer / Run Fuzzer (push) Has been cancelled
Race tests / Go race tests (ubuntu-22.04) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:01:40 +08:00

169 lines
5.3 KiB
Go

// Copyright 2019 Dolthub, 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 earl
import (
"errors"
"fmt"
"net/url"
"regexp"
"strconv"
"strings"
"github.com/dolthub/dolt/go/libraries/utils/osutil"
)
var validHostRegex = regexp.MustCompile("^[-.a-zA-z0-9]*$")
var validHostWithPortRegex = regexp.MustCompile("^[-.a-zA-z0-9]*:[0-9]*$")
func isValidHost(hostAndPortStr string) bool {
hostStr := hostAndPortStr
portStr := ""
if idx := strings.IndexRune(hostAndPortStr, ':'); idx != -1 {
hostStr = hostAndPortStr[:idx]
portStr = strings.TrimSpace(hostAndPortStr[idx+1:])
}
if len(portStr) > 0 {
if _, err := strconv.ParseUint(portStr, 10, 16); err != nil {
return false
}
}
if hostStr == "" {
return false
} else if hostStr == "localhost" {
return true
} else if strings.Index(hostStr, ".") == -1 {
return false
}
return validHostRegex.MatchString(hostStr) || validHostWithPortRegex.MatchString(hostStr)
}
func Parse(urlStr string) (*url.URL, error) {
u, err := parse(urlStr)
if err != nil {
return nil, err
}
// url.parse doesn't handle file paths that begin with . correctly
if u.Scheme == "file" && strings.HasPrefix(u.Host, ".") {
u.Path = u.Host + u.Path
u.Host = ""
}
// if Path is e.g. "/C$/" for a network location, it should instead be "C:/"
if len(u.Path) >= 3 && u.Path[0] == '/' && u.Path[1] >= 'A' && u.Path[1] <= 'Z' && u.Path[2] == '$' {
u.Path = u.Path[1:2] + ":" + u.Path[3:]
} else if !osutil.StartsWithWindowsVolume(u.Path) { // normalize some
if len(u.Path) == 0 || (u.Path[0] != '/' && u.Path[0] != '.') {
u.Path = "/" + u.Path
}
}
u.Path = strings.ReplaceAll(u.Path, `\`, "/")
return u, nil
}
func ParseRawWithAWSSupport(urlStr string) (*url.URL, error) {
// XXX: This is a kludge to support AWS remote URLs. These URLs use a non-standard syntax to specify the s3 bucket and dynamodb table names, and they look like:
// aws://[s3_bucket_name:dynamodb_table_name]/path/to/files/in/s3/and/db/key/in/dynamo
//
// This was supported by Go url.Parse until 1.25.2, where validation was added to the bracketed hostname component:
// https://github.com/golang/go/issues/75678
//
// Here we explicitly kludge around the aws schema in a hard-coded way. Pretty gross for now.
if strings.HasPrefix(urlStr, "aws://[") {
hostStart := 7
hostEnd := hostStart + strings.Index(urlStr[hostStart:], "]")
if hostEnd == hostStart-1 {
return nil, errors.New("could not parse aws schema url: expected aws://[s3_bucket:dynamodb_table] but did not find closing bracket.")
}
host := urlStr[hostStart:hostEnd]
hostColon := strings.Index(host, ":")
if hostColon == -1 {
return nil, errors.New("could not parse aws schema url: expected aws://[s3_bucket:dynamodb_table] but did not find colon introducting dynamodb_table.")
}
rawBucketName := host[:hostColon]
rawTableName := host[hostColon+1:]
// For full compliance with previous beahvior, we pass both components through url.Parse as hostnames to get the same escape handling as we used to have.
parsedBucketName, err := url.Parse("http://" + rawBucketName)
if err != nil {
return nil, fmt.Errorf("could not parse aws s3 bucket name as hostname: %w", err)
}
parsedTableName, err := url.Parse("http://" + rawTableName)
if err != nil {
return nil, fmt.Errorf("could not parse aws dynamodb table name as hostname: %w", err)
}
returnedHost := "[" + parsedBucketName.Host + ":" + parsedTableName.Host + "]"
// Here we parse the original urlStr but with the host component replaced by a hard coded compliant value. We then replace the Host in the *URL we return.
parsed, err := url.Parse("aws://hostname" + urlStr[hostEnd+1:])
if err != nil {
return nil, fmt.Errorf("could not parse aws url: %w", err)
}
parsed.Host = returnedHost
return parsed, nil
}
return url.Parse(urlStr)
}
func parse(urlStr string) (*url.URL, error) {
if strIdx := strings.Index(urlStr, ":///"); strIdx != -1 && osutil.StartsWithWindowsVolume(urlStr[strIdx+4:]) {
return &url.URL{
Scheme: urlStr[:strIdx],
Path: urlStr[strIdx+4:],
}, nil
}
if strIdx := strings.Index(urlStr, "://"); strIdx != -1 && osutil.StartsWithWindowsVolume(urlStr[strIdx+3:]) {
return &url.URL{
Scheme: urlStr[:strIdx],
Path: urlStr[strIdx+3:],
}, nil
}
if strings.Index(urlStr, "://") == -1 {
u, err := url.Parse("http://" + urlStr)
if err == nil && isValidHost(u.Host) {
u.Scheme = ""
return u, nil
} else if err != nil {
return nil, err
}
}
return ParseRawWithAWSSupport(urlStr)
}
// FileUrlFromPath returns a url for the given path with the "file" scheme i.e. file://...
func FileUrlFromPath(path string, separator rune) string {
if osutil.StartsWithWindowsVolume(path) {
path = "/" + path
}
if separator != '/' {
path = strings.ReplaceAll(path, string(separator), "/")
}
u := &url.URL{Scheme: "file", Path: path}
urlStr := u.String()
return urlStr
}