1 /* giowin32-private.c - private glib-gio functions for W32 GAppInfo
3 * Copyright 2019 Руслан Ижбулатов
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this library; if not, see <http://www.gnu.org/licenses/>.
21 g_utf16_len (const gunichar2 *str)
25 for (result = 0; str[0] != 0; str++, result++)
32 g_wcsdup (const gunichar2 *str, gssize str_len)
36 g_return_val_if_fail (str != NULL, NULL);
39 str_len = g_utf16_len (str);
41 g_assert (str_len <= G_MAXSIZE / sizeof (gunichar2) - 1);
42 str_size = (str_len + 1) * sizeof (gunichar2);
44 return g_memdup (str, str_size);
47 static const gunichar2 *
48 g_utf16_wchr (const gunichar2 *str, const wchar_t wchr)
50 for (; str != NULL && str[0] != 0; str++)
51 if ((wchar_t) str[0] == wchr)
58 g_utf16_to_utf8_and_fold (const gunichar2 *str,
61 gchar **str_u8_folded)
65 u8 = g_utf16_to_utf8 (str, length, NULL, NULL, NULL);
70 folded = g_utf8_casefold (u8, -1);
73 *str_u8 = g_steal_pointer (&u8);
78 *str_u8_folded = g_steal_pointer (&folded);
85 /* Finds the last directory separator in @filename,
86 * returns a pointer to the position after that separator.
87 * If the string ends with a separator, returned value
88 * will be pointing at the NUL terminator.
89 * If the string does not contain separators, returns the
92 static const gunichar2 *
93 g_utf16_find_basename (const gunichar2 *filename,
96 const gunichar2 *result;
99 len = g_utf16_len (filename);
103 result = &filename[len - 1];
105 while (result > filename)
107 if ((wchar_t) result[0] == L'/' ||
108 (wchar_t) result[0] == L'\\')
120 /* Finds the last directory separator in @filename,
121 * returns a pointer to the position after that separator.
122 * If the string ends with a separator, returned value
123 * will be pointing at the NUL terminator.
124 * If the string does not contain separators, returns the
128 g_utf8_find_basename (const gchar *filename,
134 len = strlen (filename);
138 result = &filename[len - 1];
140 while (result > filename)
142 if (result[0] == '/' ||
156 * Parses @commandline, figuring out what the filename being invoked
157 * is. All returned strings are pointers into @commandline.
158 * @commandline must be a valid UTF-16 string and not be NULL.
159 * @after_executable is the first character after executable
160 * (usually a space, but not always).
161 * If @comma_separator is TRUE, accepts ',' as a separator between
162 * the filename and the following argument.
165 _g_win32_parse_filename (const gunichar2 *commandline,
166 gboolean comma_separator,
167 const gunichar2 **executable_start,
168 gssize *executable_len,
169 const gunichar2 **executable_basename,
170 const gunichar2 **after_executable)
173 const gunichar2 *first_argument;
179 while ((wchar_t) commandline[0] == L' ')
185 first_argument = NULL;
187 if ((wchar_t) commandline[0] == L'"')
193 len = g_utf16_len (commandline);
196 while (p < &commandline[len])
198 switch ((wchar_t) p[0])
203 first_argument = p + 1;
204 /* Note: this is a valid commandline for opening "c:/file.txt":
205 * > "notepad"c:/file.txt
207 p = &commandline[len];
217 p = &commandline[len];
224 if (!quoted && comma_separator)
227 p = &commandline[len];
241 first_argument = &commandline[len];
243 if (executable_start)
244 *executable_start = commandline;
247 *executable_len = execlen;
249 if (executable_basename)
250 *executable_basename = g_utf16_find_basename (commandline, execlen);
252 if (after_executable)
253 *after_executable = first_argument;
256 /* Make sure @commandline is a valid UTF-16 string before
257 * calling this function!
258 * follow_class_chain_to_handler() does perform such validation.
261 _g_win32_extract_executable (const gunichar2 *commandline,
263 gchar **ex_basename_out,
264 gchar **ex_folded_out,
265 gchar **ex_folded_basename_out,
266 gchar **dll_function_out)
270 const gunichar2 *first_argument;
271 const gunichar2 *executable;
272 const gunichar2 *executable_basename;
277 _g_win32_parse_filename (commandline, FALSE, &executable, &execlen, &executable_basename, &first_argument);
279 commandline = executable;
281 while ((wchar_t) first_argument[0] == L' ')
284 folded = g_utf16_to_utf8_and_fold (executable, (gssize) execlen, &ex, &ex_folded);
285 /* This should never fail as @executable has to be valid UTF-16. */
288 if (dll_function_out)
289 *dll_function_out = NULL;
291 /* See if the executable basename is "rundll32.exe". If so, then
292 * parse the rest of the commandline as r'"?path-to-dll"?[ ]*,*[ ]*dll_function_to_invoke'
294 /* Using just "rundll32.exe", without an absolute path, seems
295 * very exploitable, but MS does that sometimes, so we have
298 if ((g_strcmp0 (ex_folded, "rundll32.exe") == 0 ||
299 g_str_has_suffix (ex_folded, "\\rundll32.exe") ||
300 g_str_has_suffix (ex_folded, "/rundll32.exe")) &&
301 first_argument[0] != 0 &&
302 dll_function_out != NULL)
305 * > rundll32.exe c:\some,file,with,commas.dll,some_function
306 * is treated by rundll32 as:
308 * function=file,with,commas.dll,some_function
309 * unless the dll name is surrounded by double quotation marks:
310 * > rundll32.exe "c:\some,file,with,commas.dll",some_function
311 * in which case everything works normally.
312 * Also, quoting only works if it surrounds the file name, i.e:
313 * > rundll32.exe "c:\some,file"",with,commas.dll",some_function
315 * Also, comma is optional when filename is quoted or when function
316 * name is separated from the filename by space(s):
317 * > rundll32.exe "c:\some,file,with,commas.dll"some_function
319 * > rundll32.exe c:\some_dll_without_commas_or_spaces.dll some_function
321 * Also, any number of commas is accepted:
322 * > rundll32.exe c:\some_dll_without_commas_or_spaces.dll , , ,,, , some_function
324 * And the ultimate example is:
325 * > "rundll32.exe""c:\some,file,with,commas.dll"some_function
327 * Good job, Microsoft!
329 const gunichar2 *filename_end = NULL;
330 gssize filename_len = 0;
331 gssize function_len = 0;
332 const gunichar2 *dllpart;
336 if ((wchar_t) first_argument[0] == L'"')
339 _g_win32_parse_filename (first_argument, TRUE, &dllpart, &filename_len, NULL, &filename_end);
341 if (filename_end[0] != 0 && filename_len > 0)
343 const gunichar2 *function_begin = filename_end;
345 while ((wchar_t) function_begin[0] == L',' || (wchar_t) function_begin[0] == L' ')
348 if (function_begin[0] != 0)
351 gchar *dllpart_utf8_folded;
352 gchar *function_utf8;
354 const gunichar2 *space = g_utf16_wchr (function_begin, L' ');
357 function_len = space - function_begin;
359 function_len = g_utf16_len (function_begin);
364 folded = g_utf16_to_utf8_and_fold (first_argument, filename_len, &dllpart_utf8, &dllpart_utf8_folded);
367 function_utf8 = g_utf16_to_utf8 (function_begin, function_len, NULL, NULL, NULL);
369 /* We only take this branch when dll_function_out is not NULL */
370 *dll_function_out = g_steal_pointer (&function_utf8);
372 g_free (function_utf8);
375 * Free our previous output candidate (rundll32) and replace it with the DLL path,
376 * then proceed forward as if nothing has changed.
382 ex_folded = dllpart_utf8_folded;
390 *ex_basename_out = (gchar *) g_utf8_find_basename (ex, -1);
392 *ex_out = g_steal_pointer (&ex);
399 if (ex_folded_basename_out)
400 *ex_folded_basename_out = (gchar *) g_utf8_find_basename (ex_folded, -1);
402 *ex_folded_out = g_steal_pointer (&ex_folded);
409 * rundll32 accepts many different commandlines. Among them is this:
410 * > rundll32.exe "c:/program files/foo/bar.dll",,, , ,,,, , function_name %1
411 * rundll32 just reads the first argument as a potentially quoted
412 * filename until the quotation ends (if quoted) or until a comma,
413 * or until a space. Then ignores all subsequent spaces (if any) and commas (if any;
414 * at least one comma is mandatory only if the filename is not quoted),
415 * and then interprets the rest of the commandline (until a space or a NUL-byte)
416 * as a name of a function.
417 * When GLib tries to run a program, it attempts to correctly re-quote the arguments,
418 * turning the first argument into "c:/program files/foo/bar.dll,,,".
419 * This breaks rundll32 parsing logic.
420 * Try to work around this by ensuring that the syntax is like this:
421 * > rundll32.exe "c:/program files/foo/bar.dll" function_name
422 * This syntax is valid for rundll32 *and* GLib spawn routines won't break it.
424 * @commandline must have at least 2 arguments, and the second argument
425 * must contain a (possibly quoted) filename, followed by a space or
426 * a comma. This can be checked for with an extract_executable() call -
427 * it should return a non-null dll_function.
430 _g_win32_fixup_broken_microsoft_rundll_commandline (gunichar2 *commandline)
432 const gunichar2 *first_argument;
433 gunichar2 *after_first_argument;
435 _g_win32_parse_filename (commandline, FALSE, NULL, NULL, NULL, &first_argument);
437 while ((wchar_t) first_argument[0] == L' ')
440 _g_win32_parse_filename (first_argument, TRUE, NULL, NULL, NULL, (const gunichar2 **) &after_first_argument);
442 if ((wchar_t) after_first_argument[0] == L',')
443 after_first_argument[0] = 0x0020;
444 /* Else everything is ok (first char after filename is ' ' or the first char
445 * of the function name - either way this will work).