url.go (3968B)
1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Code copied from 6 // https://github.com/golang/go/blob/2ebe77a2fda1ee9ff6fd9a3e08933ad1ebaea039/src/cmd/go/internal/web/url.go 7 // TODO(https://go.dev/issue/32456): if accepted, use the new API. 8 9 package web 10 11 import ( 12 "errors" 13 "net/url" 14 "path/filepath" 15 "runtime" 16 "strings" 17 ) 18 19 var errNotAbsolute = errors.New("path is not absolute") 20 21 // URLToFilePath converts a file-scheme url to a file path. 22 func URLToFilePath(u *url.URL) (string, error) { 23 if u.Scheme != "file" { 24 return "", errors.New("non-file URL") 25 } 26 27 checkAbs := func(path string) (string, error) { 28 if !filepath.IsAbs(path) { 29 return "", errNotAbsolute 30 } 31 return path, nil 32 } 33 34 if u.Path == "" { 35 if u.Host != "" || u.Opaque == "" { 36 return "", errors.New("file URL missing path") 37 } 38 return checkAbs(filepath.FromSlash(u.Opaque)) 39 } 40 41 path, err := convertFileURLPath(u.Host, u.Path) 42 if err != nil { 43 return path, err 44 } 45 return checkAbs(path) 46 } 47 48 // URLFromFilePath converts the given absolute path to a URL. 49 func URLFromFilePath(path string) (*url.URL, error) { 50 if !filepath.IsAbs(path) { 51 return nil, errNotAbsolute 52 } 53 54 // If path has a Windows volume name, convert the volume to a host and prefix 55 // per https://blogs.msdn.microsoft.com/ie/2006/12/06/file-uris-in-windows/. 56 if vol := filepath.VolumeName(path); vol != "" { 57 if strings.HasPrefix(vol, `\\`) { 58 path = filepath.ToSlash(path[2:]) 59 i := strings.IndexByte(path, '/') 60 61 if i < 0 { 62 // A degenerate case. 63 // \\host.example.com (without a share name) 64 // becomes 65 // file://host.example.com/ 66 return &url.URL{ 67 Scheme: "file", 68 Host: path, 69 Path: "/", 70 }, nil 71 } 72 73 // \\host.example.com\Share\path\to\file 74 // becomes 75 // file://host.example.com/Share/path/to/file 76 return &url.URL{ 77 Scheme: "file", 78 Host: path[:i], 79 Path: filepath.ToSlash(path[i:]), 80 }, nil 81 } 82 83 // C:\path\to\file 84 // becomes 85 // file:///C:/path/to/file 86 return &url.URL{ 87 Scheme: "file", 88 Path: "/" + filepath.ToSlash(path), 89 }, nil 90 } 91 92 // /path/to/file 93 // becomes 94 // file:///path/to/file 95 return &url.URL{ 96 Scheme: "file", 97 Path: filepath.ToSlash(path), 98 }, nil 99 } 100 101 func convertFileURLPath(host, path string) (string, error) { 102 if runtime.GOOS == "windows" { 103 return convertFileURLPathWindows(host, path) 104 } 105 switch host { 106 case "", "localhost": 107 default: 108 return "", errors.New("file URL specifies non-local host") 109 } 110 return filepath.FromSlash(path), nil 111 } 112 113 func convertFileURLPathWindows(host, path string) (string, error) { 114 if len(path) == 0 || path[0] != '/' { 115 return "", errNotAbsolute 116 } 117 118 path = filepath.FromSlash(path) 119 120 // We interpret Windows file URLs per the description in 121 // https://blogs.msdn.microsoft.com/ie/2006/12/06/file-uris-in-windows/. 122 123 // The host part of a file URL (if any) is the UNC volume name, 124 // but RFC 8089 reserves the authority "localhost" for the local machine. 125 if host != "" && host != "localhost" { 126 // A common "legacy" format omits the leading slash before a drive letter, 127 // encoding the drive letter as the host instead of part of the path. 128 // (See https://blogs.msdn.microsoft.com/freeassociations/2005/05/19/the-bizarre-and-unhappy-story-of-file-urls/.) 129 // We do not support that format, but we should at least emit a more 130 // helpful error message for it. 131 if filepath.VolumeName(host) != "" { 132 return "", errors.New("file URL encodes volume in host field: too few slashes?") 133 } 134 return `\\` + host + path, nil 135 } 136 137 // If host is empty, path must contain an initial slash followed by a 138 // drive letter and path. Remove the slash and verify that the path is valid. 139 if vol := filepath.VolumeName(path[1:]); vol == "" || strings.HasPrefix(vol, `\\`) { 140 return "", errors.New("file URL missing drive letter") 141 } 142 return path[1:], nil 143 }