]> Cypherpunks.ru repositories - gostls13.git/blob - src/internal/safefilepath/path_windows.go
7cfd6ce2eac2622b7a74135ea66bee460ae9c910
[gostls13.git] / src / internal / safefilepath / path_windows.go
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 package safefilepath
6
7 import (
8         "syscall"
9         "unicode/utf8"
10 )
11
12 func fromFS(path string) (string, error) {
13         if !utf8.ValidString(path) {
14                 return "", errInvalidPath
15         }
16         for len(path) > 1 && path[0] == '/' && path[1] == '/' {
17                 path = path[1:]
18         }
19         containsSlash := false
20         for p := path; p != ""; {
21                 // Find the next path element.
22                 i := 0
23                 for i < len(p) && p[i] != '/' {
24                         switch p[i] {
25                         case 0, '\\', ':':
26                                 return "", errInvalidPath
27                         }
28                         i++
29                 }
30                 part := p[:i]
31                 if i < len(p) {
32                         containsSlash = true
33                         p = p[i+1:]
34                 } else {
35                         p = ""
36                 }
37                 if IsReservedName(part) {
38                         return "", errInvalidPath
39                 }
40         }
41         if containsSlash {
42                 // We can't depend on strings, so substitute \ for / manually.
43                 buf := []byte(path)
44                 for i, b := range buf {
45                         if b == '/' {
46                                 buf[i] = '\\'
47                         }
48                 }
49                 path = string(buf)
50         }
51         return path, nil
52 }
53
54 // IsReservedName reports if name is a Windows reserved device name.
55 // It does not detect names with an extension, which are also reserved on some Windows versions.
56 //
57 // For details, search for PRN in
58 // https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file.
59 func IsReservedName(name string) bool {
60         // Device names can have arbitrary trailing characters following a dot or colon.
61         base := name
62         for i := 0; i < len(base); i++ {
63                 switch base[i] {
64                 case ':', '.':
65                         base = base[:i]
66                 }
67         }
68         // Trailing spaces in the last path element are ignored.
69         for len(base) > 0 && base[len(base)-1] == ' ' {
70                 base = base[:len(base)-1]
71         }
72         if !isReservedBaseName(base) {
73                 return false
74         }
75         if len(base) == len(name) {
76                 return true
77         }
78         // The path element is a reserved name with an extension.
79         // Some Windows versions consider this a reserved name,
80         // while others do not. Use FullPath to see if the name is
81         // reserved.
82         if p, _ := syscall.FullPath(name); len(p) >= 4 && p[:4] == `\\.\` {
83                 return true
84         }
85         return false
86 }
87
88 func isReservedBaseName(name string) bool {
89         if len(name) == 3 {
90                 switch string([]byte{toUpper(name[0]), toUpper(name[1]), toUpper(name[2])}) {
91                 case "CON", "PRN", "AUX", "NUL":
92                         return true
93                 }
94         }
95         if len(name) >= 4 {
96                 switch string([]byte{toUpper(name[0]), toUpper(name[1]), toUpper(name[2])}) {
97                 case "COM", "LPT":
98                         if len(name) == 4 && '1' <= name[3] && name[3] <= '9' {
99                                 return true
100                         }
101                         // Superscript ¹, ², and ³ are considered numbers as well.
102                         switch name[3:] {
103                         case "\u00b2", "\u00b3", "\u00b9":
104                                 return true
105                         }
106                         return false
107                 }
108         }
109
110         // Passing CONIN$ or CONOUT$ to CreateFile opens a console handle.
111         // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#consoles
112         //
113         // While CONIN$ and CONOUT$ aren't documented as being files,
114         // they behave the same as CON. For example, ./CONIN$ also opens the console input.
115         if len(name) == 6 && name[5] == '$' && equalFold(name, "CONIN$") {
116                 return true
117         }
118         if len(name) == 7 && name[6] == '$' && equalFold(name, "CONOUT$") {
119                 return true
120         }
121         return false
122 }
123
124 func equalFold(a, b string) bool {
125         if len(a) != len(b) {
126                 return false
127         }
128         for i := 0; i < len(a); i++ {
129                 if toUpper(a[i]) != toUpper(b[i]) {
130                         return false
131                 }
132         }
133         return true
134 }
135
136 func toUpper(c byte) byte {
137         if 'a' <= c && c <= 'z' {
138                 return c - ('a' - 'A')
139         }
140         return c
141 }