1/*
2 * Copyright (C) 2003-2005 Justin Karneges <justin@affinix.com>
3 * Copyright (C) 2004,2005 Brad Hards <bradh@frogmouth.net>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 * 02110-1301 USA
19 *
20 */
21
22#include "qca_textfilter.h"
23
24namespace QCA {
25
26//----------------------------------------------------------------------------
27// TextFilter
28//----------------------------------------------------------------------------
29TextFilter::TextFilter(Direction dir)
30{
31 setup(dir);
32}
33
34void TextFilter::setup(Direction dir)
35{
36 _dir = dir;
37}
38
39Direction TextFilter::direction() const
40{
41 return _dir;
42}
43
44MemoryRegion TextFilter::encode(const MemoryRegion &a)
45{
46 setup(Encode);
47 return process(a);
48}
49
50MemoryRegion TextFilter::decode(const MemoryRegion &a)
51{
52 setup(Decode);
53 return process(a);
54}
55
56QString TextFilter::arrayToString(const MemoryRegion &a)
57{
58 return QString::fromLatin1(ba: encode(a).toByteArray());
59}
60
61MemoryRegion TextFilter::stringToArray(const QString &s)
62{
63 if (s.isEmpty())
64 return MemoryRegion();
65 return decode(a: s.toLatin1());
66}
67
68QString TextFilter::encodeString(const QString &s)
69{
70 return arrayToString(a: s.toUtf8());
71}
72
73QString TextFilter::decodeString(const QString &s)
74{
75 return QString::fromUtf8(ba: stringToArray(s).toByteArray());
76}
77
78//----------------------------------------------------------------------------
79// Hex
80//----------------------------------------------------------------------------
81static int enhex(uchar c)
82{
83 if (c < 10)
84 return c + '0';
85 else if (c < 16)
86 return c - 10 + 'a';
87 else
88 return -1;
89}
90
91static int dehex(char c)
92{
93 if (c >= 'a' && c <= 'f')
94 return c - 'a' + 10;
95 else if (c >= 'A' && c <= 'F')
96 return c - 'A' + 10;
97 else if (c >= '0' && c <= '9')
98 return c - '0';
99 else
100 return -1;
101}
102
103Hex::Hex(Direction dir)
104 : TextFilter(dir)
105{
106 clear();
107}
108
109void Hex::clear()
110{
111 partial = false;
112 _ok = true;
113}
114
115MemoryRegion Hex::update(const MemoryRegion &m)
116{
117 const QByteArray a = m.toByteArray();
118 if (_dir == Encode) {
119 QByteArray out(a.size() * 2, 0);
120 int at = 0;
121 int c;
122 for (const char ac : a) {
123 uchar lo = (uchar)ac & 0x0f;
124 uchar hi = (uchar)ac >> 4;
125 c = enhex(c: hi);
126 if (c == -1) {
127 _ok = false;
128 break;
129 }
130 out[at++] = (char)c;
131 c = enhex(c: lo);
132 if (c == -1) {
133 _ok = false;
134 break;
135 }
136 out[at++] = (char)c;
137 }
138 if (!_ok)
139 return MemoryRegion();
140
141 return out;
142 } else {
143 uchar lo = 0;
144 uchar hi = 0;
145 bool flag = false;
146 if (partial) {
147 hi = val;
148 flag = true;
149 }
150
151 QByteArray out(a.size() / 2, 0);
152 int at = 0;
153 int c;
154 for (const char ac : a) {
155 c = dehex(c: ac);
156 if (c == -1) {
157 _ok = false;
158 break;
159 }
160 if (flag) {
161 lo = (uchar)c;
162 uchar full = ((hi & 0x0f) << 4) + (lo & 0x0f);
163 out[at++] = full;
164 flag = false;
165 } else {
166 hi = (uchar)c;
167 flag = true;
168 }
169 }
170 if (!_ok)
171 return MemoryRegion();
172
173 if (flag) {
174 val = hi;
175 partial = true;
176 }
177 return out;
178 }
179}
180
181MemoryRegion Hex::final()
182{
183 if (partial)
184 _ok = false;
185 return MemoryRegion();
186}
187
188bool Hex::ok() const
189{
190 return _ok;
191}
192
193//----------------------------------------------------------------------------
194// Base64
195//----------------------------------------------------------------------------
196Base64::Base64(Direction dir)
197 : TextFilter(dir)
198{
199 _lb_enabled = false;
200 _lb_column = 76;
201}
202
203void Base64::clear()
204{
205 partial.resize(size: 0);
206 _ok = true;
207 col = 0;
208}
209
210bool Base64::lineBreaksEnabled() const
211{
212 return _lb_enabled;
213}
214
215int Base64::lineBreaksColumn() const
216{
217 return _lb_column;
218}
219
220void Base64::setLineBreaksEnabled(bool b)
221{
222 _lb_enabled = b;
223}
224
225void Base64::setLineBreaksColumn(int column)
226{
227 if (column > 0)
228 _lb_column = column;
229 else
230 _lb_column = 76;
231}
232
233static QByteArray b64encode(const QByteArray &s)
234{
235 int i;
236 const int len = s.size();
237 static const char tbl[] =
238 "ABCDEFGH"
239 "IJKLMNOP"
240 "QRSTUVWX"
241 "YZabcdef"
242 "ghijklmn"
243 "opqrstuv"
244 "wxyz0123"
245 "456789+/"
246 "=";
247 int a, b, c;
248
249 QByteArray p((len + 2) / 3 * 4, 0);
250 int at = 0;
251 for (i = 0; i < len; i += 3) {
252 a = ((unsigned char)s[i] & 3) << 4;
253 if (i + 1 < len) {
254 a += (unsigned char)s[i + 1] >> 4;
255 b = ((unsigned char)s[i + 1] & 0xf) << 2;
256 if (i + 2 < len) {
257 b += (unsigned char)s[i + 2] >> 6;
258 c = (unsigned char)s[i + 2] & 0x3f;
259 } else
260 c = 64;
261 } else
262 b = c = 64;
263
264 p[at++] = tbl[(unsigned char)s[i] >> 2];
265 p[at++] = tbl[a];
266 p[at++] = tbl[b];
267 p[at++] = tbl[c];
268 }
269 return p;
270}
271
272static QByteArray b64decode(const QByteArray &s, bool *ok)
273{
274 // -1 specifies invalid
275 // 64 specifies eof
276 // everything else specifies data
277
278 static const signed char tbl[] = {
279 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
280 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55,
281 56, 57, 58, 59, 60, 61, -1, -1, -1, 64, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
282 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32,
283 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, -1, -1,
284 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
285 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
286 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
287 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
288 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
289 };
290
291 // return value
292 QByteArray p;
293 *ok = true;
294
295 // this should be a multiple of 4
296 const int len = s.size();
297 if (len % 4) {
298 *ok = false;
299 return p;
300 }
301
302 p.resize(size: len / 4 * 3);
303
304 int i;
305 int at = 0;
306
307 int a, b, c, d;
308 c = d = 0;
309
310 for (i = 0; i < len; i += 4) {
311 a = tbl[(int)s[i]];
312 b = tbl[(int)s[i + 1]];
313 c = tbl[(int)s[i + 2]];
314 d = tbl[(int)s[i + 3]];
315 if ((a == 64 || b == 64) || (a < 0 || b < 0 || c < 0 || d < 0)) {
316 p.resize(size: 0);
317 *ok = false;
318 return p;
319 }
320 p[at++] = ((a & 0x3F) << 2) | ((b >> 4) & 0x03);
321 p[at++] = ((b & 0x0F) << 4) | ((c >> 2) & 0x0F);
322 p[at++] = ((c & 0x03) << 6) | ((d >> 0) & 0x3F);
323 }
324
325 if (c & 64)
326 p.resize(size: at - 2);
327 else if (d & 64)
328 p.resize(size: at - 1);
329
330 return p;
331}
332
333static int findLF(const QByteArray &in, int offset)
334{
335 for (int n = offset; n < in.size(); ++n) {
336 if (in[n] == '\n')
337 return n;
338 }
339 return -1;
340}
341
342static QByteArray insert_linebreaks(const QByteArray &s, int *col, int lfAt)
343{
344 QByteArray out = s;
345
346 const int needed = (out.size() + *col) / lfAt; // how many newlines needed?
347 if (needed > 0) {
348 const int firstlen = lfAt - *col; // length of first chunk
349 int at = firstlen + (lfAt * (needed - 1)); // position of last newline
350 const int lastlen = out.size() - at; // length of last chunk
351
352 // printf("size=%d,needed=%d,firstlen=%d,at=%d,lastlen=%d\n", out.size(), needed, firstlen, at, lastlen);
353
354 // make room
355 out.resize(size: out.size() + needed);
356
357 // move backwards
358 for (int n = 0; n < needed; ++n) {
359 char *p = out.data() + at;
360 int len;
361 if (n == 0)
362 len = lastlen;
363 else
364 len = lfAt;
365 memmove(dest: p + needed - n, src: p, n: len);
366 p[needed - n - 1] = '\n';
367 at -= lfAt;
368 }
369
370 *col = lastlen;
371 } else
372 *col += out.size();
373
374 return out;
375}
376
377static QByteArray remove_linebreaks(const QByteArray &s)
378{
379 QByteArray out = s;
380
381 int removed = 0;
382 int at = findLF(in: out, offset: 0);
383 while (at != -1) {
384 int next = findLF(in: out, offset: at + 1);
385 int len;
386 if (next != -1)
387 len = next - at;
388 else
389 len = out.size() - at;
390
391 if (len > 1) {
392 char *p = out.data() + at;
393 memmove(dest: p - removed, src: p + 1, n: len - 1);
394 }
395 ++removed;
396 at = next;
397 }
398 out.resize(size: out.size() - removed);
399
400 return out;
401}
402
403static void appendArray(QByteArray *a, const QByteArray &b)
404{
405 a->append(a: b);
406}
407
408MemoryRegion Base64::update(const MemoryRegion &m)
409{
410 QByteArray in;
411 if (_dir == Decode && _lb_enabled)
412 in = remove_linebreaks(s: m.toByteArray());
413 else
414 in = m.toByteArray();
415
416 if (in.isEmpty())
417 return MemoryRegion();
418
419 int chunk;
420 if (_dir == Encode)
421 chunk = 3;
422 else
423 chunk = 4;
424
425 const int size = partial.size() + in.size();
426 if (size < chunk) {
427 appendArray(a: &partial, b: in);
428 return MemoryRegion();
429 }
430
431 int eat = size % chunk;
432
433 // s = partial + a - eat
434 QByteArray s(partial.size() + in.size() - eat, 0);
435 memcpy(dest: s.data(), src: partial.data(), n: partial.size());
436 memcpy(dest: s.data() + partial.size(), src: in.data(), n: in.size() - eat);
437
438 partial.resize(size: eat);
439 memcpy(dest: partial.data(), src: in.data() + in.size() - eat, n: eat);
440
441 if (_dir == Encode) {
442 if (_lb_enabled)
443 return insert_linebreaks(s: b64encode(s), col: &col, lfAt: _lb_column);
444 else
445 return b64encode(s);
446 } else {
447 bool ok;
448 const QByteArray out = b64decode(s, ok: &ok);
449 if (!ok)
450 _ok = false;
451 return out;
452 }
453}
454
455MemoryRegion Base64::final()
456{
457 if (_dir == Encode) {
458 if (_lb_enabled)
459 return insert_linebreaks(s: b64encode(s: partial), col: &col, lfAt: _lb_column);
460 else
461 return b64encode(s: partial);
462 } else {
463 bool ok;
464 const QByteArray out = b64decode(s: partial, ok: &ok);
465 if (!ok)
466 _ok = false;
467 return out;
468 }
469}
470
471bool Base64::ok() const
472{
473 return _ok;
474}
475
476}
477

source code of qca/src/qca_textfilter.cpp