1 /* Copyright (c) 2000, 2021, 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 also distributed 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 included with MySQL.
13 
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License, version 2.0, for more details.
18 
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software
21    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */
22 
23 
24 #include "mysys_priv.h"
25 #include "my_sys.h"
26 
27 #ifdef _WIN32
28 
29 
30 /* Windows console handling */
31 
32 /*
33   TODO : Find a relationship between the following
34          two macros and get rid of one.
35 */
36 
37 /* Maximum line length on Windows console */
38 #define MAX_CONSOLE_LINE_SIZE 65535
39 
40 /*
41   Maximum number of characters that can be entered
42   on single line in the console (including \r\n).
43 */
44 #define MAX_NUM_OF_CHARS_TO_READ 24530
45 
46 /**
47   Determine if a file is a windows console
48 
49   @param file Input stream
50 
51   @return
52   @retval  0 if file is not Windows console
53   @retval  1 if file is Windows console
54 */
55 my_bool
my_win_is_console(FILE * file)56 my_win_is_console(FILE *file)
57 {
58   DWORD mode;
59   if (GetConsoleMode((HANDLE) _get_osfhandle(_fileno(file)), &mode))
60     return 1;
61   return 0;
62 }
63 
64 
65 /**
66   Read line from Windows console using Unicode API
67   and translate input to session character set.
68   Note, as Windows API breaks supplementary characters
69   into two wchar_t pieces, we cannot read and convert individual
70   wchar_t values separately. So let's use a buffer for
71   Unicode console input, and then convert it to "cs" in a single shot.
72   String is terminated with '\0' character.
73 
74   @param cs          [IN]  Character string to convert to.
75   @param mbbuf       [OUT] Write input data here.
76   @param mbbufsize   [IN]  Number of bytes available in mbbuf.
77   @param nread       [OUT] Number of bytes read.
78 
79   @retval           Pointer to mbbuf, or NULL on I/0 error.
80 */
81 char *
my_win_console_readline(const CHARSET_INFO * cs,char * mbbuf,size_t mbbufsize,size_t * nread)82 my_win_console_readline(const CHARSET_INFO *cs, char *mbbuf, size_t mbbufsize,
83                         size_t *nread)
84 {
85   uint dummy_errors;
86   static wchar_t u16buf[MAX_CONSOLE_LINE_SIZE + 1];
87   size_t mblen= 0;
88   DWORD console_mode;
89   DWORD nchars;
90 
91   HANDLE console= GetStdHandle(STD_INPUT_HANDLE);
92 
93   assert(mbbufsize > 0); /* Need space for at least trailing '\0' */
94   GetConsoleMode(console, &console_mode);
95   SetConsoleMode(console, ENABLE_LINE_INPUT |
96                           ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT);
97 
98   if (!ReadConsoleW(console, u16buf, MAX_NUM_OF_CHARS_TO_READ, &nchars, NULL))
99   {
100     SetConsoleMode(console, console_mode);
101     return NULL;
102   }
103 
104   *nread= nchars;
105 
106   /* Set length of string */
107   if (nchars >= 2 && u16buf[nchars - 2] == L'\r')
108     nchars-= 2;
109   else if ((nchars == MAX_NUM_OF_CHARS_TO_READ) &&
110            (u16buf[nchars - 1] == L'\r'))
111     /* Special case 1 - \r\n straddles the boundary */
112     nchars--;
113   else if ((nchars == 1) && (u16buf[0] == L'\n'))
114     /* Special case 2 - read a single '\n'*/
115     nchars--;
116 
117   SetConsoleMode(console, console_mode);
118 
119   /* Convert Unicode to session character set */
120   if (nchars != 0)
121     mblen= my_convert(mbbuf, mbbufsize - 1, cs,
122                       (const char *) u16buf, nchars * sizeof(wchar_t),
123                       &my_charset_utf16le_bin, &dummy_errors);
124 
125   assert(mblen < mbbufsize); /* Safety */
126   mbbuf[mblen]= 0;
127   return mbbuf;
128 }
129 
130 
131 /**
132   Translate client charset to Windows wchars for console I/O.
133   Unlike copy_and_convert(), in case of a wrong multi-byte sequence
134   we don't print '?' character, we fallback to ISO-8859-1 instead.
135   This gives a better idea how binary data (e.g. BLOB) look like.
136 
137   @param cs           Character set of the input string
138   @param from         Input string
139   @param from_length  Length of the input string
140   @param to[OUT]      Write Unicode data here
141   @param to_chars     Number of characters available in "to"
142 */
143 static size_t
my_mbstou16s(const CHARSET_INFO * cs,const uchar * from,size_t from_length,wchar_t * to,size_t to_chars)144 my_mbstou16s(const CHARSET_INFO *cs, const uchar * from, size_t from_length,
145              wchar_t *to, size_t to_chars)
146 {
147   const CHARSET_INFO *to_cs= &my_charset_utf16le_bin;
148   const uchar *from_end= from + from_length;
149   wchar_t *to_orig= to, *to_end= to + to_chars;
150   my_charset_conv_mb_wc mb_wc= cs->cset->mb_wc;
151   my_charset_conv_wc_mb wc_mb= to_cs->cset->wc_mb;
152   while (from < from_end)
153   {
154     int cnvres;
155     my_wc_t wc;
156     if ((cnvres= (*mb_wc)(cs, &wc, from, from_end)) > 0)
157     {
158       if (!wc)
159         break;
160       from+= cnvres;
161     }
162     else if (cnvres == MY_CS_ILSEQ)
163     {
164       wc= (my_wc_t) (uchar) *from; /* Fallback to ISO-8859-1 */
165       from+= 1;
166     }
167     else if (cnvres > MY_CS_TOOSMALL)
168     {
169       /*
170         A correct multibyte sequence detected
171         But it doesn't have Unicode mapping.
172       */
173       wc= '?';
174       from+= (-cnvres); /* Note: cnvres is negative here */
175     }
176     else /* Incomplete character */
177     {
178       wc= (my_wc_t) (uchar) *from; /* Fallback to ISO-8859-1 */
179       from+= 1;
180     }
181 outp:
182     if ((cnvres= (*wc_mb)(to_cs, wc, (uchar *) to, (uchar *) to_end)) > 0)
183     {
184       /* We can never convert only a part of wchar_t */
185       assert((cnvres % sizeof(wchar_t)) == 0);
186       /* cnvres returns number of bytes, convert to number of wchar_t's */
187       to+= cnvres / sizeof(wchar_t);
188     }
189     else if (cnvres == MY_CS_ILUNI && wc != '?')
190     {
191       wc= '?';
192       goto outp;
193     }
194     else
195       break; /* Not enough space */
196   }
197   return to - to_orig;
198 }
199 
200 
201 /**
202   Write a string in the given character set to Windows console.
203   As Window breaks supplementary characters into two parts,
204   we cannot use a simple loop sending the result of
205   cs->cset->mb_wc() to console.
206   So we converts string from client charset to an array of wchar_t,
207   then write the array to console in a single shot.
208 
209   @param cs       Character set of the string
210   @param data     String to print
211   @param datalen  Length of input string in bytes
212 */
213 void
my_win_console_write(const CHARSET_INFO * cs,const char * data,size_t datalen)214 my_win_console_write(const CHARSET_INFO *cs, const char *data, size_t datalen)
215 {
216   static wchar_t u16buf[MAX_CONSOLE_LINE_SIZE + 1];
217   size_t nchars= my_mbstou16s(cs, (const uchar *) data, datalen,
218                               u16buf, sizeof(u16buf) / sizeof(u16buf[0]));
219   DWORD nwritten;
220   WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),
221                 u16buf, (DWORD) nchars, &nwritten, NULL);
222 }
223 
224 
225 /**
226   Write a single-byte character to console.
227   Note: one should not send parts of the same multi-byte character
228   in separate consequent my_win_console_putc() calls.
229   For multi-byte characters use my_win_colsole_write() instead.
230 
231   @param cs  Character set of the input character
232   @param c   Character (single byte)
233 */
234 void
my_win_console_putc(const CHARSET_INFO * cs,int c)235 my_win_console_putc(const CHARSET_INFO *cs, int c)
236 {
237   char ch= (char) c;
238   my_win_console_write(cs, &ch, 1);
239 }
240 
241 
242 /**
243   Write a 0-terminated string to Windows console.
244 
245   @param cs    Character set of the string to print
246   @param data  String to print
247 */
248 void
my_win_console_fputs(const CHARSET_INFO * cs,const char * data)249 my_win_console_fputs(const CHARSET_INFO *cs, const char *data)
250 {
251   my_win_console_write(cs, data, strlen(data));
252 }
253 
254 
255 /*
256   Handle formatted output on the Windows console.
257 */
258 void
my_win_console_vfprintf(const CHARSET_INFO * cs,const char * fmt,va_list args)259 my_win_console_vfprintf(const CHARSET_INFO *cs, const char *fmt, va_list args)
260 {
261   static char buff[MAX_CONSOLE_LINE_SIZE + 1];
262   size_t len= vsnprintf(buff, sizeof(buff) - 1, fmt, args);
263   my_win_console_write(cs, buff, len);
264 }
265 
266 
267 #include <shellapi.h>
268 
269 /**
270   Translate Unicode command line parameters to the given character set
271   (Typically to utf8mb4).
272   Translated parameters are allocated using my_once_alloc().
273 
274   @param      tocs    Character set to convert parameters to.
275   @param[OUT] argc    Write number of parameters here
276   @param[OUT] argv    Write pointer to allocated parameters here.
277 */
278 int
my_win_translate_command_line_args(const CHARSET_INFO * cs,int * argc,char *** argv)279 my_win_translate_command_line_args(const CHARSET_INFO *cs, int *argc, char ***argv)
280 {
281   int i, ac;
282   char **av;
283   wchar_t *command_line= GetCommandLineW();
284   wchar_t **wargs= CommandLineToArgvW(command_line, &ac);
285   size_t nbytes= (ac + 1) * sizeof(char *);
286 
287   /* Allocate new command line parameter */
288   av= (char **) my_once_alloc(nbytes, MYF(MY_ZEROFILL));
289 
290   for(i= 0; i < ac; i++)
291   {
292     uint dummy_errors;
293     size_t arg_len= wcslen(wargs[i]);
294     size_t len, alloced_len= arg_len * cs->mbmaxlen + 1;
295     av[i]= (char *) my_once_alloc(alloced_len, MYF(0));
296     len= my_convert(av[i], alloced_len, cs,
297                     (const char *) wargs[i], arg_len * sizeof(wchar_t),
298                     &my_charset_utf16le_bin, &dummy_errors);
299     assert(len < alloced_len);
300     av[i][len]= '\0';
301   }
302   *argv= av;
303   *argc= ac;
304   /* Cleanup on exit */
305   LocalFree((HLOCAL) wargs);
306   return 0;
307 }
308 
309 #endif /* _WIN32 */
310