/hphp/runtime/ext/ext_mailparse.cpp

http://github.com/facebook/hiphop-php · C++ · 451 lines · 347 code · 56 blank · 48 comment · 98 complexity · 289cf898b729a34563c547f380872de2 MD5 · raw file

  1. /*
  2. +----------------------------------------------------------------------+
  3. | HipHop for PHP |
  4. +----------------------------------------------------------------------+
  5. | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
  6. | Copyright (c) 1997-2010 The PHP Group |
  7. +----------------------------------------------------------------------+
  8. | This source file is subject to version 3.01 of the PHP license, |
  9. | that is bundled with this package in the file LICENSE, and is |
  10. | available through the world-wide-web at the following url: |
  11. | http://www.php.net/license/3_01.txt |
  12. | If you did not receive a copy of the PHP license and are unable to |
  13. | obtain it through the world-wide-web, please send a note to |
  14. | license@php.net so we can mail you a copy immediately. |
  15. +----------------------------------------------------------------------+
  16. */
  17. #include "hphp/runtime/ext/ext_mailparse.h"
  18. #include "hphp/runtime/base/runtime-option.h"
  19. #include "hphp/runtime/base/runtime-error.h"
  20. #include "hphp/runtime/base/temp-file.h"
  21. #include "hphp/runtime/ext/ext_process.h"
  22. #include "hphp/runtime/ext/mailparse/mime.h"
  23. #include "hphp/runtime/ext/mailparse/rfc822.h"
  24. namespace HPHP {
  25. ///////////////////////////////////////////////////////////////////////////////
  26. // utility functions
  27. /**
  28. * Removes whitespaces from the end, and replaces control characters with ' '
  29. * from the beginning.
  30. */
  31. static String php_trim(const String& str) {
  32. string s(str.c_str());
  33. unsigned int l = s.length();
  34. while (l > 0 && isspace((unsigned char)s[l - 1])) {
  35. l--;
  36. }
  37. for (unsigned int i = 0; i < l; i++) {
  38. if (iscntrl((unsigned char)s[i])) {
  39. if (i + 2 < l && s[i] == '\r' && s[i + 1] == '\n' &&
  40. (s[i + 2] == ' ' || s[i + 2] == '\t')) {
  41. i += 2;
  42. while (i + 1 < l && (s[i + 1] == ' ' || s[i + 1] == '\t')) {
  43. i++;
  44. }
  45. continue;
  46. }
  47. s[i] = ' ';
  48. }
  49. }
  50. return s.substr(0, l);
  51. }
  52. ///////////////////////////////////////////////////////////////////////////////
  53. bool php_mail(const String& to, const String& subject, const String& message,
  54. const String& headers, const String& extra_cmd) {
  55. // assumes we always have sendmail installed
  56. always_assert(!RuntimeOption::SendmailPath.empty());
  57. std::ostringstream os;
  58. os << RuntimeOption::SendmailPath;
  59. if (!extra_cmd.empty()) {
  60. os << ' ' << extra_cmd.c_str();
  61. }
  62. errno = 0;
  63. FILE *sendmail = popen(os.str().c_str(), "w");
  64. if (sendmail == NULL || EACCES == errno) {
  65. raise_warning("Unable to execute %s",
  66. RuntimeOption::SendmailPath.c_str());
  67. return false;
  68. }
  69. fprintf(sendmail, "To: %s\n", to.c_str());
  70. fprintf(sendmail, "Subject: %s\n", subject.c_str());
  71. if (!headers.empty()) {
  72. fprintf(sendmail, "%s\n", headers.c_str());
  73. }
  74. fprintf(sendmail, "\n%s\n", message.c_str());
  75. int ret = pclose(sendmail);
  76. return (!ret);
  77. }
  78. const StaticString zero(LITSTR_INIT("\0"));
  79. bool f_mail(const String& to, const String& subject, const String& message,
  80. const String& additional_headers /* = null_string */,
  81. const String& additional_parameters /* = null_string */) {
  82. // replace \0 with spaces
  83. String to2 = to.replace(zero, " ");
  84. String subject2 = subject.replace(zero, " ");
  85. String message2 = message.replace(zero, " ");
  86. String headers2;
  87. if (!additional_headers.empty()) {
  88. headers2 = additional_headers.replace(zero, " ");
  89. }
  90. String params2;
  91. if (!additional_parameters.empty()) {
  92. params2 = additional_parameters.replace(zero, " ");
  93. }
  94. to2 = php_trim(to2);
  95. subject2 = php_trim(subject2);
  96. if (!RuntimeOption::MailForceExtraParameters.empty()) {
  97. params2 = f_escapeshellcmd(RuntimeOption::MailForceExtraParameters);
  98. } else {
  99. params2 = f_escapeshellcmd(params2);
  100. }
  101. return php_mail(to2, subject2, message2, headers2, params2);
  102. }
  103. int64_t f_ezmlm_hash(const String& addr) {
  104. unsigned long h = 5381L;
  105. int str_len = addr.length();
  106. for (int i = 0; i < str_len; i++) {
  107. h = (h + (h << 5)) ^
  108. ((unsigned long)(unsigned char)tolower(addr.charAt(i)));
  109. }
  110. h = (h % 53);
  111. return (int)h;
  112. }
  113. ///////////////////////////////////////////////////////////////////////////////
  114. // mailparse
  115. Resource f_mailparse_msg_create() {
  116. return NEWOBJ(MimePart)();
  117. }
  118. bool f_mailparse_msg_free(CResRef mimemail) {
  119. return true;
  120. }
  121. Variant f_mailparse_msg_parse_file(const String& filename) {
  122. Variant stream = File::Open(filename, "rb");
  123. if (same(stream, false)) return false;
  124. File *f = stream.toResource().getTyped<File>();
  125. MimePart *p = NEWOBJ(MimePart)();
  126. Resource ret(p);
  127. while (!f->eof()) {
  128. String line = f->readLine();
  129. if (!line.isNull()) {
  130. if (!MimePart::ProcessLine(p, line)) {
  131. return false;
  132. }
  133. }
  134. }
  135. return ret;
  136. }
  137. bool f_mailparse_msg_parse(CResRef mimemail, const String& data) {
  138. return mimemail.getTyped<MimePart>()->parse(data.data(), data.size());
  139. }
  140. Variant f_mailparse_msg_extract_part_file(CResRef mimemail, CVarRef filename,
  141. CVarRef callbackfunc /* = "" */) {
  142. return mimemail.getTyped<MimePart>()->
  143. extract(filename, callbackfunc,
  144. MimePart::Decode8Bit | MimePart::DecodeNoHeaders, true);
  145. }
  146. Variant f_mailparse_msg_extract_whole_part_file(CResRef mimemail,
  147. CVarRef filename,
  148. CVarRef callbackfunc /* = "" */) {
  149. return mimemail.getTyped<MimePart>()->
  150. extract(filename, callbackfunc, MimePart::DecodeNone, true);
  151. }
  152. Variant f_mailparse_msg_extract_part(CResRef mimemail, CVarRef msgbody,
  153. CVarRef callbackfunc /* = "" */) {
  154. return mimemail.getTyped<MimePart>()->
  155. extract(msgbody, callbackfunc,
  156. MimePart::Decode8Bit | MimePart::DecodeNoHeaders, false);
  157. }
  158. Array f_mailparse_msg_get_part_data(CResRef mimemail) {
  159. return mimemail.getTyped<MimePart>()->getPartData().toArray();
  160. }
  161. Variant f_mailparse_msg_get_part(CResRef mimemail, const String& mimesection) {
  162. Resource part =
  163. mimemail.getTyped<MimePart>()->findByName(mimesection.c_str());
  164. if (part.isNull()) {
  165. raise_warning("cannot find section %s in message", mimesection.data());
  166. return false;
  167. }
  168. return part;
  169. }
  170. Array f_mailparse_msg_get_structure(CResRef mimemail) {
  171. return mimemail.getTyped<MimePart>()->getStructure();
  172. }
  173. const StaticString
  174. s_display("display"),
  175. s_address("address"),
  176. s_is_group("is_group");
  177. Array f_mailparse_rfc822_parse_addresses(const String& addresses) {
  178. php_rfc822_tokenized_t *toks =
  179. php_mailparse_rfc822_tokenize(addresses.data(), 1);
  180. php_rfc822_addresses_t *addrs = php_rfc822_parse_address_tokens(toks);
  181. Array ret = Array::Create();
  182. for (int i = 0; i < addrs->naddrs; i++) {
  183. Array item = Array::Create();
  184. if (addrs->addrs[i].name) {
  185. item.set(s_display, String(addrs->addrs[i].name, CopyString));
  186. }
  187. if (addrs->addrs[i].address) {
  188. item.set(s_address, String(addrs->addrs[i].address, CopyString));
  189. }
  190. item.set(s_is_group, (bool)addrs->addrs[i].is_group);
  191. ret.append(item);
  192. }
  193. php_rfc822_free_addresses(addrs);
  194. php_rfc822_tokenize_free(toks);
  195. return ret;
  196. }
  197. static int mailparse_stream_output(int c, void *stream) {
  198. char buf[2];
  199. buf[0] = c;
  200. buf[1] = '\0';
  201. return ((File*)stream)->write(buf, 1);
  202. }
  203. static int mailparse_stream_flush(void *stream) {
  204. return ((File*)stream)->flush() ? 1 : 0;
  205. }
  206. bool f_mailparse_stream_encode(CResRef sourcefp, CResRef destfp,
  207. const String& encoding) {
  208. File *srcstream = sourcefp.getTyped<File>(true, true);
  209. File *deststream = destfp.getTyped<File>(true, true);
  210. if (srcstream == NULL || deststream == NULL) {
  211. return false;
  212. }
  213. enum mbfl_no_encoding enc = mbfl_name2no_encoding(encoding.data());
  214. if (enc == mbfl_no_encoding_invalid) {
  215. raise_warning("Unknown encoding \"%s\"", encoding.data());
  216. return false;
  217. }
  218. mbfl_convert_filter *conv =
  219. mbfl_convert_filter_new(mbfl_no_encoding_8bit, enc,
  220. mailparse_stream_output, mailparse_stream_flush,
  221. deststream);
  222. if (enc == mbfl_no_encoding_qprint) {
  223. /* If the qp encoded section is going to be digitally signed,
  224. * it is a good idea to make sure that lines that begin "From "
  225. * have the letter F encoded, so that MTAs do not stick a > character
  226. * in front of it and invalidate the content/signature */
  227. while (!srcstream->eof()) {
  228. String line = srcstream->readLine();
  229. if (!line.isNull()) {
  230. int i;
  231. if (strncmp(line.data(), "From ", 5) == 0) {
  232. mbfl_convert_filter_flush(conv);
  233. deststream->write("=46rom ", 7);
  234. i = 5;
  235. } else {
  236. i = 0;
  237. }
  238. const char *p = line.data();
  239. for (; i < line.size(); i++) {
  240. mbfl_convert_filter_feed(p[i], conv);
  241. }
  242. }
  243. }
  244. } else {
  245. while (!srcstream->eof()) {
  246. String data = srcstream->read();
  247. if (!data.empty()) {
  248. const char *p = data.data();
  249. for (int i = 0; i < data.size(); i++) {
  250. mbfl_convert_filter_feed(p[i], conv);
  251. }
  252. }
  253. }
  254. }
  255. mbfl_convert_filter_flush(conv);
  256. mbfl_convert_filter_delete(conv);
  257. return true;
  258. }
  259. #define UUDEC(c) (char)(((c)-' ')&077)
  260. #define UU_NEXT(v) \
  261. if (line[x] == '\0' || line[x] == '\r' || line[x] == '\n') break; \
  262. v = line[x++]; v = UUDEC(v)
  263. static size_t mailparse_do_uudecode(File *instream, File *outstream) {
  264. int A, B, C, D, n;
  265. size_t file_size = 0;
  266. if (outstream) {
  267. /* write to outstream */
  268. while (!instream->eof()) {
  269. String line = instream->readLine(128);
  270. if (line.isNull()) break;
  271. int x = 0;
  272. UU_NEXT(n);
  273. while (n) {
  274. UU_NEXT(A); UU_NEXT(B); UU_NEXT(C); UU_NEXT(D);
  275. if (n-- > 0) {
  276. file_size++;
  277. outstream->putc((A << 2) | (B >> 4));
  278. }
  279. if (n-- > 0) {
  280. file_size++;
  281. outstream->putc((B << 4) | (C >> 2));
  282. }
  283. if (n-- > 0) {
  284. file_size++;
  285. outstream->putc((C << 6) | D);
  286. }
  287. }
  288. }
  289. } else {
  290. /* skip (and measure) the data, but discard it.
  291. * This is separated from the version above to speed it up by a few cycles
  292. */
  293. while (!instream->eof()) {
  294. String line = instream->readLine(128);
  295. if (line.isNull()) break;
  296. int x = 0;
  297. UU_NEXT(n);
  298. while (line[x] && n != 0) {
  299. UU_NEXT(A); UU_NEXT(B); UU_NEXT(C); UU_NEXT(D);
  300. if (n-- > 0) file_size++;
  301. if (n-- > 0) file_size++;
  302. if (n-- > 0) file_size++;
  303. }
  304. }
  305. }
  306. return file_size;
  307. }
  308. const StaticString
  309. s_filename("filename"),
  310. s_origfilename("origfilename");
  311. Variant f_mailparse_uudecode_all(CResRef fp) {
  312. File *instream = fp.getTyped<File>();
  313. instream->rewind();
  314. File *outstream = NEWOBJ(TempFile)(false);
  315. Resource deleter(outstream);
  316. Array return_value;
  317. int nparts = 0;
  318. while (!instream->eof()) {
  319. String line = instream->readLine();
  320. if (line.isNull()) break;
  321. /* Look for the "begin " sequence that identifies a uuencoded file */
  322. if (strncmp(line.data(), "begin ", 6) == 0) {
  323. /* parse out the file name.
  324. * The next 4 bytes are an octal number for perms; ignore it */
  325. // TODO: Update gcc and get rid of this dumb workaround.
  326. char *origfilename = (char *)((size_t)line.data() + (10 * sizeof(char)));
  327. /* NUL terminate the filename */
  328. int len = strlen(origfilename);
  329. while (isspace(origfilename[len-1])) {
  330. origfilename[--len] = '\0';
  331. }
  332. /* make the return an array */
  333. if (nparts == 0) {
  334. return_value = Array::Create();
  335. /* create an initial item representing the file with all uuencoded
  336. parts removed */
  337. Array item = Array::Create();
  338. item.set(s_filename, String(((TempFile*)outstream)->getName()));
  339. return_value.append(item);
  340. }
  341. /* add an item */
  342. Array item = Array::Create();
  343. item.set(s_origfilename, String(origfilename, CopyString));
  344. /* create a temp file for the data */
  345. File *partstream = NEWOBJ(TempFile)(false);
  346. Resource deleter(partstream);
  347. if (partstream) {
  348. nparts++;
  349. item.set(s_filename, String(((TempFile*)partstream)->getName()));
  350. return_value.append(item);
  351. /* decode it */
  352. mailparse_do_uudecode(instream, partstream);
  353. }
  354. } else {
  355. /* write to the output file */
  356. outstream->write(line);
  357. }
  358. }
  359. instream->rewind();
  360. if (nparts == 0) {
  361. return false;
  362. }
  363. return return_value;
  364. }
  365. Variant f_mailparse_determine_best_xfer_encoding(CResRef fp) {
  366. File *stream = fp.getTyped<File>();
  367. stream->rewind();
  368. int linelen = 0;
  369. enum mbfl_no_encoding bestenc = mbfl_no_encoding_7bit;
  370. bool longline = false;
  371. while (!stream->eof()) {
  372. int c = stream->getc();
  373. if (c > 0x80) {
  374. bestenc = mbfl_no_encoding_8bit;
  375. } else if (c == 0) {
  376. bestenc = mbfl_no_encoding_base64;
  377. longline = false;
  378. break;
  379. }
  380. if (c == '\n') {
  381. linelen = 0;
  382. } else if (++linelen > 200) {
  383. longline = true;
  384. }
  385. }
  386. if (longline) bestenc = mbfl_no_encoding_qprint;
  387. stream->rewind();
  388. char * name = (char *)mbfl_no2preferred_mime_name(bestenc);
  389. if (name) {
  390. return String(name, CopyString);
  391. }
  392. return false;
  393. }
  394. ///////////////////////////////////////////////////////////////////////////////
  395. }