LCOV - code coverage report
Current view: top level - glib/gio - giowin32-private.c (source / functions) Hit Total Coverage
Test: unnamed Lines: 163 163 100.0 %
Date: 2024-04-16 05:15:53 Functions: 9 9 100.0 %
Branches: 111 118 94.1 %

           Branch data     Line data    Source code
       1                 :            : /* giowin32-private.c - private glib-gio functions for W32 GAppInfo
       2                 :            :  *
       3                 :            :  * Copyright 2019 Руслан Ижбулатов
       4                 :            :  *
       5                 :            :  * SPDX-License-Identifier: LGPL-2.1-or-later
       6                 :            :  *
       7                 :            :  * This library is free software; you can redistribute it and/or
       8                 :            :  * modify it under the terms of the GNU Lesser General Public
       9                 :            :  * License as published by the Free Software Foundation; either
      10                 :            :  * version 2.1 of the License, or (at your option) any later version.
      11                 :            :  *
      12                 :            :  * This library is distributed in the hope that it will be useful,
      13                 :            :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      14                 :            :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      15                 :            :  * Lesser General Public License for more details.
      16                 :            :  *
      17                 :            :  * You should have received a copy of the GNU Lesser General Public License
      18                 :            :  * along with this library; if not, see <http://www.gnu.org/licenses/>.
      19                 :            :  */
      20                 :            : 
      21                 :            : 
      22                 :            : static gsize
      23                 :        171 : g_utf16_len (const gunichar2 *str)
      24                 :            : {
      25                 :            :   gsize result;
      26                 :            : 
      27         [ +  + ]:       6914 :   for (result = 0; str[0] != 0; str++, result++)
      28                 :            :     ;
      29                 :            : 
      30                 :        171 :   return result;
      31                 :            : }
      32                 :            : 
      33                 :            : static gunichar2 *
      34                 :          6 : g_wcsdup (const gunichar2 *str, gssize str_len)
      35                 :            : {
      36                 :            :   gsize str_len_unsigned;
      37                 :            :   gsize str_size;
      38                 :            : 
      39                 :          6 :   g_return_val_if_fail (str != NULL, NULL);
      40                 :            : 
      41         [ +  + ]:          6 :   if (str_len < 0)
      42                 :          3 :     str_len_unsigned = g_utf16_len (str);
      43                 :            :   else
      44                 :          3 :     str_len_unsigned = (gsize) str_len;
      45                 :            : 
      46                 :          6 :   g_assert (str_len_unsigned <= G_MAXSIZE / sizeof (gunichar2) - 1);
      47                 :          6 :   str_size = (str_len_unsigned + 1) * sizeof (gunichar2);
      48                 :            : 
      49                 :          6 :   return g_memdup2 (str, str_size);
      50                 :            : }
      51                 :            : 
      52                 :            : static const gunichar2 *
      53                 :         17 : g_utf16_wchr (const gunichar2 *str, const wchar_t wchr)
      54                 :            : {
      55   [ +  -  +  + ]:        174 :   for (; str != NULL && str[0] != 0; str++)
      56         [ +  + ]:        163 :     if ((wchar_t) str[0] == wchr)
      57                 :          6 :       return str;
      58                 :            : 
      59                 :         11 :   return NULL;
      60                 :            : }
      61                 :            : 
      62                 :            : static gboolean
      63                 :         98 : g_utf16_to_utf8_and_fold (const gunichar2  *str,
      64                 :            :                           gssize            length,
      65                 :            :                           gchar           **str_u8,
      66                 :            :                           gchar           **str_u8_folded)
      67                 :            : {
      68                 :            :   gchar *u8;
      69                 :            :   gchar *folded;
      70                 :         98 :   u8 = g_utf16_to_utf8 (str, length, NULL, NULL, NULL);
      71                 :            : 
      72         [ +  + ]:         98 :   if (u8 == NULL)
      73                 :          3 :     return FALSE;
      74                 :            : 
      75                 :         95 :   folded = g_utf8_casefold (u8, -1);
      76                 :            : 
      77         [ +  + ]:         95 :   if (str_u8)
      78                 :         93 :     *str_u8 = g_steal_pointer (&u8);
      79                 :            : 
      80                 :         95 :   g_free (u8);
      81                 :            : 
      82         [ +  + ]:         95 :   if (str_u8_folded)
      83                 :         91 :     *str_u8_folded = g_steal_pointer (&folded);
      84                 :            : 
      85                 :         95 :   g_free (folded);
      86                 :            : 
      87                 :         95 :   return TRUE;
      88                 :            : }
      89                 :            : 
      90                 :            : /* Finds the last directory separator in @filename,
      91                 :            :  * returns a pointer to the position after that separator.
      92                 :            :  * If the string ends with a separator, returned value
      93                 :            :  * will be pointing at the NUL terminator.
      94                 :            :  * If the string does not contain separators, returns the
      95                 :            :  * string itself.
      96                 :            :  */
      97                 :            : static const gunichar2 *
      98                 :         75 : g_utf16_find_basename (const gunichar2 *filename,
      99                 :            :                        gssize           len)
     100                 :            : {
     101                 :            :   const gunichar2 *result;
     102                 :            : 
     103         [ +  + ]:         75 :   if (len < 0)
     104                 :          3 :     len = g_utf16_len (filename);
     105         [ +  + ]:         75 :   if (len == 0)
     106                 :          1 :     return filename;
     107                 :            : 
     108                 :         74 :   result = &filename[len - 1];
     109                 :            : 
     110         [ +  + ]:        891 :   while (result > filename)
     111                 :            :     {
     112         [ +  + ]:        835 :       if ((wchar_t) result[0] == L'/' ||
     113         [ +  + ]:        829 :           (wchar_t) result[0] == L'\\')
     114                 :            :         {
     115                 :         18 :           result += 1;
     116                 :         18 :           break;
     117                 :            :         }
     118                 :            : 
     119                 :        817 :       result -= 1;
     120                 :            :     }
     121                 :            : 
     122                 :         74 :   return result;
     123                 :            : }
     124                 :            : 
     125                 :            : /* Finds the last directory separator in @filename,
     126                 :            :  * returns a pointer to the position after that separator.
     127                 :            :  * If the string ends with a separator, returned value
     128                 :            :  * will be pointing at the NUL terminator.
     129                 :            :  * If the string does not contain separators, returns the
     130                 :            :  * string itself.
     131                 :            :  */
     132                 :            : static const gchar *
     133                 :         50 : g_utf8_find_basename (const gchar *filename,
     134                 :            :                       gssize       len)
     135                 :            : {
     136                 :            :   const gchar *result;
     137                 :            : 
     138         [ +  + ]:         50 :   if (len < 0)
     139                 :         48 :     len = strlen (filename);
     140         [ +  + ]:         50 :   if (len == 0)
     141                 :          1 :     return filename;
     142                 :            : 
     143                 :         49 :   result = &filename[len - 1];
     144                 :            : 
     145         [ +  + ]:        593 :   while (result > filename)
     146                 :            :     {
     147         [ +  + ]:        556 :       if (result[0] == '/' ||
     148         [ +  + ]:        552 :           result[0] == '\\')
     149                 :            :         {
     150                 :         12 :           result += 1;
     151                 :         12 :           break;
     152                 :            :         }
     153                 :            : 
     154                 :        544 :       result -= 1;
     155                 :            :     }
     156                 :            : 
     157                 :         49 :   return result;
     158                 :            : }
     159                 :            : 
     160                 :            : /**
     161                 :            :  * Parses @commandline, figuring out what the filename being invoked
     162                 :            :  * is. All returned strings are pointers into @commandline.
     163                 :            :  * @commandline must be a valid UTF-16 string and not be NULL.
     164                 :            :  * @after_executable is the first character after executable
     165                 :            :  * (usually a space, but not always).
     166                 :            :  * If @comma_separator is TRUE, accepts ',' as a separator between
     167                 :            :  * the filename and the following argument.
     168                 :            :  */
     169                 :            : static void
     170                 :        151 : _g_win32_parse_filename (const gunichar2  *commandline,
     171                 :            :                          gboolean          comma_separator,
     172                 :            :                          const gunichar2 **executable_start,
     173                 :            :                          gssize           *executable_len,
     174                 :            :                          const gunichar2 **executable_basename,
     175                 :            :                          const gunichar2 **after_executable)
     176                 :            : {
     177                 :            :   const gunichar2 *p;
     178                 :            :   const gunichar2 *first_argument;
     179                 :            :   gboolean quoted;
     180                 :            :   gssize len;
     181                 :            :   gssize execlen;
     182                 :            :   gboolean found;
     183                 :            : 
     184         [ +  + ]:        181 :   while ((wchar_t) commandline[0] == L' ')
     185                 :         30 :     commandline++;
     186                 :            : 
     187                 :        151 :   quoted = FALSE;
     188                 :        151 :   execlen = 0;
     189                 :        151 :   found = FALSE;
     190                 :        151 :   first_argument = NULL;
     191                 :            : 
     192         [ +  + ]:        151 :   if ((wchar_t) commandline[0] == L'"')
     193                 :            :     {
     194                 :         74 :       quoted = TRUE;
     195                 :         74 :       commandline += 1;
     196                 :            :     }
     197                 :            : 
     198                 :        151 :   len = g_utf16_len (commandline);
     199                 :        151 :   p = commandline;
     200                 :            : 
     201         [ +  + ]:       3090 :   while (p < &commandline[len])
     202                 :            :     {
     203   [ +  +  +  + ]:       2939 :       switch ((wchar_t) p[0])
     204                 :            :         {
     205                 :         78 :         case L'"':
     206         [ +  + ]:         78 :           if (quoted)
     207                 :            :             {
     208                 :         74 :               first_argument = p + 1;
     209                 :            :               /* Note: this is a valid commandline for opening "c:/file.txt":
     210                 :            :                * > "notepad"c:/file.txt
     211                 :            :                */
     212                 :         74 :               p = &commandline[len];
     213                 :         74 :               found = TRUE;
     214                 :            :             }
     215                 :            :           else
     216                 :          4 :             execlen += 1;
     217                 :         78 :           break;
     218                 :        121 :         case L' ':
     219         [ +  + ]:        121 :           if (!quoted)
     220                 :            :             {
     221                 :         59 :               first_argument = p;
     222                 :         59 :               p = &commandline[len];
     223                 :         59 :               found = TRUE;
     224                 :            :             }
     225                 :            :           else
     226                 :         62 :             execlen += 1;
     227                 :        121 :           break;
     228                 :         21 :         case L',':
     229   [ +  +  +  + ]:         21 :           if (!quoted && comma_separator)
     230                 :            :             {
     231                 :         13 :               first_argument = p;
     232                 :         13 :               p = &commandline[len];
     233                 :         13 :               found = TRUE;
     234                 :            :             }
     235                 :            :           else
     236                 :          8 :             execlen += 1;
     237                 :         21 :           break;
     238                 :       2719 :         default:
     239                 :       2719 :           execlen += 1;
     240                 :       2719 :           break;
     241                 :            :         }
     242                 :       2939 :       p += 1;
     243                 :            :     }
     244                 :            : 
     245         [ +  + ]:        151 :   if (!found)
     246                 :          5 :     first_argument = &commandline[len];
     247                 :            : 
     248         [ +  + ]:        151 :   if (executable_start)
     249                 :         93 :     *executable_start = commandline;
     250                 :            : 
     251         [ +  + ]:        151 :   if (executable_len)
     252                 :         93 :     *executable_len = execlen;
     253                 :            : 
     254         [ +  + ]:        151 :   if (executable_basename)
     255                 :         72 :     *executable_basename = g_utf16_find_basename (commandline, execlen);
     256                 :            : 
     257         [ +  + ]:        151 :   if (after_executable)
     258                 :        127 :     *after_executable = first_argument;
     259                 :        151 : }
     260                 :            : 
     261                 :            : /* Make sure @commandline is a valid UTF-16 string before
     262                 :            :  * calling this function!
     263                 :            :  * follow_class_chain_to_handler() does perform such validation.
     264                 :            :  */
     265                 :            : static void
     266                 :         72 : _g_win32_extract_executable (const gunichar2  *commandline,
     267                 :            :                              gchar           **ex_out,
     268                 :            :                              gchar           **ex_basename_out,
     269                 :            :                              gchar           **ex_folded_out,
     270                 :            :                              gchar           **ex_folded_basename_out,
     271                 :            :                              gchar           **dll_function_out)
     272                 :            : {
     273                 :            :   gchar *ex;
     274                 :            :   gchar *ex_folded;
     275                 :            :   const gunichar2 *first_argument;
     276                 :            :   const gunichar2 *executable;
     277                 :            :   const gunichar2 *executable_basename;
     278                 :            :   gboolean quoted;
     279                 :            :   gboolean folded;
     280                 :            :   gssize execlen;
     281                 :            : 
     282                 :         72 :   _g_win32_parse_filename (commandline, FALSE, &executable, &execlen, &executable_basename, &first_argument);
     283                 :            : 
     284                 :         72 :   commandline = executable;
     285                 :            : 
     286         [ +  + ]:        144 :   while ((wchar_t) first_argument[0] == L' ')
     287                 :         72 :     first_argument++;
     288                 :            : 
     289                 :         72 :   folded = g_utf16_to_utf8_and_fold (executable, (gssize) execlen, &ex, &ex_folded);
     290                 :            :   /* This should never fail as @executable has to be valid UTF-16. */
     291                 :         72 :   g_assert (folded);
     292                 :            : 
     293         [ +  + ]:         72 :   if (dll_function_out)
     294                 :         24 :     *dll_function_out = NULL;
     295                 :            : 
     296                 :            :   /* See if the executable basename is "rundll32.exe". If so, then
     297                 :            :    * parse the rest of the commandline as r'"?path-to-dll"?[ ]*,*[ ]*dll_function_to_invoke'
     298                 :            :    */
     299                 :            :   /* Using just "rundll32.exe", without an absolute path, seems
     300                 :            :    * very exploitable, but MS does that sometimes, so we have
     301                 :            :    * to accept that.
     302                 :            :    */
     303         [ +  + ]:         72 :   if ((g_strcmp0 (ex_folded, "rundll32.exe") == 0 ||
     304   [ +  -  -  +  :         24 :        g_str_has_suffix (ex_folded, "\\rundll32.exe") ||
             +  -  +  + ]
     305   [ +  -  -  +  :         12 :        g_str_has_suffix (ex_folded, "/rundll32.exe")) &&
             +  -  +  + ]
     306   [ +  +  +  + ]:         66 :       first_argument[0] != 0 &&
     307                 :            :       dll_function_out != NULL)
     308                 :            :     {
     309                 :            :       /* Corner cases:
     310                 :            :        * > rundll32.exe c:\some,file,with,commas.dll,some_function
     311                 :            :        * is treated by rundll32 as:
     312                 :            :        * dll=c:\some
     313                 :            :        * function=file,with,commas.dll,some_function
     314                 :            :        * unless the dll name is surrounded by double quotation marks:
     315                 :            :        * > rundll32.exe "c:\some,file,with,commas.dll",some_function
     316                 :            :        * in which case everything works normally.
     317                 :            :        * Also, quoting only works if it surrounds the file name, i.e:
     318                 :            :        * > rundll32.exe "c:\some,file"",with,commas.dll",some_function
     319                 :            :        * will not work.
     320                 :            :        * Also, comma is optional when filename is quoted or when function
     321                 :            :        * name is separated from the filename by space(s):
     322                 :            :        * > rundll32.exe "c:\some,file,with,commas.dll"some_function
     323                 :            :        * will work,
     324                 :            :        * > rundll32.exe c:\some_dll_without_commas_or_spaces.dll some_function
     325                 :            :        * will work too.
     326                 :            :        * Also, any number of commas is accepted:
     327                 :            :        * > rundll32.exe c:\some_dll_without_commas_or_spaces.dll , , ,,, , some_function
     328                 :            :        * works just fine.
     329                 :            :        * And the ultimate example is:
     330                 :            :        * > "rundll32.exe""c:\some,file,with,commas.dll"some_function
     331                 :            :        * and it also works.
     332                 :            :        * Good job, Microsoft!
     333                 :            :        */
     334                 :         21 :       const gunichar2 *filename_end = NULL;
     335                 :         21 :       gssize filename_len = 0;
     336                 :         21 :       gssize function_len = 0;
     337                 :            :       const gunichar2 *dllpart;
     338                 :            : 
     339                 :         21 :       quoted = FALSE;
     340                 :            : 
     341         [ +  + ]:         21 :       if ((wchar_t) first_argument[0] == L'"')
     342                 :         10 :         quoted = TRUE;
     343                 :            : 
     344                 :         21 :       _g_win32_parse_filename (first_argument, TRUE, &dllpart, &filename_len, NULL, &filename_end);
     345                 :            : 
     346   [ +  +  +  + ]:         21 :       if (filename_end[0] != 0 && filename_len > 0)
     347                 :            :         {
     348                 :         18 :           const gunichar2 *function_begin = filename_end;
     349                 :            : 
     350   [ +  +  +  + ]:         60 :           while ((wchar_t) function_begin[0] == L',' || (wchar_t) function_begin[0] == L' ')
     351                 :         42 :             function_begin += 1;
     352                 :            : 
     353         [ +  + ]:         18 :           if (function_begin[0] != 0)
     354                 :            :             {
     355                 :            :               gchar *dllpart_utf8;
     356                 :            :               gchar *dllpart_utf8_folded;
     357                 :            :               gchar *function_utf8;
     358                 :         17 :               const gunichar2 *space = g_utf16_wchr (function_begin, L' ');
     359                 :            : 
     360         [ +  + ]:         17 :               if (space)
     361                 :          6 :                 function_len = space - function_begin;
     362                 :            :               else
     363                 :         11 :                 function_len = g_utf16_len (function_begin);
     364                 :            : 
     365         [ +  + ]:         17 :               if (quoted)
     366                 :         10 :                 first_argument += 1;
     367                 :            : 
     368                 :         17 :               folded = g_utf16_to_utf8_and_fold (first_argument, filename_len, &dllpart_utf8, &dllpart_utf8_folded);
     369                 :         17 :               g_assert (folded);
     370                 :            : 
     371                 :         17 :               function_utf8 = g_utf16_to_utf8 (function_begin, function_len, NULL, NULL, NULL);
     372                 :            : 
     373                 :            :               /* We only take this branch when dll_function_out is not NULL */
     374                 :         17 :               *dll_function_out = g_steal_pointer (&function_utf8);
     375                 :            : 
     376                 :         17 :               g_free (function_utf8);
     377                 :            : 
     378                 :            :               /*
     379                 :            :                * Free our previous output candidate (rundll32) and replace it with the DLL path,
     380                 :            :                * then proceed forward as if nothing has changed.
     381                 :            :                */
     382                 :         17 :               g_free (ex);
     383                 :         17 :               g_free (ex_folded);
     384                 :            : 
     385                 :         17 :               ex = dllpart_utf8;
     386                 :         17 :               ex_folded = dllpart_utf8_folded;
     387                 :            :             }
     388                 :            :         }
     389                 :            :     }
     390                 :            : 
     391         [ +  + ]:         72 :   if (ex_out)
     392                 :            :     {
     393         [ +  + ]:         48 :       if (ex_basename_out)
     394                 :         24 :         *ex_basename_out = (gchar *) g_utf8_find_basename (ex, -1);
     395                 :            : 
     396                 :         48 :       *ex_out = g_steal_pointer (&ex);
     397                 :            :     }
     398                 :            : 
     399                 :         72 :   g_free (ex);
     400                 :            : 
     401         [ +  + ]:         72 :   if (ex_folded_out)
     402                 :            :     {
     403         [ +  + ]:         48 :       if (ex_folded_basename_out)
     404                 :         24 :         *ex_folded_basename_out = (gchar *) g_utf8_find_basename (ex_folded, -1);
     405                 :            : 
     406                 :         48 :       *ex_folded_out = g_steal_pointer (&ex_folded);
     407                 :            :     }
     408                 :            : 
     409                 :         72 :   g_free (ex_folded);
     410                 :         72 : }
     411                 :            : 
     412                 :            : /**
     413                 :            :  * rundll32 accepts many different commandlines. Among them is this:
     414                 :            :  * > rundll32.exe "c:/program files/foo/bar.dll",,, , ,,,, , function_name %1
     415                 :            :  * rundll32 just reads the first argument as a potentially quoted
     416                 :            :  * filename until the quotation ends (if quoted) or until a comma,
     417                 :            :  * or until a space. Then ignores all subsequent spaces (if any) and commas (if any;
     418                 :            :  * at least one comma is mandatory only if the filename is not quoted),
     419                 :            :  * and then interprets the rest of the commandline (until a space or a NUL-byte)
     420                 :            :  * as a name of a function.
     421                 :            :  * When GLib tries to run a program, it attempts to correctly re-quote the arguments,
     422                 :            :  * turning the first argument into "c:/program files/foo/bar.dll,,,".
     423                 :            :  * This breaks rundll32 parsing logic.
     424                 :            :  * Try to work around this by ensuring that the syntax is like this:
     425                 :            :  * > rundll32.exe "c:/program files/foo/bar.dll" function_name
     426                 :            :  * This syntax is valid for rundll32 *and* GLib spawn routines won't break it.
     427                 :            :  *
     428                 :            :  * @commandline must have at least 2 arguments, and the second argument
     429                 :            :  * must contain a (possibly quoted) filename, followed by a space or
     430                 :            :  * a comma. This can be checked for with an extract_executable() call -
     431                 :            :  * it should return a non-null dll_function.
     432                 :            :  */
     433                 :            : static void
     434                 :         17 : _g_win32_fixup_broken_microsoft_rundll_commandline (gunichar2 *commandline)
     435                 :            : {
     436                 :            :   const gunichar2 *first_argument;
     437                 :            :   gunichar2 *after_first_argument;
     438                 :            : 
     439                 :         17 :   _g_win32_parse_filename (commandline, FALSE, NULL, NULL, NULL, &first_argument);
     440                 :            : 
     441         [ +  + ]:         33 :   while ((wchar_t) first_argument[0] == L' ')
     442                 :         16 :     first_argument++;
     443                 :            : 
     444                 :         17 :   _g_win32_parse_filename (first_argument, TRUE, NULL, NULL, NULL, (const gunichar2 **) &after_first_argument);
     445                 :            : 
     446         [ +  + ]:         17 :   if ((wchar_t) after_first_argument[0] == L',')
     447                 :         13 :     after_first_argument[0] = 0x0020;
     448                 :            :   /* Else everything is ok (first char after filename is ' ' or the first char
     449                 :            :    * of the function name - either way this will work).
     450                 :            :    */
     451                 :         17 : }

Generated by: LCOV version 1.14