static Variant HHVM_FUNCTION()

in hphp/runtime/ext/iconv/ext_iconv.cpp [1363:1717]


static Variant HHVM_FUNCTION(iconv_mime_encode,
    const String& field_name, const String& field_value,
    const Variant& preferences /* = uninit_variant */) {
  php_iconv_enc_scheme_t scheme_id = PHP_ICONV_ENC_SCHEME_BASE64;
  String in_charset;
  String out_charset;
  long line_len = 76;
  String lfchars = "\r\n";
  StringBuffer ret;
  char *buf = NULL;

  if (!preferences.isNull()) {
    Variant scheme = preferences.toArray()[s_scheme];
    if (scheme.isString()) {
      String s = scheme.toString();
      switch (*s.data()) {
      case 'B': case 'b':
        scheme_id = PHP_ICONV_ENC_SCHEME_BASE64;
        break;
      case 'Q': case 'q':
        scheme_id = PHP_ICONV_ENC_SCHEME_QPRINT;
        break;
      }
    }

    Variant input_charset = preferences.toArray()[s_input_charset];
    if (input_charset.isString()) {
      in_charset = input_charset.toString();
      if (!validate_charset(in_charset)) return false;
    }

    Variant output_charset = preferences.toArray()[s_output_charset];
    if (output_charset.isString()) {
      out_charset = output_charset.toString();
      if (!validate_charset(out_charset)) return false;
    }

    Variant line_length = preferences.toArray()[s_line_length];
    if (!line_length.isNull()) {
      line_len = line_length.toInt64();
    }

    Variant line_break_chars = preferences.toArray()[s_line_break_chars];
    if (!line_break_chars.isNull()) {
      lfchars = line_break_chars.toString();
    }
  }

  static int qp_table[256] = {
    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x00 */
    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x10 */
    3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x20 */
    1, 1, 1, 1, 1, 1, 1 ,1, 1, 1, 1, 1, 1, 3, 1, 3, /* 0x30 */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40 */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, /* 0x50 */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60 */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, /* 0x70 */
    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x80 */
    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x90 */
    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xA0 */
    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xB0 */
    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xC0 */
    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xD0 */
    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xE0 */
    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3  /* 0xF0 */
  };

  php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS;
  iconv_t cd = (iconv_t)(-1), cd_pl = (iconv_t)(-1);

  if ((field_name.size() + 2) >= line_len ||
      (out_charset.size() + 12) >= line_len) {
    /* field name is too long */
    err = PHP_ICONV_ERR_TOO_BIG;
    goto out;
  }

  cd_pl = iconv_open_helper("ASCII", in_charset.data());
  if (cd_pl == (iconv_t)(-1)) {
#if ICONV_SUPPORTS_ERRNO
    if (errno == EINVAL) {
      err = PHP_ICONV_ERR_WRONG_CHARSET;
    } else {
      err = PHP_ICONV_ERR_CONVERTER;
    }
#else
    err = PHP_ICONV_ERR_UNKNOWN;
#endif
    goto out;
  }

  cd = iconv_open_helper(out_charset.data(), in_charset.data());
  if (cd == (iconv_t)(-1)) {
#if ICONV_SUPPORTS_ERRNO
    if (errno == EINVAL) {
      err = PHP_ICONV_ERR_WRONG_CHARSET;
    } else {
      err = PHP_ICONV_ERR_CONVERTER;
    }
#else
    err = PHP_ICONV_ERR_UNKNOWN;
#endif
    goto out;
  }

  const char *in_p;
  size_t in_left;
  char *out_p;
  size_t out_left;

  buf = (char*)req::malloc_noptrs(line_len + 5);
  unsigned int char_cnt;
  char_cnt = line_len;

  _php_iconv_appendl(ret, field_name.data(), field_name.size(), cd_pl);
  char_cnt -= field_name.size();
  ret.append(": ");
  char_cnt -= 2;

  in_p = field_value.data();
  in_left = field_value.size();

  do {
    size_t prev_in_left;
    size_t out_size;

    if ((int)char_cnt < (out_charset.size() + 12)) {
      ret.append(lfchars); // lfchars must be encoded in ASCII here
      ret.append(' ');
      char_cnt = line_len - 1;
    }

    ret.append("=?");
    char_cnt -= 2;
    ret.append(out_charset);
    char_cnt -= out_charset.size();
    ret.append('?');
    char_cnt --;

    switch (scheme_id) {
    case PHP_ICONV_ENC_SCHEME_BASE64:
      {
        size_t ini_in_left;
        const char *ini_in_p;
        size_t out_reserved = 4;

        ret.append('B');
        char_cnt--;
        ret.append('?');
        char_cnt--;

        prev_in_left = ini_in_left = in_left;
        ini_in_p = in_p;

        out_size = (char_cnt - 2) / 4 * 3;

        for (;;) {
          out_p = buf;

          if (out_size <= out_reserved) {
            err = PHP_ICONV_ERR_TOO_BIG;
            goto out;
          }

          out_left = out_size - out_reserved;

          if (iconv(cd, (ICONV_CONST char **)&in_p, &in_left,
                    (char **)&out_p, &out_left) == (size_t)-1) {
#if ICONV_SUPPORTS_ERRNO
            switch (errno) {
            case EINVAL: err = PHP_ICONV_ERR_ILLEGAL_CHAR; goto out;
            case EILSEQ: err = PHP_ICONV_ERR_ILLEGAL_SEQ;  goto out;
            case E2BIG:
              if (prev_in_left == in_left) {
                err = PHP_ICONV_ERR_TOO_BIG;
                goto out;
              }
              break;
            default:
              err = PHP_ICONV_ERR_UNKNOWN;
              goto out;
            }
#else
            if (prev_in_left == in_left) {
              err = PHP_ICONV_ERR_UNKNOWN;
              goto out;
            }
#endif
          }

          out_left += out_reserved;

          if (iconv(cd, NULL, NULL, (char **)&out_p, &out_left) ==
              (size_t)-1) {
#if ICONV_SUPPORTS_ERRNO
            if (errno != E2BIG) {
              err = PHP_ICONV_ERR_UNKNOWN;
              goto out;
            }
#else
            if (out_left != 0) {
              err = PHP_ICONV_ERR_UNKNOWN;
              goto out;
            }
#endif
          } else {
            break;
          }

          if (iconv(cd, NULL, NULL, NULL, NULL) == (size_t)-1) {
            err = PHP_ICONV_ERR_UNKNOWN;
            goto out;
          }

          out_reserved += 4;
          in_left = ini_in_left;
          in_p = ini_in_p;
        }

        prev_in_left = in_left;


        int encoded_len = out_size - out_left;
        String encoded = string_base64_encode(buf, encoded_len);
        if ((int)char_cnt < encoded.size()) {
          /* something went wrong! */
          err = PHP_ICONV_ERR_UNKNOWN;
          goto out;
        }

        ret.append(encoded);
        char_cnt -= encoded.size();
        ret.append("?=");
        char_cnt -= 2;
      }
      break; /* case PHP_ICONV_ENC_SCHEME_BASE64: */

    case PHP_ICONV_ENC_SCHEME_QPRINT:
      {
        size_t ini_in_left;
        const char *ini_in_p;
        const unsigned char *p;
        size_t nbytes_required;

        ret.append('Q');
        char_cnt--;
        ret.append('?');
        char_cnt--;

        prev_in_left = ini_in_left = in_left;
        ini_in_p = in_p;

        for (out_size = (char_cnt - 2) / 3; out_size > 0;) {
          size_t prev_out_left ATTRIBUTE_UNUSED;

          nbytes_required = 0;

          out_p = buf;
          out_left = out_size;

          if (iconv(cd, (ICONV_CONST char **)&in_p, &in_left,
                    (char **)&out_p, &out_left) == (size_t)-1) {
#if ICONV_SUPPORTS_ERRNO
            switch (errno) {
            case EINVAL: err = PHP_ICONV_ERR_ILLEGAL_CHAR; goto out;
            case EILSEQ: err = PHP_ICONV_ERR_ILLEGAL_SEQ;  goto out;
            case E2BIG:
              if (prev_in_left == in_left) {
                err = PHP_ICONV_ERR_UNKNOWN;
                goto out;
              }
              break;
            default:
              err = PHP_ICONV_ERR_UNKNOWN;
              goto out;
            }
#else
            if (prev_in_left == in_left) {
              err = PHP_ICONV_ERR_UNKNOWN;
              goto out;
            }
#endif
          }

          prev_out_left = out_left;
          if (iconv(cd, NULL, NULL, (char **)&out_p, &out_left) ==
              (size_t)-1) {
#if ICONV_SUPPORTS_ERRNO
            if (errno != E2BIG) {
              err = PHP_ICONV_ERR_UNKNOWN;
              goto out;
            }
#else
            if (out_left == prev_out_left) {
              err = PHP_ICONV_ERR_UNKNOWN;
              goto out;
            }
#endif
          }

          for (p = (unsigned char *)buf; p < (unsigned char *)out_p; p++) {
            nbytes_required += qp_table[*p];
          }

          if (nbytes_required <= char_cnt - 2) {
            break;
          }

          out_size -= ((nbytes_required - (char_cnt - 2)) + 1) / (3 - 1);
          in_left = ini_in_left;
          in_p = ini_in_p;
        }

        for (p = (unsigned char *)buf; p < (unsigned char *)out_p; p++) {
          if (qp_table[*p] == 1) {
            ret.append(*(char*)p);
            char_cnt--;
          } else {
            static char qp_digits[] = "0123456789ABCDEF";
            ret.append('=');
            ret.append(qp_digits[(*p >> 4) & 0x0f]);
            ret.append(qp_digits[(*p & 0x0f)]);
            char_cnt -= 3;
          }
        }
        prev_in_left = in_left;

        ret.append("?=");
        char_cnt -= 2;

        if (iconv(cd, NULL, NULL, NULL, NULL) == (size_t)-1) {
          err = PHP_ICONV_ERR_UNKNOWN;
          goto out;
        }

      } break; /* case PHP_ICONV_ENC_SCHEME_QPRINT: */
    }
  } while (in_left > 0);

 out:
  if (cd != (iconv_t)(-1)) {
    iconv_close(cd);
  }
  if (cd_pl != (iconv_t)(-1)) {
    iconv_close(cd_pl);
  }
  if (buf != NULL) {
    req::free(buf);
  }

  if (err != PHP_ICONV_ERR_SUCCESS) {
    return false;
  }
  return ret.detach();
}