aboutsummaryrefslogtreecommitdiff
path: root/src/mime.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mime.c')
-rw-r--r--src/mime.c614
1 files changed, 614 insertions, 0 deletions
diff --git a/src/mime.c b/src/mime.c
new file mode 100644
index 0000000..6807af5
--- /dev/null
+++ b/src/mime.c
@@ -0,0 +1,614 @@
1/*=========================================================================*\
2* Encoding support functions
3* LuaSocket toolkit
4*
5* RCS ID: $Id$
6\*=========================================================================*/
7#include <string.h>
8
9#include <lua.h>
10#include <lauxlib.h>
11
12#include "luasocket.h"
13#include "mime.h"
14
15/*=========================================================================*\
16* Don't want to trust escape character constants
17\*=========================================================================*/
18#define CR 0x0D
19#define LF 0x0A
20#define HT 0x09
21#define SP 0x20
22
23typedef unsigned char UC;
24static const UC CRLF[2] = {CR, LF};
25static const UC EQCRLF[3] = {'=', CR, LF};
26
27/*=========================================================================*\
28* Internal function prototypes.
29\*=========================================================================*/
30static int mime_global_fmt(lua_State *L);
31static int mime_global_b64(lua_State *L);
32static int mime_global_unb64(lua_State *L);
33static int mime_global_qp(lua_State *L);
34static int mime_global_unqp(lua_State *L);
35static int mime_global_qpfmt(lua_State *L);
36static int mime_global_eol(lua_State *L);
37
38static void b64fill(UC *b64unbase);
39static size_t b64encode(UC c, UC *input, size_t size, luaL_Buffer *buffer);
40static size_t b64pad(const UC *input, size_t size, luaL_Buffer *buffer);
41static size_t b64decode(UC c, UC *input, size_t size, luaL_Buffer *buffer);
42
43static void qpfill(UC *qpclass, UC *qpunbase);
44static void qpquote(UC c, luaL_Buffer *buffer);
45static size_t qpdecode(UC c, UC *input, size_t size, luaL_Buffer *buffer);
46static size_t qpencode(UC c, UC *input, size_t size,
47 const UC *marker, luaL_Buffer *buffer);
48
49/* code support functions */
50static luaL_reg func[] = {
51 { "eol", mime_global_eol },
52 { "qp", mime_global_qp },
53 { "unqp", mime_global_unqp },
54 { "qpfmt", mime_global_qpfmt },
55 { "b64", mime_global_b64 },
56 { "unb64", mime_global_unb64 },
57 { "fmt", mime_global_fmt },
58 { NULL, NULL }
59};
60
61/*-------------------------------------------------------------------------*\
62* Quoted-printable globals
63\*-------------------------------------------------------------------------*/
64static UC qpclass[256];
65static UC qpbase[] = "0123456789ABCDEF";
66static UC qpunbase[256];
67enum {QP_PLAIN, QP_QUOTED, QP_CR, QP_IF_LAST};
68
69/*-------------------------------------------------------------------------*\
70* Base64 globals
71\*-------------------------------------------------------------------------*/
72static const UC b64base[] =
73 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
74static UC b64unbase[256];
75
76/*=========================================================================*\
77* Exported functions
78\*=========================================================================*/
79/*-------------------------------------------------------------------------*\
80* Initializes module
81\*-------------------------------------------------------------------------*/
82void mime_open(lua_State *L)
83{
84 lua_pushstring(L, LUASOCKET_LIBNAME);
85 lua_gettable(L, LUA_GLOBALSINDEX);
86 if (lua_isnil(L, -1)) {
87 lua_pop(L, 1);
88 lua_newtable(L);
89 lua_pushstring(L, LUASOCKET_LIBNAME);
90 lua_pushvalue(L, -2);
91 lua_settable(L, LUA_GLOBALSINDEX);
92 }
93 lua_pushstring(L, "mime");
94 lua_newtable(L);
95 luaL_openlib(L, NULL, func, 0);
96 lua_settable(L, -3);
97 lua_pop(L, 1);
98 /* initialize lookup tables */
99 qpfill(qpclass, qpunbase);
100 b64fill(b64unbase);
101}
102
103/*=========================================================================*\
104* Global Lua functions
105\*=========================================================================*/
106/*-------------------------------------------------------------------------*\
107* Incrementaly breaks a string into lines
108* A, n = fmt(B, length, left)
109* A is a copy of B, broken into lines of at most 'length' bytes.
110* Left is how many bytes are left in the first line of B. 'n' is the number
111* of bytes left in the last line of A.
112\*-------------------------------------------------------------------------*/
113static int mime_global_fmt(lua_State *L)
114{
115 size_t size = 0;
116 const UC *input = lua_isnil(L, 1)? NULL: luaL_checklstring(L, 1, &size);
117 const UC *last = input + size;
118 int length = (int) luaL_checknumber(L, 2);
119 int left = (int) luaL_optnumber(L, 3, length);
120 const UC *marker = luaL_optstring(L, 4, CRLF);
121 luaL_Buffer buffer;
122 luaL_buffinit(L, &buffer);
123 while (input < last) {
124 luaL_putchar(&buffer, *input++);
125 if (--left <= 0) {
126 luaL_addstring(&buffer, marker);
127 left = length;
128 }
129 }
130 if (!input && left < length) {
131 luaL_addstring(&buffer, marker);
132 left = length;
133 }
134 luaL_pushresult(&buffer);
135 lua_pushnumber(L, left);
136 return 2;
137}
138
139/*-------------------------------------------------------------------------*\
140* Fill base64 decode map.
141\*-------------------------------------------------------------------------*/
142static void b64fill(UC *b64unbase)
143{
144 int i;
145 for (i = 0; i < 255; i++) b64unbase[i] = 255;
146 for (i = 0; i < 64; i++) b64unbase[b64base[i]] = i;
147 b64unbase['='] = 0;
148}
149
150/*-------------------------------------------------------------------------*\
151* Acumulates bytes in input buffer until 3 bytes are available.
152* Translate the 3 bytes into Base64 form and append to buffer.
153* Returns new number of bytes in buffer.
154\*-------------------------------------------------------------------------*/
155static size_t b64encode(UC c, UC *input, size_t size,
156 luaL_Buffer *buffer)
157{
158 input[size++] = c;
159 if (size == 3) {
160 UC code[4];
161 unsigned long value = 0;
162 value += input[0]; value <<= 8;
163 value += input[1]; value <<= 8;
164 value += input[2];
165 code[3] = b64base[value & 0x3f]; value >>= 6;
166 code[2] = b64base[value & 0x3f]; value >>= 6;
167 code[1] = b64base[value & 0x3f]; value >>= 6;
168 code[0] = b64base[value];
169 luaL_addlstring(buffer, code, 4);
170 size = 0;
171 }
172 return size;
173}
174
175/*-------------------------------------------------------------------------*\
176* Encodes the Base64 last 1 or 2 bytes and adds padding '='
177* Result, if any, is appended to buffer.
178* Returns 0.
179\*-------------------------------------------------------------------------*/
180static size_t b64pad(const UC *input, size_t size,
181 luaL_Buffer *buffer)
182{
183 unsigned long value = 0;
184 UC code[4] = "====";
185 switch (size) {
186 case 1:
187 value = input[0] << 4;
188 code[1] = b64base[value & 0x3f]; value >>= 6;
189 code[0] = b64base[value];
190 luaL_addlstring(buffer, code, 4);
191 break;
192 case 2:
193 value = input[0]; value <<= 8;
194 value |= input[1]; value <<= 2;
195 code[2] = b64base[value & 0x3f]; value >>= 6;
196 code[1] = b64base[value & 0x3f]; value >>= 6;
197 code[0] = b64base[value];
198 luaL_addlstring(buffer, code, 4);
199 break;
200 case 0: /* fall through */
201 default:
202 break;
203 }
204 return 0;
205}
206
207/*-------------------------------------------------------------------------*\
208* Acumulates bytes in input buffer until 4 bytes are available.
209* Translate the 4 bytes from Base64 form and append to buffer.
210* Returns new number of bytes in buffer.
211\*-------------------------------------------------------------------------*/
212static size_t b64decode(UC c, UC *input, size_t size,
213 luaL_Buffer *buffer)
214{
215
216 /* ignore invalid characters */
217 if (b64unbase[c] > 64) return size;
218 input[size++] = c;
219 /* decode atom */
220 if (size == 4) {
221 UC decoded[3];
222 int valid, value = 0;
223 value = b64unbase[input[0]]; value <<= 6;
224 value |= b64unbase[input[1]]; value <<= 6;
225 value |= b64unbase[input[2]]; value <<= 6;
226 value |= b64unbase[input[3]];
227 decoded[2] = (UC) (value & 0xff); value >>= 8;
228 decoded[1] = (UC) (value & 0xff); value >>= 8;
229 decoded[0] = (UC) value;
230 /* take care of paddding */
231 valid = (input[2] == '=') ? 1 : (input[3] == '=') ? 2 : 3;
232 luaL_addlstring(buffer, decoded, valid);
233 return 0;
234 /* need more data */
235 } else return size;
236}
237
238/*-------------------------------------------------------------------------*\
239* Incrementally applies the Base64 transfer content encoding to a string
240* A, B = b64(C, D)
241* A is the encoded version of the largest prefix of C .. D that is
242* divisible by 3. B has the remaining bytes of C .. D, *without* encoding.
243* The easiest thing would be to concatenate the two strings and
244* encode the result, but we can't afford that or Lua would dupplicate
245* every chunk we received.
246\*-------------------------------------------------------------------------*/
247static int mime_global_b64(lua_State *L)
248{
249 UC atom[3];
250 size_t isize = 0, asize = 0;
251 const UC *input = luaL_checklstring(L, 1, &isize);
252 const UC *last = input + isize;
253 luaL_Buffer buffer;
254 luaL_buffinit(L, &buffer);
255 while (input < last)
256 asize = b64encode(*input++, atom, asize, &buffer);
257 input = luaL_optlstring(L, 2, NULL, &isize);
258 if (input) {
259 last = input + isize;
260 while (input < last)
261 asize = b64encode(*input++, atom, asize, &buffer);
262 } else
263 asize = b64pad(atom, asize, &buffer);
264 luaL_pushresult(&buffer);
265 lua_pushlstring(L, atom, asize);
266 return 2;
267}
268
269/*-------------------------------------------------------------------------*\
270* Incrementally removes the Base64 transfer content encoding from a string
271* A, B = b64(C, D)
272* A is the encoded version of the largest prefix of C .. D that is
273* divisible by 4. B has the remaining bytes of C .. D, *without* encoding.
274\*-------------------------------------------------------------------------*/
275static int mime_global_unb64(lua_State *L)
276{
277 UC atom[4];
278 size_t isize = 0, asize = 0;
279 const UC *input = luaL_checklstring(L, 1, &isize);
280 const UC *last = input + isize;
281 luaL_Buffer buffer;
282 luaL_buffinit(L, &buffer);
283 while (input < last)
284 asize = b64decode(*input++, atom, asize, &buffer);
285 input = luaL_optlstring(L, 2, NULL, &isize);
286 if (input) {
287 last = input + isize;
288 while (input < last)
289 asize = b64decode(*input++, atom, asize, &buffer);
290 }
291 luaL_pushresult(&buffer);
292 lua_pushlstring(L, atom, asize);
293 return 2;
294}
295
296/*-------------------------------------------------------------------------*\
297* Quoted-printable encoding scheme
298* all (except CRLF in text) can be =XX
299* CLRL in not text must be =XX=XX
300* 33 through 60 inclusive can be plain
301* 62 through 120 inclusive can be plain
302* 9 and 32 can be plain, unless in the end of a line, where must be =XX
303* encoded lines must be no longer than 76 not counting CRLF
304* soft line-break are =CRLF
305* !"#$@[\]^`{|}~ should be =XX for EBCDIC compatibility
306* To encode one byte, we need to see the next two.
307* Worst case is when we see a space, and wonder if a CRLF is comming
308\*-------------------------------------------------------------------------*/
309/*-------------------------------------------------------------------------*\
310* Split quoted-printable characters into classes
311* Precompute reverse map for encoding
312\*-------------------------------------------------------------------------*/
313static void qpfill(UC *qpclass, UC *qpunbase)
314{
315 int i;
316 for (i = 0; i < 256; i++) qpclass[i] = QP_QUOTED;
317 for (i = 33; i <= 60; i++) qpclass[i] = QP_PLAIN;
318 for (i = 62; i <= 120; i++) qpclass[i] = QP_PLAIN;
319 qpclass[HT] = QP_IF_LAST; qpclass[SP] = QP_IF_LAST;
320 qpclass['!'] = QP_QUOTED; qpclass['"'] = QP_QUOTED;
321 qpclass['#'] = QP_QUOTED; qpclass['$'] = QP_QUOTED;
322 qpclass['@'] = QP_QUOTED; qpclass['['] = QP_QUOTED;
323 qpclass['\\'] = QP_QUOTED; qpclass[']'] = QP_QUOTED;
324 qpclass['^'] = QP_QUOTED; qpclass['`'] = QP_QUOTED;
325 qpclass['{'] = QP_QUOTED; qpclass['|'] = QP_QUOTED;
326 qpclass['}'] = QP_QUOTED; qpclass['~'] = QP_QUOTED;
327 qpclass['}'] = QP_QUOTED; qpclass[CR] = QP_CR;
328 for (i = 0; i < 256; i++) qpunbase[i] = 255;
329 qpunbase['0'] = 0; qpunbase['1'] = 1; qpunbase['2'] = 2;
330 qpunbase['3'] = 3; qpunbase['4'] = 4; qpunbase['5'] = 5;
331 qpunbase['6'] = 6; qpunbase['7'] = 7; qpunbase['8'] = 8;
332 qpunbase['9'] = 9; qpunbase['A'] = 10; qpunbase['a'] = 10;
333 qpunbase['B'] = 11; qpunbase['b'] = 11; qpunbase['C'] = 12;
334 qpunbase['c'] = 12; qpunbase['D'] = 13; qpunbase['d'] = 13;
335 qpunbase['E'] = 14; qpunbase['e'] = 14; qpunbase['F'] = 15;
336 qpunbase['f'] = 15;
337}
338
339/*-------------------------------------------------------------------------*\
340* Output one character in form =XX
341\*-------------------------------------------------------------------------*/
342static void qpquote(UC c, luaL_Buffer *buffer)
343{
344 luaL_putchar(buffer, '=');
345 luaL_putchar(buffer, qpbase[c >> 4]);
346 luaL_putchar(buffer, qpbase[c & 0x0F]);
347}
348
349/*-------------------------------------------------------------------------*\
350* Accumulate characters until we are sure about how to deal with them.
351* Once we are sure, output the to the buffer, in the correct form.
352\*-------------------------------------------------------------------------*/
353static size_t qpencode(UC c, UC *input, size_t size,
354 const UC *marker, luaL_Buffer *buffer)
355{
356 input[size++] = c;
357 /* deal with all characters we can have */
358 while (size > 0) {
359 switch (qpclass[input[0]]) {
360 /* might be the CR of a CRLF sequence */
361 case QP_CR:
362 if (size < 2) return size;
363 if (input[1] == LF) {
364 luaL_addstring(buffer, marker);
365 return 0;
366 } else qpquote(input[0], buffer);
367 break;
368 /* might be a space and that has to be quoted if last in line */
369 case QP_IF_LAST:
370 if (size < 3) return size;
371 /* if it is the last, quote it and we are done */
372 if (input[1] == CR && input[2] == LF) {
373 qpquote(input[0], buffer);
374 luaL_addstring(buffer, marker);
375 return 0;
376 } else luaL_putchar(buffer, input[0]);
377 break;
378 /* might have to be quoted always */
379 case QP_QUOTED:
380 qpquote(input[0], buffer);
381 break;
382 /* might never have to be quoted */
383 default:
384 luaL_putchar(buffer, input[0]);
385 break;
386 }
387 input[0] = input[1]; input[1] = input[2];
388 size--;
389 }
390 return 0;
391}
392
393/*-------------------------------------------------------------------------*\
394* Deal with the final characters
395\*-------------------------------------------------------------------------*/
396static void qppad(UC *input, size_t size, luaL_Buffer *buffer)
397{
398 size_t i;
399 for (i = 0; i < size; i++) {
400 if (qpclass[input[i]] == QP_PLAIN) luaL_putchar(buffer, input[i]);
401 else qpquote(input[i], buffer);
402 }
403 luaL_addstring(buffer, EQCRLF);
404}
405
406/*-------------------------------------------------------------------------*\
407* Incrementally converts a string to quoted-printable
408* A, B = qp(C, D, marker)
409* Crlf is the text to be used to replace CRLF sequences found in A.
410* A is the encoded version of the largest prefix of C .. D that
411* can be encoded without doubts.
412* B has the remaining bytes of C .. D, *without* encoding.
413\*-------------------------------------------------------------------------*/
414static int mime_global_qp(lua_State *L)
415{
416
417 size_t asize = 0, isize = 0;
418 UC atom[3];
419 const UC *input = lua_isnil(L, 1) ? NULL: luaL_checklstring(L, 1, &isize);
420 const UC *last = input + isize;
421 const UC *marker = luaL_optstring(L, 3, CRLF);
422 luaL_Buffer buffer;
423 luaL_buffinit(L, &buffer);
424 while (input < last)
425 asize = qpencode(*input++, atom, asize, marker, &buffer);
426 input = luaL_optlstring(L, 2, NULL, &isize);
427 if (input) {
428 last = input + isize;
429 while (input < last)
430 asize = qpencode(*input++, atom, asize, marker, &buffer);
431 } else qppad(atom, asize, &buffer);
432 luaL_pushresult(&buffer);
433 lua_pushlstring(L, atom, asize);
434 return 2;
435}
436
437/*-------------------------------------------------------------------------*\
438* Accumulate characters until we are sure about how to deal with them.
439* Once we are sure, output the to the buffer, in the correct form.
440\*-------------------------------------------------------------------------*/
441static size_t qpdecode(UC c, UC *input, size_t size,
442 luaL_Buffer *buffer)
443{
444 input[size++] = c;
445 /* deal with all characters we can deal */
446 while (size > 0) {
447 int c, d;
448 switch (input[0]) {
449 /* if we have an escape character */
450 case '=':
451 if (size < 3) return size;
452 /* eliminate soft line break */
453 if (input[1] == CR && input[2] == LF) return 0;
454 /* decode quoted representation */
455 c = qpunbase[input[1]]; d = qpunbase[input[2]];
456 /* if it is an invalid, do not decode */
457 if (c > 15 || d > 15) luaL_addlstring(buffer, input, 3);
458 else luaL_putchar(buffer, (c << 4) + d);
459 return 0;
460 case CR:
461 if (size < 2) return size;
462 if (input[1] == LF) luaL_addlstring(buffer, input, 2);
463 return 0;
464 default:
465 if (input[0] == HT || (input[0] > 31 && input[0] < 127))
466 luaL_putchar(buffer, input[0]);
467 return 0;
468 }
469 input[0] = input[1]; input[1] = input[2];
470 size--;
471 }
472 return 0;
473}
474
475/*-------------------------------------------------------------------------*\
476* Incrementally decodes a string in quoted-printable
477* A, B = qp(C, D)
478* A is the decoded version of the largest prefix of C .. D that
479* can be decoded without doubts.
480* B has the remaining bytes of C .. D, *without* decoding.
481\*-------------------------------------------------------------------------*/
482static int mime_global_unqp(lua_State *L)
483{
484
485 size_t asize = 0, isize = 0;
486 UC atom[3];
487 const UC *input = lua_isnil(L, 1) ? NULL: luaL_checklstring(L, 1, &isize);
488 const UC *last = input + isize;
489 luaL_Buffer buffer;
490 luaL_buffinit(L, &buffer);
491 while (input < last)
492 asize = qpdecode(*input++, atom, asize, &buffer);
493 input = luaL_optlstring(L, 2, NULL, &isize);
494 if (input) {
495 last = input + isize;
496 while (input < last)
497 asize = qpdecode(*input++, atom, asize, &buffer);
498 }
499 luaL_pushresult(&buffer);
500 lua_pushlstring(L, atom, asize);
501 return 2;
502}
503
504/*-------------------------------------------------------------------------*\
505* Incrementally breaks a quoted-printed string into lines
506* A, n = qpfmt(B, length, left)
507* A is a copy of B, broken into lines of at most 'length' bytes.
508* Left is how many bytes are left in the first line of B. 'n' is the number
509* of bytes left in the last line of A.
510* There are two complications: lines can't be broken in the middle
511* of an encoded =XX, and there might be line breaks already
512\*-------------------------------------------------------------------------*/
513static int mime_global_qpfmt(lua_State *L)
514{
515 size_t size = 0;
516 const UC *input = lua_isnil(L, 1)? NULL: luaL_checklstring(L, 1, &size);
517 const UC *last = input + size;
518 int length = (int) luaL_checknumber(L, 2);
519 int left = (int) luaL_optnumber(L, 3, length);
520 luaL_Buffer buffer;
521 luaL_buffinit(L, &buffer);
522 while (input < last) {
523 left--;
524 switch (*input) {
525 case '=':
526 /* if there's no room in this line for the quoted char,
527 * output a soft line break now */
528 if (left <= 3) {
529 luaL_addstring(&buffer, EQCRLF);
530 left = length;
531 }
532 break;
533 /* \r\n starts a new line */
534 case CR:
535 break;
536 case LF:
537 left = length;
538 break;
539 default:
540 /* if in last column, output a soft line break */
541 if (left <= 1) {
542 luaL_addstring(&buffer, EQCRLF);
543 left = length;
544 }
545 }
546 luaL_putchar(&buffer, *input);
547 input++;
548 }
549 if (!input && left < length) {
550 luaL_addstring(&buffer, EQCRLF);
551 left = length;
552 }
553 luaL_pushresult(&buffer);
554 lua_pushnumber(L, left);
555 return 2;
556}
557
558/*-------------------------------------------------------------------------*\
559* Here is what we do: \n, \r and \f are considered candidates for line
560* break. We issue *one* new line marker if any of them is seen alone, or
561* followed by a different one. That is, \n\n, \r\r and \f\f will issue two
562* end of line markers each, but \r\n, \n\r, \r\f etc will only issue *one*
563* marker. This covers Mac OS, Mac OS X, VMS, Unix and DOS, as well as
564* probably other more obscure conventions.
565\*-------------------------------------------------------------------------*/
566#define eolcandidate(c) (c == CR || c == LF)
567static size_t eolconvert(UC c, UC *input, size_t size,
568 const UC *marker, luaL_Buffer *buffer)
569{
570 input[size++] = c;
571 /* deal with all characters we can deal */
572 if (eolcandidate(input[0])) {
573 if (size < 2) return size;
574 luaL_addstring(buffer, marker);
575 if (eolcandidate(input[1])) {
576 if (input[0] == input[1]) luaL_addstring(buffer, marker);
577 } else luaL_putchar(buffer, input[1]);
578 return 0;
579 } else {
580 luaL_putchar(buffer, input[0]);
581 return 0;
582 }
583}
584
585/*-------------------------------------------------------------------------*\
586* Converts a string to uniform EOL convention.
587* A, B = eol(C, D, marker)
588* A is the converted version of the largest prefix of C .. D that
589* can be converted without doubts.
590* B has the remaining bytes of C .. D, *without* convertion.
591\*-------------------------------------------------------------------------*/
592static int mime_global_eol(lua_State *L)
593{
594 size_t asize = 0, isize = 0;
595 UC atom[2];
596 const UC *input = lua_isnil(L, 1)? NULL: luaL_checklstring(L, 1, &isize);
597 const UC *last = input + isize;
598 const UC *marker = luaL_optstring(L, 3, CRLF);
599 luaL_Buffer buffer;
600 luaL_buffinit(L, &buffer);
601 while (input < last)
602 asize = eolconvert(*input++, atom, asize, marker, &buffer);
603 input = luaL_optlstring(L, 2, NULL, &isize);
604 if (input) {
605 last = input + isize;
606 while (input < last)
607 asize = eolconvert(*input++, atom, asize, marker, &buffer);
608 /* if there is something in atom, it's one character, and it
609 * is a candidate. so we output a new line */
610 } else if (asize > 0) luaL_addstring(&buffer, marker);
611 luaL_pushresult(&buffer);
612 lua_pushlstring(L, atom, asize);
613 return 2;
614}