1 /*
2 Functions for escaping and unescaping strings
3
4 Copyright (C) 2009-2025
5 Free Software Foundation, Inc.
6
7 Written by:
8 Slava Zanko <slavazanko@gmail.com>, 2009;
9 Patrick Winnertz <winnie@debian.org>, 2009
10
11 This file is part of the Midnight Commander.
12
13 The Midnight Commander is free software: you can redistribute it
14 and/or modify it under the terms of the GNU General Public License as
15 published by the Free Software Foundation, either version 3 of the License,
16 or (at your option) any later version.
17
18 The Midnight Commander is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License
24 along with this program. If not, see <https://www.gnu.org/licenses/>.
25 */
26
27 #include <config.h>
28
29 #include "lib/global.h"
30 #include "lib/strutil.h"
31
32 /*** global variables ****************************************************************************/
33
34 /*** file scope macro definitions ****************************************************************/
35
36 /*** file scope type declarations ****************************************************************/
37
38 /*** forward declarations (file scope functions) *************************************************/
39
40 /*** file scope variables ************************************************************************/
41
42 static const char ESCAPE_SHELL_CHARS[] = " !#$%()&{}[]`?|<>;*\\\"'";
43 static const char ESCAPE_REGEX_CHARS[] = "^!#$%()&{}[]`?|<>;*+.\\";
44 static const char ESCAPE_GLOB_CHARS[] = "$*\\?";
45
46 /* --------------------------------------------------------------------------------------------- */
47 /*** file scope functions ************************************************************************/
48 /* --------------------------------------------------------------------------------------------- */
49
50 /* --------------------------------------------------------------------------------------------- */
51 /*** public functions ****************************************************************************/
52 /* --------------------------------------------------------------------------------------------- */
53
54 char *
55 str_escape (const char *src, const ssize_t src_len, const char *escaped_chars,
/* ![[previous]](../icons/n_left.png)
![[next]](../icons/right.png)
![[first]](../icons/n_first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
56 const gboolean escape_non_printable)
57 {
58 GString *ret;
59
60 // do NOT break allocation semantics
61 if (src == NULL)
62 return NULL;
63
64 if (*src == '\0')
65 return g_strdup ("");
66
67 ret = g_string_new ("");
68
69 const size_t src_len1 = src_len < 0 ? strlen (src) : (size_t) src_len;
70
71 for (size_t curr_index = 0; curr_index < src_len1; curr_index++)
72 {
73 if (escape_non_printable)
74 {
75 switch (src[curr_index])
76 {
77 case '\n':
78 g_string_append (ret, "\\n");
79 continue;
80 case '\t':
81 g_string_append (ret, "\\t");
82 continue;
83 case '\b':
84 g_string_append (ret, "\\b");
85 continue;
86 case '\0':
87 g_string_append (ret, "\\0");
88 continue;
89 default:
90 break;
91 }
92 }
93
94 if (strchr (escaped_chars, (int) src[curr_index]))
95 g_string_append_c (ret, '\\');
96
97 g_string_append_c (ret, src[curr_index]);
98 }
99 return g_string_free (ret, FALSE);
100 }
101
102 /* --------------------------------------------------------------------------------------------- */
103
104 char *
105 str_unescape (const char *src, const ssize_t src_len, const char *unescaped_chars,
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
106 const gboolean unescape_non_printable)
107 {
108 GString *ret;
109 size_t curr_index;
110
111 if (src == NULL)
112 return NULL;
113
114 if (*src == '\0')
115 return g_strdup ("");
116
117 ret = g_string_sized_new (16);
118
119 const size_t src_len1 = src_len < 0 ? strlen (src) : (size_t) src_len;
120
121 for (curr_index = 0; curr_index < src_len1 - 1; curr_index++)
122 {
123 if (src[curr_index] != '\\')
124 {
125 g_string_append_c (ret, src[curr_index]);
126 continue;
127 }
128
129 curr_index++;
130
131 if (unescaped_chars == ESCAPE_SHELL_CHARS && src[curr_index] == '$')
132 {
133 // special case: \$ is used to disallow variable substitution
134 g_string_append_c (ret, '\\');
135 }
136 else
137 {
138 if (unescape_non_printable)
139 {
140 switch (src[curr_index])
141 {
142 case 'n':
143 g_string_append_c (ret, '\n');
144 continue;
145 case 't':
146 g_string_append_c (ret, '\t');
147 continue;
148 case 'b':
149 g_string_append_c (ret, '\b');
150 continue;
151 case '0':
152 g_string_append_c (ret, '\0');
153 continue;
154 default:
155 break;
156 }
157 }
158
159 if (strchr (unescaped_chars, (int) src[curr_index]) == NULL)
160 g_string_append_c (ret, '\\');
161 }
162
163 g_string_append_c (ret, src[curr_index]);
164 }
165 g_string_append_c (ret, src[curr_index]);
166
167 return g_string_free (ret, FALSE);
168 }
169
170 /* --------------------------------------------------------------------------------------------- */
171
172 /**
173 * To be compatible with the general posix command lines we have to escape
174 * strings for the command line
175 *
176 * @param src string for escaping
177 *
178 * @return escaped string (which needs to be freed later) or NULL when NULL string is passed.
179 */
180
181 char *
182 str_shell_escape (const char *src)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
183 {
184 return str_escape (src, -1, ESCAPE_SHELL_CHARS, FALSE);
185 }
186
187 /* --------------------------------------------------------------------------------------------- */
188
189 char *
190 str_glob_escape (const char *src)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
191 {
192 return str_escape (src, -1, ESCAPE_GLOB_CHARS, TRUE);
193 }
194
195 /* --------------------------------------------------------------------------------------------- */
196
197 char *
198 str_regex_escape (const char *src)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
199 {
200 return str_escape (src, -1, ESCAPE_REGEX_CHARS, TRUE);
201 }
202
203 /* --------------------------------------------------------------------------------------------- */
204
205 /**
206 * Unescape paths or other strings for e.g the internal cd
207 * shell-unescape within a given buffer (writing to it!)
208 *
209 * @param text string for unescaping
210 *
211 * @return unescaped string (which needs to be freed)
212 */
213
214 char *
215 str_shell_unescape (const char *text)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
216 {
217 return str_unescape (text, -1, ESCAPE_SHELL_CHARS, TRUE);
218 }
219
220 /* --------------------------------------------------------------------------------------------- */
221
222 char *
223 str_glob_unescape (const char *text)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
224 {
225 return str_unescape (text, -1, ESCAPE_GLOB_CHARS, TRUE);
226 }
227
228 /* --------------------------------------------------------------------------------------------- */
229 char *
230 str_regex_unescape (const char *text)
/* ![[previous]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
231 {
232 return str_unescape (text, -1, ESCAPE_REGEX_CHARS, TRUE);
233 }
234
235 /* --------------------------------------------------------------------------------------------- */
236
237 /**
238 * Check if char in pointer contain escape'd chars
239 *
240 * @param start string for checking
241 * @param current pointer to checked character
242 *
243 * @return TRUE if string contain escaped chars otherwise return FALSE
244 */
245
246 gboolean
247 str_is_char_escaped (const char *start, const char *current)
/* ![[previous]](../icons/left.png)
![[next]](../icons/n_right.png)
![[first]](../icons/first.png)
![[last]](../icons/n_last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
248 {
249 int num_esc = 0;
250
251 if (start == NULL || current == NULL || current <= start)
252 return FALSE;
253
254 current--;
255 while (current >= start && *current == '\\')
256 {
257 num_esc++;
258 current--;
259 }
260 return (gboolean) num_esc % 2;
261 }
262
263 /* --------------------------------------------------------------------------------------------- */