/src/mysql-server/mysys/mf_pack.cc
Line | Count | Source |
1 | | /* Copyright (c) 2000, 2025, Oracle and/or its affiliates. |
2 | | |
3 | | This program is free software; you can redistribute it and/or modify |
4 | | it under the terms of the GNU General Public License, version 2.0, |
5 | | as published by the Free Software Foundation. |
6 | | |
7 | | This program is designed to work with certain software (including |
8 | | but not limited to OpenSSL) that is licensed under separate terms, |
9 | | as designated in a particular file or component or in included license |
10 | | documentation. The authors of MySQL hereby grant you an additional |
11 | | permission to link the program and your derivative works with the |
12 | | separately licensed software that they have either included with |
13 | | the program or referenced in the documentation. |
14 | | |
15 | | Without limiting anything contained in the foregoing, this file, |
16 | | which is part of C Driver for MySQL (Connector/C), is also subject to the |
17 | | Universal FOSS Exception, version 1.0, a copy of which can be found at |
18 | | http://oss.oracle.com/licenses/universal-foss-exception. |
19 | | |
20 | | This program is distributed in the hope that it will be useful, |
21 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
22 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
23 | | GNU General Public License, version 2.0, for more details. |
24 | | |
25 | | You should have received a copy of the GNU General Public License |
26 | | along with this program; if not, write to the Free Software |
27 | | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
28 | | */ |
29 | | |
30 | | /** |
31 | | @file mysys/mf_pack.cc |
32 | | */ |
33 | | |
34 | | #include "my_config.h" |
35 | | |
36 | | #include <string> |
37 | | |
38 | | #include <cstring> |
39 | | |
40 | | #ifdef _WIN32 |
41 | | #include "mysql/strings/m_ctype.h" |
42 | | #endif |
43 | | #include "m_string.h" |
44 | | #include "my_dbug.h" |
45 | | #include "my_getpwnam.h" |
46 | | #include "my_inttypes.h" |
47 | | #include "my_io.h" |
48 | | #include "my_sys.h" |
49 | | #include "mysys/my_static.h" |
50 | | #include "strmake.h" |
51 | | |
52 | | static std::string expand_tilde(char **path); |
53 | | |
54 | | /** |
55 | | Remove unwanted chars from dirname. |
56 | | |
57 | | Pre-condition: At least FN_REFLEN bytes can be stored in buffer |
58 | | pointed to by 'to'. 'from' is a '\0'-terminated byte buffer. |
59 | | |
60 | | Post-condition: At most FN_REFLEN bytes will have been written to |
61 | | 'to'. If the combined length of 'from' and any expanded elements |
62 | | exceeds FN_REFLEN-1, the result is truncated and likely not what the |
63 | | caller expects. |
64 | | |
65 | | IMPLEMENTATION: |
66 | | "/../" removes prev dir |
67 | | "/~/" removes all before ~ |
68 | | //" is same as "/", except on Win32 at start of a file |
69 | | "/./" is removed |
70 | | Unpacks home_dir if "~/.." used |
71 | | Unpacks current dir if if "./.." used |
72 | | |
73 | | @param to Store result here |
74 | | @param from Dirname to fix. May be same as to |
75 | | |
76 | | @return length of new name |
77 | | */ |
78 | | |
79 | 0 | size_t cleanup_dirname(char *to, const char *from) { |
80 | 0 | char *pos; |
81 | 0 | const char *from_ptr; |
82 | 0 | char *start; |
83 | 0 | char parent[5], /* for "FN_PARENTDIR" */ |
84 | 0 | buff[FN_REFLEN + 1], *end_parentdir; |
85 | | #ifdef _WIN32 |
86 | | CHARSET_INFO *fs = fs_character_set(); |
87 | | #endif |
88 | 0 | DBUG_TRACE; |
89 | 0 | DBUG_PRINT("enter", ("from: '%s'", from)); |
90 | |
|
91 | 0 | start = buff; |
92 | 0 | from_ptr = from; |
93 | | #ifdef FN_DEVCHAR |
94 | | { |
95 | | const char *dev_pos = strrchr(from_ptr, FN_DEVCHAR); |
96 | | if (dev_pos != nullptr) { /* Skip device part */ |
97 | | const size_t length = (dev_pos - from_ptr) + 1; |
98 | | start = my_stpnmov(buff, from_ptr, length); |
99 | | from_ptr += length; |
100 | | } |
101 | | } |
102 | | #endif |
103 | |
|
104 | 0 | parent[0] = FN_LIBCHAR; |
105 | 0 | const size_t length = my_stpcpy(parent + 1, FN_PARENTDIR) - parent; |
106 | 0 | const char *end = start + FN_REFLEN; |
107 | 0 | for (pos = start; pos < end && ((*pos = *from_ptr++) != 0); pos++) { |
108 | | #ifdef _WIN32 |
109 | | uint l; |
110 | | if (use_mb(fs) && (l = my_ismbchar(fs, from_ptr - 1, from_ptr + 2))) { |
111 | | for (l--; l; *++pos = *from_ptr++, l--) |
112 | | ; |
113 | | start = pos + 1; /* Don't look inside multi-byte char */ |
114 | | continue; |
115 | | } |
116 | | #endif |
117 | 0 | if (*pos == '/') *pos = FN_LIBCHAR; |
118 | 0 | if (*pos == FN_LIBCHAR) { |
119 | 0 | if ((size_t)(pos - start) > length && |
120 | 0 | memcmp(pos - length, parent, length) == |
121 | 0 | 0) { /* If .../../; skip prev */ |
122 | 0 | pos -= length; |
123 | 0 | if (pos != start) { /* not /../ */ |
124 | 0 | pos--; |
125 | 0 | if (*pos == FN_HOMELIB && (pos == start || pos[-1] == FN_LIBCHAR)) { |
126 | 0 | if (!home_dir) { |
127 | 0 | pos += length + 1; /* Don't unpack ~/.. */ |
128 | 0 | continue; |
129 | 0 | } |
130 | 0 | pos = my_stpcpy(buff, home_dir) - 1; /* Unpacks ~/.. */ |
131 | 0 | if (*pos == FN_LIBCHAR) pos--; /* home ended with '/' */ |
132 | 0 | } |
133 | 0 | if (*pos == FN_CURLIB && (pos == start || pos[-1] == FN_LIBCHAR)) { |
134 | 0 | if (my_getwd(curr_dir, FN_REFLEN, MYF(0))) { |
135 | 0 | pos += length + 1; /* Don't unpack ./.. */ |
136 | 0 | continue; |
137 | 0 | } |
138 | 0 | pos = my_stpcpy(buff, curr_dir) - 1; /* Unpacks ./.. */ |
139 | 0 | if (*pos == FN_LIBCHAR) pos--; /* home ended with '/' */ |
140 | 0 | } |
141 | 0 | end_parentdir = pos; |
142 | 0 | while (pos >= start && *pos != FN_LIBCHAR) /* remove prev dir */ |
143 | 0 | pos--; |
144 | 0 | if (pos[1] == FN_HOMELIB || |
145 | 0 | (pos >= start && |
146 | 0 | memcmp(pos, parent, length) == 0)) { /* Don't remove ~user/ */ |
147 | 0 | pos = my_stpcpy(end_parentdir + 1, parent); |
148 | 0 | *pos = FN_LIBCHAR; |
149 | 0 | continue; |
150 | 0 | } |
151 | 0 | } |
152 | 0 | } else if ((size_t)(pos - start) == length - 1 && |
153 | 0 | !memcmp(start, parent + 1, length - 1)) |
154 | 0 | start = pos; /* Starts with "../" */ |
155 | 0 | else if (pos - start > 0 && pos[-1] == FN_LIBCHAR) { |
156 | | #ifdef FN_NETWORK_DRIVES |
157 | | if (pos - start != 1) |
158 | | #endif |
159 | 0 | pos--; /* Remove duplicate '/' */ |
160 | 0 | } else if (pos - start > 1 && pos[-1] == FN_CURLIB && |
161 | 0 | pos[-2] == FN_LIBCHAR) |
162 | 0 | pos -= 2; /* Skip /./ */ |
163 | 0 | else if (pos > buff + 1 && pos[-1] == FN_HOMELIB && |
164 | 0 | pos[-2] == FN_LIBCHAR) { /* Found ..../~/ */ |
165 | 0 | buff[0] = FN_HOMELIB; |
166 | 0 | buff[1] = FN_LIBCHAR; |
167 | 0 | start = buff; |
168 | 0 | pos = buff + 1; |
169 | 0 | } |
170 | 0 | } |
171 | 0 | } |
172 | |
|
173 | 0 | buff[FN_REFLEN - 1] = '\0'; |
174 | 0 | (void)my_stpcpy(to, buff); |
175 | 0 | DBUG_PRINT("exit", ("to: '%s'", to)); |
176 | 0 | return (size_t)(pos - buff); |
177 | 0 | } /* cleanup_dirname */ |
178 | | |
179 | | /** |
180 | | Convert a directory name to a format which can be compared as strings. |
181 | | |
182 | | Pre-condition: At least FN_REFLEN bytes can be stored in buffer |
183 | | pointed to by 'to'. 'from' is a '\0'-terminated byte buffer. |
184 | | |
185 | | Post-condition: At most FN_REFLEN bytes will have been written to |
186 | | 'to'. If the combined length of 'from' and any expanded elements |
187 | | exceeds FN_REFLEN-1, the result is truncated and likely not what the |
188 | | caller expects. |
189 | | |
190 | | @param to result buffer, FN_REFLEN chars in length; may be == from |
191 | | @param from 'packed' directory name, in whatever format |
192 | | @returns size of the normalized name |
193 | | |
194 | | @details |
195 | | - Ensures that last char is FN_LIBCHAR, unless it is FN_DEVCHAR |
196 | | - Uses cleanup_dirname |
197 | | |
198 | | @note It does *not* expand ~/ (although, see cleanup_dirname). Nor does it do |
199 | | any case folding. All case-insensitive normalization should be done by |
200 | | the caller. |
201 | | */ |
202 | | |
203 | 0 | size_t normalize_dirname(char *to, const char *from) { |
204 | 0 | size_t length; |
205 | 0 | char buff[FN_REFLEN]; |
206 | 0 | DBUG_TRACE; |
207 | | |
208 | | /* |
209 | | Despite the name, this actually converts the name to the system's |
210 | | format (TODO: name this properly). |
211 | | */ |
212 | 0 | (void)intern_filename(buff, from); |
213 | 0 | length = strlen(buff); /* Fix that '/' is last */ |
214 | 0 | if (length && |
215 | | #ifdef FN_DEVCHAR |
216 | | buff[length - 1] != FN_DEVCHAR && |
217 | | #endif |
218 | 0 | buff[length - 1] != FN_LIBCHAR && buff[length - 1] != '/') { |
219 | | /* we need reserve 2 bytes for the trailing slash and the zero */ |
220 | 0 | if (length >= sizeof(buff) - 1) length = sizeof(buff) - 2; |
221 | 0 | buff[length] = FN_LIBCHAR; |
222 | 0 | buff[length + 1] = '\0'; |
223 | 0 | } |
224 | |
|
225 | 0 | length = cleanup_dirname(to, buff); |
226 | |
|
227 | 0 | return length; |
228 | 0 | } |
229 | | |
230 | | /** |
231 | | Fixes a directory name so that can be used by open(). |
232 | | |
233 | | Pre-condition: At least FN_REFLEN bytes can be stored in buffer |
234 | | pointed to by 'to'. 'from' is a '\0'-terminated byte buffer. |
235 | | |
236 | | Post-condition: At most FN_REFLEN bytes will have been written to |
237 | | 'to'. If the combined length of 'from' and any expanded elements |
238 | | exceeds FN_REFLEN-1, the result is truncated and likely not what the |
239 | | caller expects. |
240 | | |
241 | | @param to Result buffer, FN_REFLEN characters. May be == from |
242 | | @param from 'Packed' directory name (may contain ~) |
243 | | |
244 | | @details |
245 | | - Uses normalize_dirname() |
246 | | - Expands ~/... to home_dir/... |
247 | | - Changes a UNIX filename to system filename (replaces / with \ on windows) |
248 | | |
249 | | @returns |
250 | | Length of new directory name (= length of to) |
251 | | */ |
252 | | |
253 | 0 | size_t unpack_dirname(char *to, const char *from) { |
254 | 0 | size_t length, h_length; |
255 | 0 | char buff[FN_REFLEN + 1 + 4], *suffix; |
256 | 0 | DBUG_TRACE; |
257 | |
|
258 | 0 | length = normalize_dirname(buff, from); |
259 | |
|
260 | 0 | if (buff[0] == FN_HOMELIB) { |
261 | 0 | suffix = buff + 1; |
262 | 0 | std::string tilde_expansion = expand_tilde(&suffix); |
263 | 0 | if (!tilde_expansion.empty()) { |
264 | 0 | length -= (size_t)(suffix - buff) - 1; |
265 | 0 | if (length + (h_length = tilde_expansion.length()) <= FN_REFLEN) { |
266 | 0 | if ((h_length > 0) && (tilde_expansion.back() == FN_LIBCHAR)) |
267 | 0 | h_length--; |
268 | 0 | memmove(buff + h_length, suffix, length); |
269 | 0 | memmove(buff, tilde_expansion.c_str(), h_length); |
270 | 0 | } |
271 | 0 | } |
272 | 0 | } |
273 | 0 | return system_filename(to, buff); /* Fix for open */ |
274 | 0 | } /* unpack_dirname */ |
275 | | |
276 | | /** |
277 | | Expand tilde to home or user-directory. |
278 | | Path is reset to point at FN_LIBCHAR after ~xxx |
279 | | @param path pointer to path containing tilde. |
280 | | @return home directory. |
281 | | */ |
282 | | |
283 | 0 | static std::string expand_tilde(char **path) { |
284 | 0 | if (path[0][0] == FN_LIBCHAR) |
285 | 0 | return (home_dir ? std::string{home_dir} |
286 | 0 | : std::string{}); /* ~/ expanded to home */ |
287 | | |
288 | 0 | #ifdef HAVE_GETPWNAM |
289 | 0 | { |
290 | 0 | char *str, save; |
291 | |
|
292 | 0 | if (!(str = strchr(*path, FN_LIBCHAR))) str = strend(*path); |
293 | 0 | save = *str; |
294 | 0 | *str = '\0'; |
295 | 0 | PasswdValue const user_entry = my_getpwnam(*path); |
296 | 0 | *str = save; |
297 | 0 | if (!user_entry.IsVoid()) { |
298 | 0 | *path = str; |
299 | 0 | return user_entry.pw_dir; |
300 | 0 | } |
301 | 0 | } |
302 | 0 | #endif |
303 | 0 | return std::string{}; |
304 | 0 | } |
305 | | |
306 | | /** |
307 | | Fix filename so it can be used by open, create |
308 | | |
309 | | Pre-condition: At least FN_REFLEN bytes can be stored in buffer |
310 | | pointed to by 'to'. 'from' is a '\0'-terminated byte buffer. |
311 | | |
312 | | Post-condition: At most FN_REFLEN bytes will have been written to |
313 | | 'to'. If the combined length of 'from' and any expanded elements |
314 | | exceeds FN_REFLEN-1, the result is truncated and likely not what the |
315 | | caller expects. |
316 | | |
317 | | @note to may be == from |
318 | | @note ~ will only be expanded if total length < FN_REFLEN |
319 | | |
320 | | @param to Store result here. Must be at least of size FN_REFLEN. |
321 | | @param from Filename in unix format (with ~) |
322 | | @return # length of to |
323 | | */ |
324 | | |
325 | 0 | size_t unpack_filename(char *to, const char *from) { |
326 | 0 | size_t length, n_length, buff_length; |
327 | 0 | char buff[FN_REFLEN]; |
328 | 0 | DBUG_TRACE; |
329 | |
|
330 | 0 | length = dirname_part(buff, from, &buff_length); /* copy & convert dirname */ |
331 | 0 | n_length = unpack_dirname(buff, buff); |
332 | 0 | if (n_length + strlen(from + length) < FN_REFLEN) { |
333 | 0 | (void)my_stpcpy(buff + n_length, from + length); |
334 | 0 | length = system_filename(to, buff); /* Fix to usably filename */ |
335 | 0 | } else |
336 | 0 | length = system_filename(to, from); /* Fix to usably filename */ |
337 | 0 | return length; |
338 | 0 | } /* unpack_filename */ |
339 | | |
340 | | /** |
341 | | Convert filename (unix standard) to system standard |
342 | | Used before system command's like open(), create() |
343 | | |
344 | | Pre-condition: At least FN_REFLEN bytes can be stored in buffer |
345 | | pointed to by 'to'. 'from' is a '\0'-terminated byte buffer. |
346 | | |
347 | | Post-condition: At most FN_REFLEN bytes will have been written to |
348 | | 'to'. If the combined length of 'from' and any expanded elements |
349 | | exceeds FN_REFLEN-1, the result is truncated and likely not what the |
350 | | caller expects. |
351 | | |
352 | | @param to destination buffer. |
353 | | @param from source string. |
354 | | @return used length of to |
355 | | */ |
356 | | |
357 | 0 | size_t system_filename(char *to, const char *from) { |
358 | 0 | return (size_t)(strmake(to, from, FN_REFLEN - 1) - to); |
359 | 0 | } |
360 | | |
361 | | /** |
362 | | Fix a filename to intern (UNIX format). |
363 | | |
364 | | Pre-condition: At least FN_REFLEN bytes can be stored in buffer |
365 | | pointed to by 'to'. 'from' is a '\0'-terminated byte buffer. |
366 | | |
367 | | Post-condition: At most FN_REFLEN bytes will have been written to |
368 | | 'to'. If the combined length of 'from' and any expanded elements |
369 | | exceeds FN_REFLEN-1, the result is truncated and likely not what the |
370 | | caller expects. |
371 | | |
372 | | @param to destination buffer. |
373 | | @param from source string. |
374 | | @return to (destination buffer). |
375 | | */ |
376 | | |
377 | 2 | char *intern_filename(char *to, const char *from) { |
378 | 2 | size_t length, to_length; |
379 | 2 | char buff[FN_REFLEN]; |
380 | | |
381 | 2 | if (from == to) { /* Dirname may destroy from */ |
382 | 0 | (void)my_stpnmov(buff, from, FN_REFLEN); |
383 | 0 | buff[FN_REFLEN - 1] = '\0'; // make sure buff is valid c-string |
384 | 0 | from = buff; |
385 | 0 | } |
386 | 2 | length = dirname_part(to, from, &to_length); /* Copy dirname & fix chars */ |
387 | 2 | (void)my_stpnmov(to + to_length, from + length, FN_REFLEN - 1 - to_length); |
388 | 2 | to[FN_REFLEN - 1] = '\0'; // make sure to is valid c-string |
389 | 2 | return (to); |
390 | 2 | } /* intern_filename */ |