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