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
16errno_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
253error_einval:
254 bEinval = 1;
255
256error_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