text: /*=========================================================================*\
  * MIME support functions
  * LuaSocket toolkit
  *
  * RCS ID: $Id: mime.c,v 1.28 2005/11/20 07:20:23 diego Exp $
  \*=========================================================================*/
  #include <string.h>
  
  #define LUA_LIB
  #include "lua.h"
  #include "lauxlib.h"
  
  #if !defined(LUA_VERSION_NUM) || (LUA_VERSION_NUM < 501)
  #include "compat-5.1.h"
  #endif
  
  #include "mime.h"
  
  /*=========================================================================*\
  * Don't want to trust escape character constants
  \*=========================================================================*/
  typedef unsigned char UC;
  static const char CRLF[] = "\r\n";
  static const char EQCRLF[] = "=\r\n";
  
  /*=========================================================================*\
  * Internal function prototypes.
  \*=========================================================================*/
  static int mime_global_wrp(lua_State *L);
  static int mime_global_b64(lua_State *L);
  static int mime_global_unb64(lua_State *L);
  static int mime_global_qp(lua_State *L);
  static int mime_global_unqp(lua_State *L);
  static int mime_global_qpwrp(lua_State *L);
  static int mime_global_eol(lua_State *L);
  static int mime_global_dot(lua_State *L);
  
  static size_t dot(int c, size_t state, luaL_Buffer *buffer);
  static void b64setup(UC *b64unbase);
  static size_t b64encode(UC c, UC *input, size_t size, luaL_Buffer *buffer);
  static size_t b64pad(const UC *input, size_t size, luaL_Buffer *buffer);
  static size_t b64decode(UC c, UC *input, size_t size, luaL_Buffer *buffer);
  
  static void qpsetup(UC *qpclass, UC *qpunbase);
  static void qpquote(UC c, luaL_Buffer *buffer);
  static size_t qpdecode(UC c, UC *input, size_t size, luaL_Buffer *buffer);
  static size_t qpencode(UC c, UC *input, size_t size, 
          const char *marker, luaL_Buffer *buffer);
  static size_t qppad(UC *input, size_t size, luaL_Buffer *buffer);
  
  /* code support functions */
  static luaL_reg func[] = {
      { "dot", mime_global_dot },
      { "b64", mime_global_b64 },
      { "eol", mime_global_eol },
      { "qp", mime_global_qp },
      { "qpwrp", mime_global_qpwrp },
      { "unb64", mime_global_unb64 },
      { "unqp", mime_global_unqp },
      { "wrp", mime_global_wrp },
      { NULL, NULL }
  };
  
  /*-------------------------------------------------------------------------*\
  * Quoted-printable globals
  \*-------------------------------------------------------------------------*/
  static UC qpclass[256];
  static UC qpbase[] = "0123456789ABCDEF";
  static UC qpunbase[256];
  enum {QP_PLAIN, QP_QUOTED, QP_CR, QP_IF_LAST};
  
  /*-------------------------------------------------------------------------*\
  * Base64 globals
  \*-------------------------------------------------------------------------*/
  static const UC b64base[] =
          "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  static UC b64unbase[256];
  
  /*=========================================================================*\
  * Exported functions
  \*=========================================================================*/
  /*-------------------------------------------------------------------------*\
  * Initializes module
  \*-------------------------------------------------------------------------*/
  MIME_API int luaopen_mime_core(lua_State *L)
  {
      luaL_openlib(L, "mime", func, 0);
      /* make version string available to scripts */
      lua_pushstring(L, "_VERSION");
      lua_pushstring(L, MIME_VERSION);
      lua_rawset(L, -3);
      /* initialize lookup tables */
      qpsetup(qpclass, qpunbase);
      b64setup(b64unbase);
      return 1;
  }
  
  /*=========================================================================*\
  * Global Lua functions
  \*=========================================================================*/
  /*-------------------------------------------------------------------------*\
  * Incrementaly breaks a string into lines. The string can have CRLF breaks.
  * A, n = wrp(l, B, length)
  * A is a copy of B, broken into lines of at most 'length' bytes. 
  * 'l' is how many bytes are left for the first line of B. 
  * 'n' is the number of bytes left in the last line of A. 
  \*-------------------------------------------------------------------------*/
  static int mime_global_wrp(lua_State *L)
  {
      size_t size = 0;
      int left = (int) luaL_checknumber(L, 1);
      const UC *input = (UC *) luaL_optlstring(L, 2, NULL, &size);
      const UC *last = input + size;
      int length = (int) luaL_optnumber(L, 3, 76);
      luaL_Buffer buffer;
      /* end of input black-hole */
      if (!input) {
          /* if last line has not been terminated, add a line break */
          if (left < length) lua_pushstring(L, CRLF);
          /* otherwise, we are done */
          else lua_pushnil(L);
          lua_pushnumber(L, length);
          return 2;
      } 
      luaL_buffinit(L, &buffer);
      while (input < last) {
          switch (*input) {
              case '\r':
                  break;
              case '\n':
                  luaL_addstring(&buffer, CRLF);
                  left = length;
                  break;
              default:
                  if (left <= 0) {
                      left = length;
                      luaL_addstring(&buffer, CRLF);
                  }
                  luaL_putchar(&buffer, *input);
                  left--;
                  break;
          }
          input++;
      }
      luaL_pushresult(&buffer);
      lua_pushnumber(L, left);
      return 2;
  }
  
  /*-------------------------------------------------------------------------*\
  * Fill base64 decode map. 
  \*-------------------------------------------------------------------------*/
  static void b64setup(UC *b64unbase) 
  {
      int i;
      for (i = 0; i <= 255; i++) b64unbase[i] = (UC) 255;
      for (i = 0; i < 64; i++) b64unbase[b64base[i]] = (UC) i;
      b64unbase['='] = 0;
  }
  
  /*-------------------------------------------------------------------------*\
  * Acumulates bytes in input buffer until 3 bytes are available. 
  * Translate the 3 bytes into Base64 form and append to buffer.
  * Returns new number of bytes in buffer.
  \*-------------------------------------------------------------------------*/
  static size_t b64encode(UC c, UC *input, size_t size, 
          luaL_Buffer *buffer)
  {
      input[size++] = c;
      if (size == 3) {
          UC code[4];
          unsigned long value = 0;
          value += input[0]; value <<= 8;
          value += input[1]; value <<= 8;
          value += input[2]; 
          code[3] = b64base[value & 0x3f]; value >>= 6;
          code[2] = b64base[value & 0x3f]; value >>= 6;
          code[1] = b64base[value & 0x3f]; value >>= 6;
          code[0] = b64base[value];
          luaL_addlstring(buffer, (char *) code, 4);
          size = 0;
      }
      return size;
  }
  
  /*-------------------------------------------------------------------------*\
  * Encodes the Base64 last 1 or 2 bytes and adds padding '=' 
  * Result, if any, is appended to buffer.
  * Returns 0.
  \*-------------------------------------------------------------------------*/
  static size_t b64pad(const UC *input, size_t size, 
          luaL_Buffer *buffer)
  {
      unsigned long value = 0;
      UC code[4] = {'=', '=', '=', '='};
      switch (size) {
          case 1:
              value = input[0] << 4;
              code[1] = b64base[value & 0x3f]; value >>= 6;
              code[0] = b64base[value];
              luaL_addlstring(buffer, (char *) code, 4);
              break;
          case 2:
              value = input[0]; value <<= 8; 
              value |= input[1]; value <<= 2;
              code[2] = b64base[value & 0x3f]; value >>= 6;
              code[1] = b64base[value & 0x3f]; value >>= 6;
              code[0] = b64base[value];
              luaL_addlstring(buffer, (char *) code, 4);
              break;
          default:
              break;
      }
      return 0;
  }
  
  /*-------------------------------------------------------------------------*\
  * Acumulates bytes in input buffer until 4 bytes are available. 
  * Translate the 4 bytes from Base64 form and append to buffer.
  * Returns new number of bytes in buffer.
  \*-------------------------------------------------------------------------*/
  static size_t b64decode(UC c, UC *input, size_t size, 
          luaL_Buffer *buffer)
  {
      /* ignore invalid characters */
      if (b64unbase[c] > 64) return size;
      input[size++] = c;
      /* decode atom */
      if (size == 4) {
          UC decoded[3];
          int valid, value = 0;
          value =  b64unbase[input[0]]; value <<= 6;
          value |= b64unbase[input[1]]; value <<= 6;
          value |= b64unbase[input[2]]; value <<= 6;
          value |= b64unbase[input[3]];
          decoded[2] = (UC) (value & 0xff); value >>= 8;
          decoded[1] = (UC) (value & 0xff); value >>= 8;
          decoded[0] = (UC) value;
          /* take care of paddding */
          valid = (input[2] == '=') ? 1 : (input[3] == '=') ? 2 : 3; 
          luaL_addlstring(buffer, (char *) decoded, valid);
          return 0;
      /* need more data */
      } else return size;
  }
  
  /*-------------------------------------------------------------------------*\
  * Incrementally applies the Base64 transfer content encoding to a string
  * A, B = b64(C, D)
  * A is the encoded version of the largest prefix of C .. D that is
  * divisible by 3. B has the remaining bytes of C .. D, *without* encoding.
  * The easiest thing would be to concatenate the two strings and 
  * encode the result, but we can't afford that or Lua would dupplicate
  * every chunk we received.
  \*-------------------------------------------------------------------------*/
  static int mime_global_b64(lua_State *L)
  {
      UC atom[3];
      size_t isize = 0, asize = 0;
      const UC *input = (UC *) luaL_optlstring(L, 1, NULL, &isize);
      const UC *last = input + isize;
      luaL_Buffer buffer;
      /* end-of-input blackhole */
      if (!input) {
          lua_pushnil(L);
          lua_pushnil(L);
          return 2;
      }
      /* process first part of the input */
      luaL_buffinit(L, &buffer);
      while (input < last) 
          asize = b64encode(*input++, atom, asize, &buffer);
      input = (UC *) luaL_optlstring(L, 2, NULL, &isize);
      /* if second part is nil, we are done */
      if (!input) {
          asize = b64pad(atom, asize, &buffer);
          luaL_pushresult(&buffer);
          if (!(*lua_tostring(L, -1))) lua_pushnil(L);
          lua_pushnil(L);
          return 2;
      }
      /* otherwise process the second part */
      last = input + isize;
      while (input < last) 
          asize = b64encode(*input++, atom, asize, &buffer);
      luaL_pushresult(&buffer);
      lua_pushlstring(L, (char *) atom, asize);
      return 2;
  }
  
  /*-------------------------------------------------------------------------*\
  * Incrementally removes the Base64 transfer content encoding from a string
  * A, B = b64(C, D)
  * A is the encoded version of the largest prefix of C .. D that is
  * divisible by 4. B has the remaining bytes of C .. D, *without* encoding.
  \*-------------------------------------------------------------------------*/
  static int mime_global_unb64(lua_State *L)
  {
      UC atom[4];
      size_t isize = 0, asize = 0;
      const UC *input = (UC *) luaL_optlstring(L, 1, NULL, &isize);
      const UC *last = input + isize;
      luaL_Buffer buffer;
      /* end-of-input blackhole */
      if (!input) {
          lua_pushnil(L);
          lua_pushnil(L);
          return 2;
      }
      /* process first part of the input */
      luaL_buffinit(L, &buffer);
      while (input < last) 
          asize = b64decode(*input++, atom, asize, &buffer);
      input = (UC *) luaL_optlstring(L, 2, NULL, &isize);
      /* if second is nil, we are done */
      if (!input) {
          luaL_pushresult(&buffer);
          if (!(*lua_tostring(L, -1))) lua_pushnil(L);
          lua_pushnil(L);
          return 2;
      }
      /* otherwise, process the rest of the input */
      last = input + isize;
      while (input < last) 
          asize = b64decode(*input++, atom, asize, &buffer);
      luaL_pushresult(&buffer);
      lua_pushlstring(L, (char *) atom, asize);
      return 2;
  }
  
  /*-------------------------------------------------------------------------*\
  * Quoted-printable encoding scheme
  * all (except CRLF in text) can be =XX
  * CLRL in not text must be =XX=XX
  * 33 through 60 inclusive can be plain
  * 62 through 126 inclusive can be plain
  * 9 and 32 can be plain, unless in the end of a line, where must be =XX
  * encoded lines must be no longer than 76 not counting CRLF
  * soft line-break are =CRLF
  * To encode one byte, we need to see the next two. 
  * Worst case is when we see a space, and wonder if a CRLF is comming
  \*-------------------------------------------------------------------------*/
  /*-------------------------------------------------------------------------*\
  * Split quoted-printable characters into classes
  * Precompute reverse map for encoding
  \*-------------------------------------------------------------------------*/
  static void qpsetup(UC *qpclass, UC *qpunbase)
  {
      int i;
      for (i = 0; i < 256; i++) qpclass[i] = QP_QUOTED;
      for (i = 33; i <= 60; i++) qpclass[i] = QP_PLAIN;
      for (i = 62; i <= 126; i++) qpclass[i] = QP_PLAIN;
      qpclass['\t'] = QP_IF_LAST; 
      qpclass[' '] = QP_IF_LAST;
      qpclass['\r'] = QP_CR;
      for (i = 0; i < 256; i++) qpunbase[i] = 255;
      qpunbase['0'] = 0; qpunbase['1'] = 1; qpunbase['2'] = 2;
      qpunbase['3'] = 3; qpunbase['4'] = 4; qpunbase['5'] = 5;
      qpunbase['6'] = 6; qpunbase['7'] = 7; qpunbase['8'] = 8;
      qpunbase['9'] = 9; qpunbase['A'] = 10; qpunbase['a'] = 10;
      qpunbase['B'] = 11; qpunbase['b'] = 11; qpunbase['C'] = 12;
      qpunbase['c'] = 12; qpunbase['D'] = 13; qpunbase['d'] = 13;
      qpunbase['E'] = 14; qpunbase['e'] = 14; qpunbase['F'] = 15;
      qpunbase['f'] = 15;
  }
  
  /*-------------------------------------------------------------------------*\
  * Output one character in form =XX
  \*-------------------------------------------------------------------------*/
  static void qpquote(UC c, luaL_Buffer *buffer)
  {
      luaL_putchar(buffer, '=');
      luaL_putchar(buffer, qpbase[c >> 4]);
      luaL_putchar(buffer, qpbase[c & 0x0F]);
  }
  
  /*-------------------------------------------------------------------------*\
  * Accumulate characters until we are sure about how to deal with them.
  * Once we are sure, output to the buffer, in the correct form. 
  \*-------------------------------------------------------------------------*/
  static size_t qpencode(UC c, UC *input, size_t size, 
          const char *marker, luaL_Buffer *buffer)
  {
      input[size++] = c;
      /* deal with all characters we can have */
      while (size > 0) {
          switch (qpclass[input[0]]) {
              /* might be the CR of a CRLF sequence */
              case QP_CR:
                  if (size < 2) return size;
                  if (input[1] == '\n') {
                      luaL_addstring(buffer, marker);
                      return 0;
                  } else qpquote(input[0], buffer);
                  break;
              /* might be a space and that has to be quoted if last in line */
              case QP_IF_LAST:
                  if (size < 3) return size;
                  /* if it is the last, quote it and we are done */
                  if (input[1] == '\r' && input[2] == '\n') {
                      qpquote(input[0], buffer);
                      luaL_addstring(buffer, marker);
                      return 0;
                  } else luaL_putchar(buffer, input[0]);
                  break;
                  /* might have to be quoted always */
              case QP_QUOTED:
                  qpquote(input[0], buffer);
                  break;
                  /* might never have to be quoted */
              default:
                  luaL_putchar(buffer, input[0]);
                  break;
          }
          input[0] = input[1]; input[1] = input[2];
          size--;
      }
      return 0;
  }
  
  /*-------------------------------------------------------------------------*\
  * Deal with the final characters 
  \*-------------------------------------------------------------------------*/
  static size_t qppad(UC *input, size_t size, luaL_Buffer *buffer)
  {
      size_t i;
      for (i = 0; i < size; i++) {
          if (qpclass[input[i]] == QP_PLAIN) luaL_putchar(buffer, input[i]);
          else qpquote(input[i], buffer);
      }
      if (size > 0) luaL_addstring(buffer, EQCRLF);
      return 0;
  }
  
  /*-------------------------------------------------------------------------*\
  * Incrementally converts a string to quoted-printable
  * A, B = qp(C, D, marker)
  * Marker is the text to be used to replace CRLF sequences found in A.
  * A is the encoded version of the largest prefix of C .. D that 
  * can be encoded without doubts. 
  * B has the remaining bytes of C .. D, *without* encoding.
  \*-------------------------------------------------------------------------*/
  static int mime_global_qp(lua_State *L)
  {
  
      size_t asize = 0, isize = 0;
      UC atom[3];
      const UC *input = (UC *) luaL_optlstring(L, 1, NULL, &isize);
      const UC *last = input + isize;
      const char *marker = luaL_optstring(L, 3, CRLF);
      luaL_Buffer buffer;
      /* end-of-input blackhole */
      if (!input) {
          lua_pushnil(L);
          lua_pushnil(L);
          return 2;
      }
      /* process first part of input */
      luaL_buffinit(L, &buffer);
      while (input < last)
          asize = qpencode(*input++, atom, asize, marker, &buffer);
      input = (UC *) luaL_optlstring(L, 2, NULL, &isize);
      /* if second part is nil, we are done */
      if (!input) {
          asize = qppad(atom, asize, &buffer);
          luaL_pushresult(&buffer);
          if (!(*lua_tostring(L, -1))) lua_pushnil(L);
          lua_pushnil(L);
          return 2;
      }
      /* otherwise process rest of input */
      last = input + isize;
      while (input < last)
          asize = qpencode(*input++, atom, asize, marker, &buffer);
      luaL_pushresult(&buffer);
      lua_pushlstring(L, (char *) atom, asize);
      return 2;
  }
  
  /*-------------------------------------------------------------------------*\
  * Accumulate characters until we are sure about how to deal with them.
  * Once we are sure, output the to the buffer, in the correct form. 
  \*-------------------------------------------------------------------------*/
  static size_t qpdecode(UC c, UC *input, size_t size, luaL_Buffer *buffer) {
      int d;
      input[size++] = c;
      /* deal with all characters we can deal */
      switch (input[0]) {
          /* if we have an escape character */
          case '=': 
              if (size < 3) return size; 
              /* eliminate soft line break */
              if (input[1] == '\r' && input[2] == '\n') return 0;
              /* decode quoted representation */
              c = qpunbase[input[1]]; d = qpunbase[input[2]];
              /* if it is an invalid, do not decode */
              if (c > 15 || d > 15) luaL_addlstring(buffer, (char *)input, 3);
              else luaL_putchar(buffer, (c << 4) + d);
              return 0;
          case '\r':
              if (size < 2) return size; 
              if (input[1] == '\n') luaL_addlstring(buffer, (char *)input, 2);
              return 0;
          default:
              if (input[0] == '\t' || (input[0] > 31 && input[0] < 127))
                  luaL_putchar(buffer, input[0]);
              return 0;
      }
  }
  
  /*-------------------------------------------------------------------------*\
  * Incrementally decodes a string in quoted-printable
  * A, B = qp(C, D)
  * A is the decoded version of the largest prefix of C .. D that 
  * can be decoded without doubts. 
  * B has the remaining bytes of C .. D, *without* decoding.
  \*-------------------------------------------------------------------------*/
  static int mime_global_unqp(lua_State *L)
  {
      size_t asize = 0, isize = 0;
      UC atom[3];
      const UC *input = (UC *) luaL_optlstring(L, 1, NULL, &isize);
      const UC *last = input + isize;
      luaL_Buffer buffer;
      /* end-of-input blackhole */
      if (!input) {
          lua_pushnil(L);
          lua_pushnil(L);
          return 2;
      }
      /* process first part of input */
      luaL_buffinit(L, &buffer);
      while (input < last)
          asize = qpdecode(*input++, atom, asize, &buffer);
      input = (UC *) luaL_optlstring(L, 2, NULL, &isize);
      /* if second part is nil, we are done */
      if (!input) {
          luaL_pushresult(&buffer);
          if (!(*lua_tostring(L, -1))) lua_pushnil(L);
          lua_pushnil(L);
          return 2;
      } 
      /* otherwise process rest of input */
      last = input + isize;
      while (input < last)
          asize = qpdecode(*input++, atom, asize, &buffer);
      luaL_pushresult(&buffer);
      lua_pushlstring(L, (char *) atom, asize);
      return 2;
  }
  
  /*-------------------------------------------------------------------------*\
  * Incrementally breaks a quoted-printed string into lines
  * A, n = qpwrp(l, B, length)
  * A is a copy of B, broken into lines of at most 'length' bytes. 
  * 'l' is how many bytes are left for the first line of B. 
  * 'n' is the number of bytes left in the last line of A. 
  * There are two complications: lines can't be broken in the middle
  * of an encoded =XX, and there might be line breaks already
  \*-------------------------------------------------------------------------*/
  static int mime_global_qpwrp(lua_State *L)
  {
      size_t size = 0;
      int left = (int) luaL_checknumber(L, 1);
      const UC *input = (UC *) luaL_optlstring(L, 2, NULL, &size);
      const UC *last = input + size;
      int length = (int) luaL_optnumber(L, 3, 76);
      luaL_Buffer buffer;
      /* end-of-input blackhole */
      if (!input) {
          if (left < length) lua_pushstring(L, EQCRLF);
          else lua_pushnil(L);
          lua_pushnumber(L, length);
          return 2;
      }
      /* process all input */
      luaL_buffinit(L, &buffer);
      while (input < last) {
          switch (*input) {
              case '\r':
                  break;
              case '\n':
                  left = length;
                  luaL_addstring(&buffer, CRLF);
                  break;
              case '=':
                  if (left <= 3) {
                      left = length;
                      luaL_addstring(&buffer, EQCRLF);
                  } 
                  luaL_putchar(&buffer, *input);
                  left--;
                  break;
              default: 
                  if (left <= 1) {
                      left = length;
                      luaL_addstring(&buffer, EQCRLF);
                  }
                  luaL_putchar(&buffer, *input);
                  left--;
                  break;
          }
          input++;
      }
      luaL_pushresult(&buffer);
      lua_pushnumber(L, left);
      return 2;
  }
  
  /*-------------------------------------------------------------------------*\
  * Here is what we do: \n, and \r are considered candidates for line
  * break. We issue *one* new line marker if any of them is seen alone, or
  * followed by a different one. That is, \n\n and \r\r will issue two
  * end of line markers each, but \r\n, \n\r etc will only issue *one*
  * marker.  This covers Mac OS, Mac OS X, VMS, Unix and DOS, as well as
  * probably other more obscure conventions.
  *
  * c is the current character being processed
  * last is the previous character
  \*-------------------------------------------------------------------------*/
  #define eolcandidate(c) (c == '\r' || c == '\n')
  static int eolprocess(int c, int last, const char *marker, 
          luaL_Buffer *buffer)
  {
      if (eolcandidate(c)) {
          if (eolcandidate(last)) {
              if (c == last) luaL_addstring(buffer, marker);
              return 0;
          } else {
              luaL_addstring(buffer, marker);
              return c;
          }
      } else {
          luaL_putchar(buffer, c);
          return 0;
      }
  }
  
  /*-------------------------------------------------------------------------*\
  * Converts a string to uniform EOL convention. 
  * A, n = eol(o, B, marker)
  * A is the converted version of the largest prefix of B that can be
  * converted unambiguously. 'o' is the context returned by the previous 
  * call. 'n' is the new context.
  \*-------------------------------------------------------------------------*/
  static int mime_global_eol(lua_State *L)
  {
      int ctx = luaL_checkint(L, 1);
      size_t isize = 0;
      const char *input = luaL_optlstring(L, 2, NULL, &isize);
      const char *last = input + isize;
      const char *marker = luaL_optstring(L, 3, CRLF);
      luaL_Buffer buffer;
      luaL_buffinit(L, &buffer);
      /* end of input blackhole */
      if (!input) {
         lua_pushnil(L);
         lua_pushnumber(L, 0);
         return 2;
      }
      /* process all input */
      while (input < last)
          ctx = eolprocess(*input++, ctx, marker, &buffer);
      luaL_pushresult(&buffer);
      lua_pushnumber(L, ctx);
      return 2;
  }
  
  /*-------------------------------------------------------------------------*\
  * Takes one byte and stuff it if needed. 
  \*-------------------------------------------------------------------------*/
  static size_t dot(int c, size_t state, luaL_Buffer *buffer)
  {
      luaL_putchar(buffer, c);
      switch (c) {
          case '\r': 
              return 1;
          case '\n': 
              return (state == 1)? 2: 0; 
          case '.':  
              if (state == 2) 
                  luaL_putchar(buffer, '.');
          default:
              return 0;
      }
  }
  
  /*-------------------------------------------------------------------------*\
  * Incrementally applies smtp stuffing to a string
  * A, n = dot(l, D)
  \*-------------------------------------------------------------------------*/
  static int mime_global_dot(lua_State *L)
  {
      size_t isize = 0, state = (size_t) luaL_checknumber(L, 1);
      const char *input = luaL_optlstring(L, 2, NULL, &isize);
      const char *last = input + isize;
      luaL_Buffer buffer;
      /* end-of-input blackhole */
      if (!input) {
          lua_pushnil(L);
          lua_pushnumber(L, 2);
          return 2;
      }
      /* process all input */
      luaL_buffinit(L, &buffer);
      while (input < last) 
          state = dot(*input++, state, &buffer);
      luaL_pushresult(&buffer);
      lua_pushnumber(L, state);
      return 2;
  }
  
  