Branch data Line data Source code
1 : : /*
2 : : * Copyright © 2025 Luca Bacci
3 : : *
4 : : * SPDX-License-Identifier: LGPL-2.1-or-later
5 : : *
6 : : * This library is free software; you can redistribute it and/or
7 : : * modify it under the terms of the GNU Lesser General Public
8 : : * License as published by the Free Software Foundation; either
9 : : * version 2.1 of the License, or (at your option) any later version.
10 : : *
11 : : * This library is distributed in the hope that it will be useful,
12 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 : : * Lesser General Public License for more details.
15 : : *
16 : : * You should have received a copy of the GNU Lesser General Public
17 : : * License along with this library; if not, see <http://www.gnu.org/licenses/>.
18 : : *
19 : : * Author: Luca Bacci <luca.bacci@outlook.com>
20 : : */
21 : :
22 : : #include "config.h"
23 : :
24 : : #include <stdlib.h>
25 : : #include <stdio.h>
26 : :
27 : : #include "gtypes.h"
28 : : #include "gunicodeprivate.h"
29 : : #include "gprintprivate.h"
30 : : #include "gprintfint.h"
31 : :
32 : : #ifndef _WIN32
33 : : #include <unistd.h>
34 : : #else
35 : : #include "gwin32private.h"
36 : : #include <io.h>
37 : : #endif
38 : :
39 : : #define CHAR_IS_SAFE(wc) (!((wc < 0x20 && wc != '\t' && wc != '\n' && wc != '\r') || \
40 : : (wc == 0x7f) || \
41 : : (wc >= 0x80 && wc < 0xa0)))
42 : :
43 : : char *
44 : 0 : g_print_convert (const char *string,
45 : : const char *charset)
46 : : {
47 : 0 : if (!g_utf8_validate (string, -1, NULL))
48 : : {
49 : 0 : GString *gstring = g_string_new ("[Invalid UTF-8] ");
50 : : guchar *p;
51 : :
52 : 0 : for (p = (guchar *)string; *p; p++)
53 : : {
54 : 0 : if (CHAR_IS_SAFE(*p) &&
55 : 0 : !(*p == '\r' && *(p + 1) != '\n') &&
56 : 0 : *p < 0x80)
57 : 0 : g_string_append_c (gstring, *p);
58 : : else
59 : 0 : g_string_append_printf (gstring, "\\x%02x", (guint)(guchar)*p);
60 : : }
61 : :
62 : 0 : return g_string_free (gstring, FALSE);
63 : : }
64 : : else
65 : : {
66 : 0 : GError *err = NULL;
67 : :
68 : 0 : gchar *result = g_convert_with_fallback (string, -1, charset, "UTF-8", "?", NULL, NULL, &err);
69 : 0 : if (result)
70 : 0 : return result;
71 : : else
72 : : {
73 : : /* Not thread-safe, but doesn't matter if we print the warning twice
74 : : */
75 : : static gboolean warned = FALSE;
76 : 0 : if (!warned)
77 : : {
78 : 0 : warned = TRUE;
79 : 0 : _g_fprintf (stderr, "GLib: Cannot convert message: %s\n", err->message);
80 : : }
81 : 0 : g_error_free (err);
82 : :
83 : 0 : return g_strdup (string);
84 : : }
85 : : }
86 : : }
87 : :
88 : : #ifdef _WIN32
89 : :
90 : : static int
91 : : print_console_nolock (const char *string,
92 : : FILE *stream)
93 : : {
94 : : HANDLE handle = (HANDLE) _get_osfhandle (_fileno (stream));
95 : : size_t size = strlen (string);
96 : : DWORD written = 0;
97 : :
98 : : if (size > INT_MAX)
99 : : return 0;
100 : :
101 : : /* WriteFile are WriteConsole are limited to DWORD lengths,
102 : : * but int and DWORD should are of the same size, so we don't
103 : : * care.
104 : : */
105 : : G_STATIC_ASSERT (INT_MAX <= MAXDWORD);
106 : :
107 : : /* We might also check if the source string is ASCII */
108 : : if (GetConsoleOutputCP () == CP_UTF8)
109 : : {
110 : : /* If the output codepage is UTF-8, we can just call WriteFile,
111 : : * avoiding a conversion to UTF-16 (which probably will be done
112 : : * by ConDrv).
113 : : */
114 : : /* Note: we cannot use fputs() here. When outputting to the
115 : : * console, the UCRT converts the passed string to the console
116 : : * charset, which is UTF-8, but interprets the string in the
117 : : * LC_CTYPE charset, which can be anything.
118 : : */
119 : :
120 : : if (!WriteFile (handle, string, size, &written, NULL))
121 : : WIN32_API_FAILED ("WriteFile");
122 : : }
123 : : else
124 : : {
125 : : /* Convert to UTF-16 and output using WriteConsole */
126 : :
127 : : /* Note: we can't use fputws() with mode _O_U16TEXT because:
128 : : *
129 : : * - file descriptors cannot be locked, unlike FILE streams, so
130 : : * we cannot set a custom mode on the file descriptor.
131 : : * - the fputws() implementation is not very good: it outputs codeunit
132 : : * by codeunit in a loop, so it's slow [1] and breaks UTF-16 surrogate
133 : : * pairs [2].
134 : : *
135 : : * [1] https://github.com/microsoft/terminal/issues/18124#issuecomment-2451987873
136 : : * [2] https://developercommunity.visualstudio.com/t/wprintf-with-_setmode-_O_U16TEXT-or-_O_U/10447076
137 : : */
138 : :
139 : : wchar_t buffer[1024];
140 : : wchar_t *utf16 = NULL;
141 : : size_t utf16_len = 0;
142 : : DWORD utf16_written = 0;
143 : :
144 : : g_utf8_to_utf16_make_valid (string,
145 : : buffer, G_N_ELEMENTS (buffer),
146 : : &utf16, &utf16_len);
147 : :
148 : : /* The length of the UTF-16 string (in count of gunichar2) cannot be
149 : : * greater than the length of the UTF-8 string (in count of bytes).
150 : : * So utf16_len <= size <= INT_MAX <= MAXDWORD.
151 : : */
152 : : g_assert (utf16_len <= size);
153 : :
154 : : if (!WriteConsole (handle, utf16, utf16_len, &utf16_written, NULL))
155 : : WIN32_API_FAILED ("WriteConsole");
156 : :
157 : : if (utf16_written < utf16_len)
158 : : {
159 : : written = g_utf8_to_utf16_make_valid_backtrack (string, utf16_written);
160 : : }
161 : : else
162 : : {
163 : : written = size;
164 : : }
165 : :
166 : : if (utf16 != buffer)
167 : : g_free (utf16);
168 : : }
169 : :
170 : : if (written > INT_MAX)
171 : : written = INT_MAX;
172 : :
173 : : return (int) written;
174 : : }
175 : :
176 : : static int
177 : : print_console (const char *string,
178 : : FILE *stream)
179 : : {
180 : : int ret;
181 : :
182 : : /* Locking the stream is not important, but leads
183 : : * to nicer output in case of concurrent writes.
184 : : */
185 : : _lock_file (stream);
186 : :
187 : : #if defined (_MSC_VER) || defined (_UCRT)
188 : : _fflush_nolock (stream);
189 : : #else
190 : : fflush (stream);
191 : : #endif
192 : :
193 : : ret = print_console_nolock (string, stream);
194 : :
195 : : _unlock_file (stream);
196 : :
197 : : return ret;
198 : : }
199 : :
200 : : #endif /* _WIN32 */
201 : :
202 : : static int
203 : 30232 : print_string (char const *string,
204 : : FILE *stream)
205 : : {
206 : 30232 : size_t written = fwrite (string, 1, strlen (string), stream);
207 : :
208 : 30232 : return MIN (written, INT_MAX);
209 : : }
210 : :
211 : : int
212 : 30232 : g_fputs (char const *string,
213 : : FILE *stream)
214 : : {
215 : : int ret;
216 : :
217 : : #ifdef _WIN32
218 : : if (g_win32_file_stream_is_console_output (stream))
219 : : {
220 : : ret = print_console (string, stream);
221 : :
222 : : if (string[ret] != '\0')
223 : : ret += print_string (&string[ret], stream);
224 : : }
225 : : else
226 : : {
227 : : ret = print_string (string, stream);
228 : : }
229 : : #else
230 : : const char *charset;
231 : :
232 : 30232 : if (isatty (fileno (stream)) &&
233 : 0 : !g_get_charset (&charset))
234 : 0 : {
235 : 0 : char *converted = g_print_convert (string, charset);
236 : 0 : ret = print_string (converted, stream);
237 : 0 : g_free (converted);
238 : : }
239 : : else
240 : : {
241 : 30232 : ret = print_string (string, stream);
242 : : }
243 : : #endif
244 : :
245 : 30232 : return ret;
246 : : }
|