1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// SPDX-License-Identifier: 0BSD
///////////////////////////////////////////////////////////////////////////////
//
/// \file tuklib_mbstr_nonprint.c
/// \brief Find and replace non-printable characters with question marks
//
// Author: Lasse Collin
//
///////////////////////////////////////////////////////////////////////////////
#include "tuklib_mbstr_nonprint.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifdef HAVE_MBRTOWC
# include <wchar.h>
# include <wctype.h>
#else
# include <ctype.h>
#endif
static bool
is_next_printable(const char *str, size_t len, size_t *next_len)
{
#ifdef HAVE_MBRTOWC
// This assumes that character sets with locking shift states aren't
// used, and thus mbsinit() is never needed.
mbstate_t ps;
memset(&ps, 0, sizeof(ps));
wchar_t wc;
*next_len = mbrtowc(&wc, str, len, &ps);
if (*next_len == (size_t)-2) {
// Incomplete multibyte sequence: Treat the whole sequence
// as a single non-printable multibyte character that ends
// the string.
*next_len = len;
return false;
}
// Check more broadly than just ret == (size_t)-1 to be safe
// in case mbrtowc() returns something weird. This check
// covers (size_t)-1 (that is, SIZE_MAX) too because len is from
// strlen() and the terminating '\0' isn't part of the length.
if (*next_len < 1 || *next_len > len) {
// Invalid multibyte sequence: Treat the first byte as
// a non-printable single-byte character. Decoding will
// be restarted from the next byte on the next call to
// this function.
*next_len = 1;
return false;
}
# if defined(_WIN32) && !defined(__CYGWIN__)
// On Windows, wchar_t stores UTF-16 code units, thus characters
// outside the Basic Multilingual Plane (BMP) don't fit into
// a single wchar_t. In an UTF-8 locale, UCRT's mbrtowc() returns
// successfully when the input is a non-BMP character but the
// output is the replacement character U+FFFD.
//
// iswprint() returns 0 for U+FFFD on Windows for some reason. Treat
// U+FFFD as printable and thus also all non-BMP chars as printable.
if (wc == 0xFFFD)
return true;
# endif
return iswprint((wint_t)wc) != 0;
#else
(void)len;
*next_len = 1;
return isprint((unsigned char)str[0]) != 0;
#endif
}
static bool
has_nonprint(const char *str, size_t len)
{
for (size_t i = 0; i < len; ) {
size_t next_len;
if (!is_next_printable(str + i, len - i, &next_len))
return true;
i += next_len;
}
return false;
}
extern bool
tuklib_has_nonprint(const char *str)
{
const int saved_errno = errno;
const bool ret = has_nonprint(str, strlen(str));
errno = saved_errno;
return ret;
}
extern const char *
tuklib_mask_nonprint_r(const char *str, char **mem)
{
const int saved_errno = errno;
// Free the old string, if any.
free(*mem);
*mem = NULL;
// If the whole input string contains only printable characters,
// return the input string.
const size_t len = strlen(str);
if (!has_nonprint(str, len)) {
errno = saved_errno;
return str;
}
// Allocate memory for the masked string. Since we use the single-byte
// character '?' to mask non-printable characters, it's possible that
// a few bytes less memory would be needed in reality if multibyte
// characters are masked.
//
// If allocation fails, return "???" because it should be safer than
// returning the unmasked string.
*mem = malloc(len + 1);
if (*mem == NULL) {
errno = saved_errno;
return "???";
}
// Replace all non-printable characters with '?'.
char *dest = *mem;
for (size_t i = 0; i < len; ) {
size_t next_len;
if (is_next_printable(str + i, len - i, &next_len)) {
memcpy(dest, str + i, next_len);
dest += next_len;
} else {
*dest++ = '?';
}
i += next_len;
}
*dest = '\0';
errno = saved_errno;
return *mem;
}
extern const char *
tuklib_mask_nonprint(const char *str)
{
static char *mem = NULL;
return tuklib_mask_nonprint_r(str, &mem);
}