193 lines
4.3 KiB
Go
193 lines
4.3 KiB
Go
|
// 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] == ':'
|
||
|
}
|