1. --!movie
  2. --!encoding=utf-8
  3.  
  4. --****************************************************************************
  5. -- @file Curl SMTP demo
  6. -- @author Valentin Schmidt
  7. -- @version 0.6
  8. -- @requires Curl Xtra + wrapper lib "curl"
  9. -- @requires FileIO Xtra + wrapper lib "file"
  10. -- @requires lib "base64"
  11. -- @requires lib "date"
  12. --****************************************************************************
  13.  
  14. global $
  15.  
  16. ----------------------------------------
  17. --
  18. ----------------------------------------
  19. on startMovie
  20.     _player.debugPlaybackEnabled = 1
  21.  
  22.     -- libs
  23.     $.import("base64")
  24.     $.import("curl")
  25.     $.import("date")
  26.     $.import("file")
  27.  
  28.     host = "wp227.webpack.hosteurope.de" -- actual smtp host here
  29.     port = 465 -- 465 for SSL, 587 for STARTTLS
  30.     usr = "******" -- actual username here
  31.     pwd = "******" -- actual password here
  32.     from = "me@dasdeck.com"
  33.     recipients = "john@domain.com" -- or: recipients = ["john@domain.com", "me@dasdeck.com"]
  34.     subject = "SMTP/SSL with Curl Xtra"
  35.  
  36.     message_plain = ""
  37.     put "Hi John," & $.CRLF & $.CRLF after message_plain
  38.     put "this is the plain text part of an email sent with Curl Xtra." & $.CRLF after message_plain
  39.     put "The email has an attached video file and an inline image." & $.CRLF & $.CRLF after message_plain
  40.     put "[image]" & $.CRLF & $.CRLF after message_plain
  41.     put "Cheers," & $.CRLF after message_plain
  42.     put "Valentin" & $.CRLF after message_plain
  43.  
  44.     message_html = ""
  45.     put "<!DOCTYPE HTML><html lang='en'><head><meta charset='utf-8'></head>" & $.CRLF after message_html
  46.     put "<body>" & $.CRLF after message_html
  47.  
  48.     put "<b>Hi John,</b><br><br>" & $.CRLF after message_html
  49.     put "this is the HTML part of an email sent with Curl Xtra.<br>" & $.CRLF after message_html
  50.     put "The email has an attached video file and an inline image.<br><br>" & $.CRLF after message_html
  51.     put "<img src='{{1}}' width='256' height='256' alt='lena'><br><br>" & $.CRLF after message_html
  52.     put "Cheers,<br>" & $.CRLF after message_html
  53.     put "Valentin" & $.CRLF after message_html
  54.  
  55.     put "</body>" & $.CRLF after message_html
  56.     put "</html>" & $.CRLF after message_html
  57.  
  58.     files_attached = ["C:\test\bbb_360p_24fps_10sec.mp4"]
  59.     files_inline = ["C:\test\lena_256.jpg"]
  60.  
  61.     res = sendMail(host, port, usr, pwd, from, recipients, subject, message_plain, message_html, files_attached, files_inline)
  62.  
  63.     if res = 0 then
  64.         put "Email sent successfully"
  65.     else
  66.         put "Error sending email: " & curl_error(res)
  67.     end if
  68. end
  69.  
  70. ----------------------------------------
  71. -- Sends email with SMTP
  72. -- @param {string} host
  73. -- @param {integer} port - standard SMTP ports: 25=no security, 465=SSL/TLS, 587=STARTTLS
  74. -- @param {string} usr
  75. -- @param {string} pwd
  76. -- @param {string} from
  77. -- @param {string|list} recipients - single recipient as string or multiple recipients as list
  78. -- @param {string} subject
  79. -- @param {string} message_plain - the plain text part
  80. -- @param {string} [message_html=VOID] - the HTML part (optional)
  81. -- @param {list} [files_attached=VOID] - list of files attached to email (optional)
  82. -- @param {list} [files_inline=VOID] - list of files embedded into HTML part (optional)
  83. -- @param {boolean} [force_ssl=FALSE] - force SSL also for port <> 465
  84. -- @return {integer} curl error code, 0 = success
  85. ----------------------------------------
  86. on sendMail (host, port, usr, pwd, from, recipients, subject, message_plain, message_html, files_attached, files_inline, force_ssl)
  87.     if stringP(recipients) then recipients = [recipients]
  88.     if voidP(message_html) then message_html = ""
  89.     if voidP(files_inline) then files_inline = []
  90.     if voidP(files_attached) then files_attached = []
  91.  
  92.     to_header = recipients[1]
  93.     repeat with i = 2 to recipients.count
  94.         put ", " & recipients[i] after to_header
  95.     end repeat
  96.  
  97.     if port=465 or force_ssl then
  98.         smpt_url = "smtps://"
  99.     else
  100.         smpt_url = "smtp://"
  101.     end if
  102.     put usr & ":" & pwd & "@" & host & ":" & string(port) after smpt_url
  103.  
  104.     -- create message, save as temporary file
  105.     tmpFile = $.PATH & "~mail.eml" -- in case of an installed app, use the user's TMP dir instead
  106.     fp = $.file.fopen(tmpFile, "wb")
  107.  
  108.     -- to keep the spam score as low as possible, we add both Date and Message-ID headers
  109.     fp.writeString("Date: " && $.date.createUTCDateString() & $.CRLF)
  110.     fp.writeString("Message-ID: <" & uid() & "@" & host & ">" & $.CRLF)
  111.  
  112.     fp.writeString("To: " & to_header & $.CRLF)
  113.     fp.writeString("From: " & from & $.CRLF)
  114.     fp.writeString("Subject: " & mime_encode(subject) & $.CRLF)
  115.     fp.writeString("User-Agent: Curl Xtra/" & xtraVersion(#curl) & $.CRLF) -- completely optional
  116.     fp.writeString("MIME-Version: 1.0" & $.CRLF)
  117.  
  118.     cnt_files_attached = count(files_attached)
  119.     cnt_files_inline = count(files_inline)
  120.  
  121.     if cnt_files_attached=0 and message_html="" then
  122.         fp.writeString("Content-Type: text/plain; charset=utf-8" & $.CRLF)
  123.         fp.writeString("Content-Transfer-Encoding: 8bit" & $.CRLF)
  124.         fp.writeString($.CRLF)
  125.         fp.writeString(message_plain)
  126.     else
  127.         if cnt_files_attached then
  128.             boundary_mixed = "------------" & uid()
  129.  
  130.             fp.writeString("Content-Type: multipart/mixed; boundary=" & QUOTE & boundary_mixed & QUOTE & $.CRLF & $.CRLF)
  131.             fp.writeString("This is a multi-part message in MIME format." & $.CRLF)
  132.  
  133.             if message_html<>"" then
  134.                 -- files_attached and html
  135.                 fp.writeString("--" & boundary_mixed & $.CRLF)
  136.  
  137.                 boundary_alternative = "------------" & uid()
  138.                 fp.writeString("Content-Type: multipart/alternative; boundary=" & QUOTE & boundary_alternative & QUOTE & $.CRLF & $.CRLF)
  139.  
  140.                 write_mime_part(fp, "text/plain", message_plain, boundary_alternative)
  141.  
  142.                 if cnt_files_inline then
  143.                     fp.writeString("--" & boundary_alternative & $.CRLF)
  144.  
  145.                     boundary_related = "------------" & uid()
  146.                     fp.writeString("Content-Type: multipart/related; boundary=" & QUOTE & boundary_related & QUOTE & $.CRLF & $.CRLF)
  147.  
  148.                     content_ids = []
  149.                     repeat with i = 1 to cnt_files_inline
  150.                         content_ids[i] = "part" & i & "." & uid() & "@" & host
  151.                         pos = offset("{{" & i & "}}", message_html)
  152.                         if pos then
  153.                             message_html = message_html.char[1..pos-1] & "cid:" & content_ids[i] & message_html.char[pos+length("{{" & i & "}}")..message_html.length]
  154.                         end if
  155.                     end repeat
  156.  
  157.                     write_mime_part(fp, "text/html", message_html, boundary_related)
  158.  
  159.                     repeat with i = 1 to cnt_files_inline
  160.                         f = files_inline[i]
  161.                         fn = $.file.getFileName(f)
  162.                         fp.writeString("--" & boundary_related & $.CRLF)
  163.                         fp.writeString("Content-Type: " & mime_type($.file.getFileType(f)) & "; name=" & QUOTE & fn & QUOTE & $.CRLF)
  164.                         fp.writeString("Content-Transfer-Encoding: base64" & $.CRLF)
  165.                         fp.writeString("Content-ID: <" & content_ids[i] & ">" & $.CRLF)
  166.                         fp.writeString("Content-Disposition: inline; filename=" & QUOTE & fn & QUOTE & $.CRLF & $.CRLF)
  167.                         fp.writeString($.base64.encode($.file.getBytes(f), True) & $.CRLF & $.CRLF)
  168.                     end repeat
  169.  
  170.                     fp.writeString("--" & boundary_related & "--" & $.CRLF & $.CRLF)
  171.  
  172.                 else
  173.                     write_mime_part(fp, "text/html", message_html, boundary_alternative)
  174.                 end if
  175.  
  176.                 fp.writeString("--" & boundary_alternative & "--" & $.CRLF & $.CRLF)
  177.             else
  178.                 -- files_attached, no html
  179.                 write_mime_part(fp, "text/plain", message_plain, boundary_mixed)
  180.             end if
  181.  
  182.             repeat with f in files_attached
  183.                 fn = $.file.getFileName(f)
  184.                 fp.writeString("--" & boundary_mixed & $.CRLF)
  185.                 fp.writeString("Content-Type: " & mime_type($.file.getFileType(f)) & "; name=" & QUOTE & fn & QUOTE & $.CRLF)
  186.                 fp.writeString("Content-Transfer-Encoding: base64" & $.CRLF)
  187.                 fp.writeString("Content-Disposition: attachment; filename=" & QUOTE & fn & QUOTE & $.CRLF & $.CRLF)
  188.                 fp.writeString($.base64.encode($.file.getBytes(f), True) & $.CRLF & $.CRLF)
  189.             end repeat
  190.  
  191.             fp.writeString("--" & boundary_mixed & "--" & $.CRLF)
  192.         else
  193.             -- html, no files_attached
  194.             boundary_alternative = "------------" & uid()
  195.  
  196.             fp.writeString("Content-Type: multipart/alternative; boundary=" & QUOTE & boundary_alternative & QUOTE & $.CRLF & $.CRLF)
  197.             fp.writeString("This is a multi-part message in MIME format." & $.CRLF)
  198.  
  199.             write_mime_part(fp, "text/plain", message_plain, boundary_alternative)
  200.  
  201.             if cnt_files_inline then
  202.                 fp.writeString("--" & boundary_alternative & $.CRLF)
  203.  
  204.                 boundary_related = "------------" & uid()
  205.                 fp.writeString("Content-Type: multipart/related; boundary=" & QUOTE & boundary_related & QUOTE & $.CRLF & $.CRLF)
  206.  
  207.                 content_ids = []
  208.                 repeat with i = 1 to cnt_files_inline
  209.                     content_ids[i] = "part" & i & "." & uid() & "@" & host
  210.                     pos = offset("{{" & i & "}}", message_html)
  211.                     if pos then
  212.                         message_html = message_html.char[1..pos-1] & "cid:" & content_ids[i] & message_html.char[pos+length("{{" & i & "}}")..message_html.length]
  213.                     end if
  214.                 end repeat
  215.  
  216.                 write_mime_part(fp, "text/html", message_html, boundary_related)
  217.  
  218.                 repeat with i = 1 to cnt_files_inline
  219.                     f = files_inline[i]
  220.                     fn = $.file.getFileName(f)
  221.                     fp.writeString("--" & boundary_related & $.CRLF)
  222.                     fp.writeString("Content-Type: " & mime_type($.file.getFileType(f)) & "; name=" & QUOTE & fn & QUOTE & $.CRLF)
  223.                     fp.writeString("Content-Transfer-Encoding: base64" & $.CRLF)
  224.                     fp.writeString("Content-ID: <" & content_ids[i] & ">" & $.CRLF)
  225.                     fp.writeString("Content-Disposition: inline; filename=" & QUOTE & fn & QUOTE & $.CRLF & $.CRLF)
  226.                     fp.writeString($.base64.encode($.file.getBytes(f), True) & $.CRLF & $.CRLF)
  227.                 end repeat
  228.  
  229.                 fp.writeString("--" & boundary_related & "--" & $.CRLF & $.CRLF)
  230.             else
  231.                 write_mime_part(fp, "text/html", message_html, boundary_alternative)
  232.             end if
  233.  
  234.             fp.writeString("--" & boundary_alternative & "--" & $.CRLF)
  235.         end if
  236.  
  237.     end if
  238.  
  239.     $.file.fclose(fp)
  240.  
  241.     -- get a CURL handle (xtra instance)
  242.     ch = $.curl.init()
  243.  
  244.     -- specify options
  245.     --ch.setOption($.curl.CURLOPT.VERBOSE, 1) -- for debugging, prints to stdout
  246.     ch.setOption($.curl.CURLOPT.URL, smpt_url)
  247.     ch.setOption($.curl.CURLOPT.MAIL_FROM, from)
  248.     ch.setOption($.curl.CURLOPT.MAIL_RCPT, recipients)
  249.     ch.setOption($.curl.CURLOPT.SSL_VERIFYPEER, 0)
  250.     ch.setOption($.curl.CURLOPT.SSL_VERIFYHOST, 0)
  251.     ch.setOption($.curl.CURLOPT.UPLOAD, 1)
  252.     ch.setSourceFile(tmpFile)
  253.  
  254.     -- execute request
  255.     res = ch.exec()
  256.  
  257.     $.file.delete(tmpFile)
  258.     return res
  259. end
  260.  
  261. ----------------------------------------
  262. --
  263. ----------------------------------------
  264. on write_mime_part (fp, content_type, str, boundary)
  265.     fp.writeString("--" & boundary & $.CRLF)
  266.     fp.writeString("Content-Type: " & content_type & "; charset=utf-8" & $.CRLF)
  267.     fp.writeString("Content-Transfer-Encoding: 8bit" & $.CRLF)
  268.     fp.writeString($.CRLF)
  269.     fp.writeString(str)
  270.     fp.writeString($.CRLF & $.CRLF)
  271. end
  272.  
  273. ----------------------------------------
  274. --
  275. ----------------------------------------
  276. on mime_encode (str)
  277.     return "=?UTF-8?B?" & $.base64.encode(str) & "?="
  278. end
  279.  
  280. ----------------------------------------
  281. --
  282. ----------------------------------------
  283. on mime_type (t)
  284.     case t of
  285.         "3gp": return "video/3gpp"
  286.         "3g2": return "video/3gpp2"
  287.         "7z": return "application/x-7z-compressed"
  288.         "aac": return "audio/aac"
  289.         "abw": return "application/x-abiword"
  290.         "aif", "aiff": return "audio/x-aiff"
  291.         "arc": return "application/octet-stream"
  292.         "avi": return "video/x-msvideo"
  293.         "azw": return "application/vnd.amazon.ebook"
  294.         "bin": return "application/octet-stream"
  295.         "bmp": return "image/bmp"
  296.         "bz": return "application/x-bzip"
  297.         "bz2": return "application/x-bzip2"
  298.         "csh": return "application/x-csh"
  299.         "css": return "text/css"
  300.         "csv": return "text/csv"
  301.         "dcr", "dir", "dxr": return "application/x-director"
  302.         "doc": return "application/msword"
  303.         "docx": return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
  304.         "eot": return "application/vnd.ms-fontobject"
  305.         "epub": return "application/epub+zip"
  306.         "gif": return "image/gif"
  307.         "gz": return "application/gzip"
  308.         "htm", "html": return "text/html"
  309.         "ico": return "image/x-icon"
  310.         "ics": return "text/calendar"
  311.         "jar": return "application/java-archive"
  312.         "jpeg", "jpg": return "image/jpeg"
  313.         "js": return "application/javascript"
  314.         "json": return "application/json"
  315.         "mid", "midi": return "audio/midi"
  316.         "mov": return "video/quicktime"
  317.         "mp3": return "audio/mpeg"
  318.         "mp4": return "video/mp4"
  319.         "mpg", "mpeg": return "video/mpeg"
  320.         "mpkg": return "application/vnd.apple.installer+xml"
  321.         "odp": return "application/vnd.oasis.opendocument.presentation"
  322.         "ods": return "application/vnd.oasis.opendocument.spreadsheet"
  323.         "odt": return "application/vnd.oasis.opendocument.text"
  324.         "oga","ogg": return "audio/ogg"
  325.         "ogv": return "video/ogg"
  326.         "otf": return "font/otf"
  327.         "png": return "image/png"
  328.         "pdf": return "application/pdf"
  329.         "ppt": return "application/vnd.ms-powerpoint"
  330.         "pptx": return "application/vnd.openxmlformats-officedocument.presentationml.presentation"
  331.         "rar": return "application/x-rar-compressed"
  332.         "rtf": return "application/rtf"
  333.         "sh": return "application/x-sh"
  334.         "svg": return "image/svg+xml"
  335.         "swf": return "application/x-shockwave-flash"
  336.         "tar": return "application/x-tar"
  337.         "tif", "tiff": return "image/tiff"
  338.         "ts": return "application/typescript"
  339.         "ttf": return "font/ttf"
  340.         "txt": return "text/plain"
  341.         "vsd": return "application/vnd.visio"
  342.         "wav": return "audio/wav"
  343.         "weba": return "audio/webm"
  344.         "webm": return "video/webm"
  345.         "webp": return "image/webp"
  346.         "woff": return "font/woff"
  347.         "woff2": return "font/woff2"
  348.         "xhtml": return "application/xhtml+xml"
  349.         "xls": return "application/vnd.ms-excel"
  350.         "xlsx": return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
  351.         "xml": return "application/xml"
  352.         "zip": return "application/zip"
  353.     end case
  354.     return "application/octet-stream"
  355. end
  356.  
  357. ----------------------------------------
  358. --
  359. ----------------------------------------
  360. on xtraVersion (xtraName)
  361.     xtraName = symbol(xtraName)
  362.     repeat with x in _player.scriptingXtraList
  363.         if value("x.name") = xtraName then return value("x.version")
  364.     end repeat
  365. end
  366.  
  367. ----------------------------------------
  368. -- "unique id" - not guaranteed to be unique, but most likely
  369. ----------------------------------------
  370. on uid
  371.     return string(random(the maxInteger) & "-" & random(the maxInteger) & "-" & random(the maxInteger))
  372. end
  373.  
[raw code]