Line data Source code
1 : // Copyright (c) 2011-2022 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/edhttp
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program 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
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 :
19 : // self
20 : //
21 : #include "edhttp/quoted_printable.h"
22 :
23 :
24 : // snapdev lib
25 : //
26 : #include <snapdev/not_reached.h>
27 :
28 :
29 : // last include
30 : //
31 : #include <snapdev/poison.h>
32 :
33 :
34 :
35 : namespace edhttp
36 : {
37 :
38 :
39 :
40 0 : std::string quoted_printable_encode(std::string const & input, int flags)
41 : {
42 0 : class result
43 : {
44 : public:
45 0 : result(size_t input_length, int flags)
46 0 : : f_flags(flags)
47 : {
48 0 : f_result.reserve(input_length * 2);
49 0 : }
50 :
51 0 : char to_hex(int c)
52 : {
53 0 : c &= 15;
54 0 : if(c >= 0 && c <= 9)
55 : {
56 0 : return static_cast<char>(c + '0');
57 : }
58 0 : return static_cast<char>(c + 'A' - 10);
59 : }
60 :
61 0 : void add_byte(char c)
62 : {
63 0 : if(c == '\n' || c == '\r')
64 : {
65 0 : if(c != '\r' || (f_flags & QUOTED_PRINTABLE_FLAG_LFONLY) == 0)
66 : {
67 0 : f_result += c;
68 : }
69 0 : f_line = 0;
70 0 : return;
71 : }
72 : // the maximum line length is 76
73 : // it is not clear whether that includes the CR+LF or not
74 : // "=\r\n" is 3 characters
75 0 : if(f_line >= 75)
76 : {
77 0 : if((f_flags & QUOTED_PRINTABLE_FLAG_LFONLY) == 0)
78 : {
79 0 : f_result += "=\r\n";
80 : }
81 : else
82 : {
83 0 : f_result += "=\n";
84 : }
85 0 : f_line = 0;
86 : }
87 0 : f_result += c;
88 0 : ++f_line;
89 : }
90 :
91 0 : void add_hex(int c)
92 : {
93 : // make sure there is enough space on the current line before
94 : // adding the 3 encoded bytes
95 0 : if(f_line >= 73)
96 : {
97 : // IMPORTANT: we cannot call add_byte while adding
98 : // an '=' as character 75 otherwise it will add "=\r\n"!
99 0 : if((f_flags & QUOTED_PRINTABLE_FLAG_LFONLY) == 0)
100 : {
101 0 : f_result += "=\r\n";
102 : }
103 : else
104 : {
105 0 : f_result += "=\n";
106 : }
107 0 : f_line = 0;
108 : }
109 0 : add_byte('=');
110 0 : add_byte(to_hex(c >> 4));
111 0 : add_byte(to_hex(c));
112 0 : }
113 :
114 0 : void add_data(char c)
115 : {
116 0 : if(c == ' ' || c == '\t')
117 : {
118 : // we have to buffer the last space or tab because they
119 : // cannot appear as the last character on a line (i.e.
120 : // when followed by \r or \n)
121 0 : if(f_buffer != '\0')
122 : {
123 0 : add_byte(f_buffer);
124 : }
125 0 : f_buffer = c;
126 0 : return;
127 : }
128 0 : if(c == '\r')
129 : {
130 0 : f_cr = true;
131 : }
132 0 : else if(c == '\n' && f_cr)
133 : {
134 : // CR+LF already taken cared of
135 0 : f_cr = false;
136 0 : return;
137 : }
138 : else
139 : {
140 0 : f_cr = false;
141 : }
142 0 : if(c == '\n' || c == '\r')
143 : {
144 : // spaces and tabs must be encoded in this case
145 0 : if(f_buffer != '\0')
146 : {
147 0 : add_hex(f_buffer);
148 0 : f_buffer = '\0';
149 : }
150 : // force the CR+LF sequence
151 0 : add_byte('\r');
152 0 : add_byte('\n');
153 0 : return;
154 : }
155 0 : if(f_buffer != '\0')
156 : {
157 0 : add_byte(f_buffer);
158 0 : f_buffer = '\0';
159 : }
160 0 : add_byte(c);
161 : }
162 :
163 0 : bool encode_char(char c)
164 : {
165 0 : switch(c)
166 : {
167 0 : case '\n':
168 : case '\r':
169 : case '\t':
170 : case ' ':
171 0 : return (f_flags & QUOTED_PRINTABLE_FLAG_BINARY) != 0;
172 :
173 0 : case '=':
174 0 : return true;
175 :
176 0 : case '!': // !"#$@[\]^`{|}~
177 : case '"':
178 : case '#':
179 : case '$':
180 : case '@':
181 : case '[':
182 : case '\\':
183 : case ']':
184 : case '^':
185 : case '`':
186 : case '{':
187 : case '|':
188 : case '}':
189 : case '~':
190 0 : return (f_flags & QUOTED_PRINTABLE_FLAG_EDBIC) != 0;
191 :
192 0 : default:
193 : // note: the following won't match ' ' and '~' which are
194 : // already captured by other cases
195 0 : return !(c >= ' ' && c <= '~');
196 :
197 : }
198 : snapdev::NOT_REACHED();
199 : }
200 :
201 0 : void add_char(char c)
202 : {
203 : // a few controls are allowed as is
204 0 : if(encode_char(c))
205 : {
206 : // needs to be encoded
207 0 : add_hex(c);
208 : }
209 : else
210 : {
211 0 : add_data(c);
212 : }
213 0 : }
214 :
215 0 : void add_string(char const * s)
216 : {
217 0 : bool const lone_periods((f_flags & QUOTED_PRINTABLE_FLAG_NO_LONE_PERIOD) != 0);
218 : // reset the buffer, just in case
219 0 : f_buffer = '\0';
220 0 : for(; *s != '\0'; ++s)
221 : {
222 0 : if(lone_periods
223 0 : && *s == '.'
224 0 : && (s[1] == '\r' || s[1] == '\n' || s[1] == '\0')
225 0 : && (f_line == 0 || f_line >= 75))
226 : {
227 : // special case of a lone period at the start of a line
228 0 : add_hex('.');
229 : }
230 : else
231 : {
232 0 : add_char(*s);
233 : }
234 : }
235 :
236 : // at the end we may still have a space or tab to add
237 0 : if(f_buffer != '\0')
238 : {
239 0 : add_hex(f_buffer);
240 0 : f_buffer = '\0';
241 : }
242 0 : }
243 :
244 0 : std::string get_result() const
245 : {
246 0 : return f_result;
247 : }
248 :
249 : private:
250 : int32_t f_flags = 0;
251 : char f_buffer = 0;
252 : std::string f_result = std::string();
253 : int32_t f_line = 0;
254 : bool f_cr = false;
255 : };
256 :
257 0 : result r(input.length(), flags);
258 0 : r.add_string(input.c_str());
259 0 : return r.get_result();
260 : }
261 :
262 :
263 0 : std::string quoted_printable_decode(std::string const & input)
264 : {
265 0 : class result
266 : {
267 : public:
268 0 : result(std::string const & input)
269 : //: f_result("") -- auto-initiazlied
270 0 : : f_input(input)
271 0 : , f_str(f_input.c_str())
272 : {
273 0 : }
274 :
275 : result(result const & rhs) = delete;
276 : result & operator = (result const & rhs) = delete;
277 :
278 0 : int from_hex(int c)
279 : {
280 0 : if(c >= '0' && c <= '9')
281 : {
282 0 : return c - '0';
283 : }
284 : // note that the documentation clearly says that only capitalized
285 : // (A-F) characters are acceptable...
286 0 : if(c >= 'a' && c <= 'f')
287 : {
288 0 : return c - 'a' + 10;
289 : }
290 0 : if(c >= 'A' && c <= 'F')
291 : {
292 0 : return c - 'A' + 10;
293 : }
294 0 : return -1;
295 : }
296 :
297 0 : int get_byte()
298 : {
299 : for(;;)
300 : {
301 0 : if(*f_str == '\0')
302 : {
303 0 : return '\0';
304 : }
305 0 : if(*f_str == '=')
306 : {
307 0 : if(f_str[1] == '\r')
308 : {
309 0 : if(f_str[2] == '\n')
310 : {
311 0 : f_str += 3;
312 : }
313 : else
314 : {
315 0 : f_str += 2;
316 : }
317 0 : continue;
318 : }
319 0 : if(f_str[1] == '\n')
320 : {
321 0 : f_str += 2;
322 0 : continue;
323 : }
324 : }
325 0 : return *f_str++;
326 : }
327 : }
328 :
329 0 : int getc()
330 : {
331 0 : int c(get_byte());
332 0 : if(c == '\0')
333 : {
334 0 : return '\0';
335 : }
336 0 : if(c == '=')
337 : {
338 : // all equal must be followed by a newline (taken care
339 : // off already) or a 2 hex digits
340 0 : c = get_byte();
341 0 : int const p(from_hex(c));
342 0 : if(p == -1)
343 : {
344 0 : return '?';
345 : }
346 0 : c = get_byte();
347 0 : int const q(from_hex(c));
348 0 : if(q == -1)
349 : {
350 0 : return '?';
351 : }
352 0 : return p * 16 + q;
353 : }
354 0 : return c;
355 : }
356 :
357 0 : void process()
358 : {
359 0 : for(int c(getc()); c != '\0'; c = getc())
360 : {
361 0 : f_result += static_cast<char>(c);
362 : }
363 0 : }
364 :
365 0 : std::string get_result() const
366 : {
367 0 : return f_result;
368 : }
369 :
370 : private:
371 : std::string f_input = std::string();
372 : std::string f_result = std::string();
373 : char const * f_str = nullptr;
374 : };
375 :
376 0 : result r(input);
377 0 : r.process();
378 0 : return r.get_result();
379 : }
380 :
381 :
382 :
383 6 : } // namespace edhttp
384 : // vim: ts=4 sw=4 et
|