status-go/vendor/go.lsp.dev/uri/uri.go

193 lines
4.3 KiB
Go
Raw Normal View History

// Copyright 2019 The Go Language Server Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uri
import (
"errors"
"fmt"
"net/url"
"path/filepath"
"runtime"
"strings"
"unicode"
)
const (
// FileScheme schema of filesystem path.
FileScheme = "file"
// HTTPScheme schema of http.
HTTPScheme = "http"
// HTTPSScheme schema of https.
HTTPSScheme = "https"
)
const (
hierPart = "://"
)
// URI Uniform Resource Identifier (URI) https://tools.ietf.org/html/rfc3986.
//
// This class is a simple parser which creates the basic component parts
// (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation
// and encoding.
//
// foo://example.com:8042/over/there?name=ferret#nose
// \_/ \______________/\_________/ \_________/ \__/
// | | | | |
// scheme authority path query fragment
// | _____________________|__
// / \ / \
// urn:example:animal:ferret:nose
type URI string
// Filename returns the file path for the given URI.
// It is an error to call this on a URI that is not a valid filename.
func (u URI) Filename() string {
filename, err := filename(u)
if err != nil {
panic(err)
}
return filepath.FromSlash(filename)
}
func filename(uri URI) (string, error) {
u, err := url.ParseRequestURI(string(uri))
if err != nil {
return "", fmt.Errorf("failed to parse request URI: %w", err)
}
if u.Scheme != FileScheme {
return "", fmt.Errorf("only file URIs are supported, got %v", u.Scheme)
}
if isWindowsDriveURI(u.Path) {
u.Path = u.Path[1:]
}
return u.Path, nil
}
// New parses and creates a new URI from s.
func New(s string) URI {
if u, err := url.PathUnescape(s); err == nil {
s = u
}
if strings.HasPrefix(s, FileScheme+hierPart) {
return URI(s)
}
return File(s)
}
// File parses and creates a new filesystem URI from path.
func File(path string) URI {
const goRootPragma = "$GOROOT"
if len(path) >= len(goRootPragma) && strings.EqualFold(goRootPragma, path[:len(goRootPragma)]) {
path = runtime.GOROOT() + path[len(goRootPragma):]
}
if !isWindowsDrivePath(path) {
if abs, err := filepath.Abs(path); err == nil {
path = abs
}
}
if isWindowsDrivePath(path) {
path = "/" + path
}
path = filepath.ToSlash(path)
u := url.URL{
Scheme: FileScheme,
Path: path,
}
return URI(u.String())
}
// Parse parses and creates a new URI from s.
func Parse(s string) (u URI, err error) {
us, err := url.Parse(s)
if err != nil {
return u, fmt.Errorf("url.Parse: %w", err)
}
switch us.Scheme {
case FileScheme:
ut := url.URL{
Scheme: FileScheme,
Path: us.Path,
RawPath: filepath.FromSlash(us.Path),
}
u = URI(ut.String())
case HTTPScheme, HTTPSScheme:
ut := url.URL{
Scheme: us.Scheme,
Host: us.Host,
Path: us.Path,
RawQuery: us.Query().Encode(),
Fragment: us.Fragment,
}
u = URI(ut.String())
default:
return u, errors.New("unknown scheme")
}
return
}
// From returns the new URI from args.
func From(scheme, authority, path, query, fragment string) URI {
switch scheme {
case FileScheme:
u := url.URL{
Scheme: FileScheme,
Path: path,
RawPath: filepath.FromSlash(path),
}
return URI(u.String())
case HTTPScheme, HTTPSScheme:
u := url.URL{
Scheme: scheme,
Host: authority,
Path: path,
RawQuery: url.QueryEscape(query),
Fragment: fragment,
}
return URI(u.String())
default:
panic(fmt.Sprintf("unknown scheme: %s", scheme))
}
}
// isWindowsDrivePath returns true if the file path is of the form used by Windows.
//
// We check if the path begins with a drive letter, followed by a ":".
func isWindowsDrivePath(path string) bool {
if len(path) < 4 {
return false
}
return unicode.IsLetter(rune(path[0])) && path[1] == ':'
}
// isWindowsDriveURI returns true if the file URI is of the format used by
// Windows URIs. The url.Parse package does not specially handle Windows paths
// (see https://golang.org/issue/6027). We check if the URI path has
// a drive prefix (e.g. "/C:"). If so, we trim the leading "/".
func isWindowsDriveURI(uri string) bool {
if len(uri) < 4 {
return false
}
return uri[0] == '/' && unicode.IsLetter(rune(uri[1])) && uri[2] == ':'
}