1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | // See the LICENSE file in the project root for more information. |
4 | |
5 | /*** |
6 | *tsplitpath_s.inl - general implementation of _tsplitpath_s |
7 | * |
8 | |
9 | * |
10 | *Purpose: |
11 | * This file contains the general algorithm for _splitpath_s and its variants. |
12 | * |
13 | *******************************************************************************/ |
14 | |
15 | _FUNC_PROLOGUE |
16 | errno_t __cdecl _FUNC_NAME( |
17 | __in_z const _CHAR *_Path, |
18 | __out_ecount_z_opt(_DriveSize) _CHAR *_Drive, __in size_t _DriveSize, |
19 | __out_ecount_z_opt(_DirSize) _CHAR *_Dir, __in size_t _DirSize, |
20 | __out_ecount_z_opt(_FilenameSize) _CHAR *_Filename, __in size_t _FilenameSize, |
21 | __out_ecount_z_opt(_ExtSize) _CHAR *_Ext, __in size_t _ExtSize |
22 | ) |
23 | { |
24 | const _CHAR *tmp; |
25 | const _CHAR *last_slash; |
26 | const _CHAR *dot; |
27 | int drive_set = 0; |
28 | size_t length = 0; |
29 | int bEinval = 0; |
30 | |
31 | /* validation section */ |
32 | if (_Path == NULL) |
33 | { |
34 | goto error_einval; |
35 | } |
36 | if ((_Drive == NULL && _DriveSize != 0) || (_Drive != NULL && _DriveSize == 0)) |
37 | { |
38 | goto error_einval; |
39 | } |
40 | if ((_Dir == NULL && _DirSize != 0) || (_Dir != NULL && _DirSize == 0)) |
41 | { |
42 | goto error_einval; |
43 | } |
44 | if ((_Filename == NULL && _FilenameSize != 0) || (_Filename != NULL && _FilenameSize == 0)) |
45 | { |
46 | goto error_einval; |
47 | } |
48 | if ((_Ext == NULL && _ExtSize != 0) || (_Ext != NULL && _ExtSize == 0)) |
49 | { |
50 | goto error_einval; |
51 | } |
52 | |
53 | /* check if _Path begins with the longpath prefix */ |
54 | if (_Path[0] == _T('\\') && _Path[1] == _T('\\') && _Path[2] == _T('?') && _Path[3] == _T('\\')) |
55 | { |
56 | _Path += 4; |
57 | } |
58 | |
59 | /* extract drive letter and ':', if any */ |
60 | if (!drive_set) |
61 | { |
62 | // The CorUnix PAL is never built on Windows and thus, the code below |
63 | // for the drive check is not required. |
64 | #if 0 |
65 | size_t skip = _MAX_DRIVE - 2; |
66 | tmp = _Path; |
67 | while (skip > 0 && *tmp != 0) |
68 | { |
69 | skip--; |
70 | tmp++; |
71 | } |
72 | if (*tmp == _T(':')) |
73 | { |
74 | if (_Drive != NULL) |
75 | { |
76 | if (_DriveSize < _MAX_DRIVE) |
77 | { |
78 | goto error_erange; |
79 | } |
80 | _TCSNCPY_S(_Drive, _DriveSize, _Path, _MAX_DRIVE - 1); |
81 | } |
82 | _Path = tmp + 1; |
83 | } |
84 | else |
85 | #endif |
86 | { |
87 | if (_Drive != NULL) |
88 | { |
89 | _RESET_STRING(_Drive, _DriveSize); |
90 | } |
91 | } |
92 | } |
93 | |
94 | /* extract path string, if any. _Path now points to the first character |
95 | * of the path, if any, or the filename or extension, if no path was |
96 | * specified. Scan ahead for the last occurence, if any, of a '/' or |
97 | * '\' path separator character. If none is found, there is no path. |
98 | * We will also note the last '.' character found, if any, to aid in |
99 | * handling the extension. |
100 | */ |
101 | last_slash = NULL; |
102 | dot = NULL; |
103 | tmp = _Path; |
104 | for (; *tmp != 0; ++tmp) |
105 | { |
106 | #if _MBS_SUPPORT |
107 | #pragma warning(push) |
108 | #pragma warning(disable:4127) |
109 | if (_ISMBBLEAD(*tmp)) |
110 | #pragma warning(pop) |
111 | { |
112 | tmp++; |
113 | } |
114 | else |
115 | #endif /* _MBS_SUPPORT */ |
116 | { |
117 | if (*tmp == _T('/') || *tmp == _T('\\')) |
118 | { |
119 | /* point to one beyond for later copy */ |
120 | last_slash = tmp + 1; |
121 | } |
122 | else if (*tmp == _T('.')) |
123 | { |
124 | dot = tmp; |
125 | } |
126 | } |
127 | } |
128 | |
129 | if (last_slash != NULL) |
130 | { |
131 | /* found a path - copy up through last_slash or max characters |
132 | * allowed, whichever is smaller |
133 | */ |
134 | if (_Dir != NULL) { |
135 | length = (size_t)(last_slash - _Path); |
136 | if (_DirSize <= length) |
137 | { |
138 | goto error_erange; |
139 | } |
140 | _TCSNCPY_S(_Dir, _DirSize, _Path, length); |
141 | |
142 | // Normalize the path seperator |
143 | int iIndex; |
144 | for(iIndex = 0; iIndex < length; iIndex++) |
145 | { |
146 | if (_Dir[iIndex] == _T('\\')) |
147 | { |
148 | _Dir[iIndex] = _T('/'); |
149 | } |
150 | } |
151 | } |
152 | _Path = last_slash; |
153 | } |
154 | else |
155 | { |
156 | /* there is no path */ |
157 | if (_Dir != NULL) |
158 | { |
159 | _RESET_STRING(_Dir, _DirSize); |
160 | } |
161 | } |
162 | |
163 | /* extract file name and extension, if any. Path now points to the |
164 | * first character of the file name, if any, or the extension if no |
165 | * file name was given. Dot points to the '.' beginning the extension, |
166 | * if any. |
167 | */ |
168 | if (dot != NULL && (dot >= _Path)) |
169 | { |
170 | /* found the marker for an extension - copy the file name up to the '.' */ |
171 | if (_Filename) |
172 | { |
173 | length = (size_t)(dot - _Path); |
174 | if (length == 0) |
175 | { |
176 | // At this time, dot will be equal to _Path if string is something like "/." |
177 | // since _path was set to last_slash, which in turn, was set to "tmp +1" |
178 | // where "tmp" is the location where "/" was found. See code above for |
179 | // clarification. |
180 | // |
181 | // For such cases, return the "." in filename buffer. |
182 | // |
183 | // Thus, if the length is zero, we know its a string like "/." and thus, we |
184 | // set length to 1 to get the "." in filename buffer. |
185 | length = 1; |
186 | } |
187 | |
188 | if (_FilenameSize <= length) |
189 | { |
190 | goto error_erange; |
191 | } |
192 | _TCSNCPY_S(_Filename, _FilenameSize, _Path, length); |
193 | } |
194 | |
195 | /* now we can get the extension - remember that tmp still points |
196 | * to the terminating NULL character of path. |
197 | */ |
198 | if (_Ext) |
199 | { |
200 | // At this time, _Path is pointing to the character after the last slash found. |
201 | // (See comments and code above for clarification). |
202 | // |
203 | // Returns extension as empty string for strings like "/.". |
204 | if (dot > _Path) |
205 | { |
206 | length = (size_t)(tmp - dot); |
207 | if (_ExtSize <= length) |
208 | { |
209 | goto error_erange; |
210 | } |
211 | |
212 | /* Since dot pointed to the ".", make sure we actually have an extension |
213 | like ".cmd" and not just ".", OR |
214 | |
215 | Confirm that its a string like "/.." - for this, return the |
216 | second "." in the extension part. |
217 | |
218 | However, for strings like "/myfile.", return empty string |
219 | in extension buffer. |
220 | */ |
221 | int fIsDir = (*(dot-1) == _T('.'))?1:0; |
222 | if (length > 1 || (length == 1 && fIsDir == 1)) |
223 | _TCSNCPY_S(_Ext, _ExtSize, dot, length); |
224 | else |
225 | _RESET_STRING(_Ext, _ExtSize); |
226 | } |
227 | else |
228 | _RESET_STRING(_Ext, _ExtSize); |
229 | } |
230 | } |
231 | else |
232 | { |
233 | /* found no extension, give empty extension and copy rest of |
234 | * string into fname. |
235 | */ |
236 | if (_Filename) |
237 | { |
238 | length = (size_t)(tmp - _Path); |
239 | if (_FilenameSize <= length) |
240 | { |
241 | goto error_erange; |
242 | } |
243 | _TCSNCPY_S(_Filename, _FilenameSize, _Path, length); |
244 | } |
245 | if (_Ext) |
246 | { |
247 | _RESET_STRING(_Ext, _ExtSize); |
248 | } |
249 | } |
250 | |
251 | _RETURN_NO_ERROR; |
252 | |
253 | error_einval: |
254 | bEinval = 1; |
255 | |
256 | error_erange: |
257 | if (_Drive != NULL && _DriveSize > 0) |
258 | { |
259 | _RESET_STRING(_Drive, _DriveSize); |
260 | } |
261 | if (_Dir != NULL && _DirSize > 0) |
262 | { |
263 | _RESET_STRING(_Dir, _DirSize); |
264 | } |
265 | if (_Filename != NULL && _FilenameSize > 0) |
266 | { |
267 | _RESET_STRING(_Filename, _FilenameSize); |
268 | } |
269 | if (_Ext != NULL && _ExtSize > 0) |
270 | { |
271 | _RESET_STRING(_Ext, _ExtSize); |
272 | } |
273 | |
274 | _VALIDATE_POINTER(_Path); |
275 | if (bEinval) |
276 | { |
277 | _RETURN_EINVAL; |
278 | } |
279 | return (errno = ERANGE); |
280 | } |
281 | |