# Property of Four Js* # (c) Copyright Four Js 1995, 2019. All Rights Reserved. # * Trademark of Four Js Development Tools Europe Ltd # in the United States and elsewhere # # Four Js and its suppliers do not warrant or guarantee that these # samples are accurate and suitable for your purposes. Their inclusion is # purely for information purposes only. IMPORT os IMPORT util IMPORT FGL MsgBox DEFINE charset STRING DEFINE boundary STRING DEFINE rv INTEGER DEFINE smtplog base.StringBuffer DEFINE logtext STRING DEFINE tz STRING DEFINE ext2mime DYNAMIC ARRAY OF RECORD ext STRING, mime STRING, encoding STRING END RECORD &define EXT2MIME(a, b, c) \ LET ext2mime[nmime].ext = a \ LET ext2mime[nmime].mime = b \ LET ext2mime[nmime].encoding = c \ LET nmime = nmime + 1 TYPE cnf_t RECORD hostname STRING, smtphost STRING, smtpport INTEGER, senderemail STRING, cnxtimeout INTEGER END RECORD DEFINE cnf cnf_t MAIN DEFINE subject STRING DEFINE recipients DYNAMIC ARRAY OF RECORD mode STRING, recipient STRING END RECORD DEFINE mailtext STRING DEFINE mailbody DYNAMIC ARRAY OF STRING DEFINE attachments DYNAMIC ARRAY OF RECORD fname, sname STRING END RECORD DEFINE rcpt DYNAMIC ARRAY OF STRING DEFINE nmime INTEGER DEFINE filename STRING OPTIONS INPUT WRAP # TZ checks IF NUM_ARGS() == 1 THEN IF ARG_VAL(1) == "TZGMT" THEN CALL fgl_setenv("TZ", "GMT") CALL showTZ() END IF IF ARG_VAL(1) == "TZLOCAL" THEN CALL showTZ() END IF IF ARG_VAL(1) == "DTSHOW" THEN DISPLAY CURRENT YEAR TO MINUTE END IF EXIT PROGRAM 0 END IF OPEN FORM maildemo FROM "Mailer" DISPLAY FORM maildemo LET charset = getCharset() # Mime types initialization - should be left configurable by the client LET nmime = 1 EXT2MIME("bin", "application/octet-stream", "base64") EXT2MIME("pdf", "application/x-pdf", "base64") EXT2MIME("gz", "application/x-gzip", "base64") EXT2MIME("tgz", "application/x-compressed-tar", "base64") EXT2MIME("gif", "image/gif", "base64") EXT2MIME("jpg", "image/jpg", "base64") EXT2MIME("png", "image/png", "base64") EXT2MIME("txt", "text/plain", "7bit") EXT2MIME("per", "text/plain", "7bit") EXT2MIME("4gl", "text/plain", "7bit") EXT2MIME("42m", "application/octet-stream", "base64") CALL load_maildemo_cnf() LET tz = detect_timezone() LET boundary='--------------' || util.Math.rand(1000) || util.Math.rand(1000) || util.Math.rand(1000) || util.Math.rand(1000) # Mail composition dialog DIALOG ATTRIBUTE(UNBUFFERED) INPUT BY NAME cnf.* ATTRIBUTES(WITHOUT DEFAULTS) END INPUT INPUT ARRAY recipients FROM srrecipients.* ATTRIBUTES(WITHOUT DEFAULTS) BEFORE INSERT LET recipients[dialog.getCurrentRow("srrecipients")].mode = "To" AFTER ROW IF LENGTH(recipients[dialog.getCurrentRow("srrecipients")].recipient) == 0 THEN CALL recipients.deleteElement(dialog.getCurrentRow("srrecipients")) END IF END INPUT INPUT BY NAME subject, mailtext END INPUT DISPLAY ARRAY attachments TO srattachments.* END DISPLAY INPUT BY NAME logtext END INPUT BEFORE DIALOG IF LENGTH(cnf.hostname) == 0 OR LENGTH(cnf.smtpHost) == 0 OR LENGTH(cnf.senderEmail) == 0 THEN NEXT FIELD hostname END IF NEXT FIELD recipient ON ACTION attachfile CALL ui.Interface.frontCall("standard", "openfile", ["C:\\", "*", "", "Attach file"], [filename]) IF filename IS NOT NULL THEN LET attachments[attachments.getLength()+1].fname = filename LET attachments[attachments.getLength()].sname = "attachment" || (attachments.getLength() USING "<<<<<<<") END IF ON ACTION delattachment CALL attachments.deleteElement(dialog.getCurrentRow("srattachments")) ON ACTION viewattachment CALL mbox_ok("View attachment", "View : " || attachments[dialog.getCurrentRow("srattachments")].sname, "information") ON ACTION close EXIT DIALOG ON ACTION send IF INFIELD(recipient) THEN # We are currently editing the recipient column IF LENGTH(recipients[dialog.getCurrentRow("srrecipients")].recipient) == 0 THEN # Empty, remove it # AFTER FIELD has not been executed, do it here CALL recipients.deleteElement(dialog.getCurrentRow("srrecipients")) END IF END IF IF recipients.getLength() == 0 THEN CALL mbox_ok("Error", "The recipient list is empty.", "stop") CONTINUE DIALOG END IF IF LENGTH(subject) == 0 THEN # Note: click on the X button returns NULL or "", I would expect the default specified, i.e. "No" here. IF NOT mbox_yn("Warning", "The subject line is empty.\nDo you really want to send this mail ?", "exclamation") THEN NEXT FIELD subject END IF END IF IF NOT retrieve_attachments(attachments) THEN CONTINUE DIALOG END IF CALL genmail(cnf.senderemail, recipients, subject, mailtext, attachments, mailbody, rcpt) IF sendmail(rcpt, mailbody) THEN CALL clear_attachments(attachments) CALL mbox_ok("Send mail", "Mail successfully sent.", "information") CALL rcpt.clear() CALL recipients.clear() CALL attachments.clear() CALL mailbody.clear() LET subject = "" LET mailtext = "" NEXT FIELD recipient END IF CALL clear_attachments(attachments) ON ACTION savecnf CALL save_maildemo_cnf() ON ACTION chkcnf CALL check_maildemo_cnf() END DIALOG END MAIN # Retrieve the current charset FUNCTION getCharset() DEFINE l STRING DEFINE c base.Channel LET c = base.Channel.create() CALL c.openPipe("fglrun -i mbcs 2>&1", "r") LET l = c.readLine() WHILE l IS NOT NULL IF l MATCHES "Charmap : *" THEN RETURN l.subString(16,length(l)) EXIT WHILE END IF LET l = c.readLine() END WHILE END FUNCTION &define MB(array, idx, line) \ LET array[idx] = line \ LET idx = idx + 1 # Generate the mail content FUNCTION genmail(senderemail, recipients, subject, mailtext, attachments, mailbody, rcpt) DEFINE senderemail STRING DEFINE subject STRING DEFINE recipients DYNAMIC ARRAY OF RECORD mode STRING, recipient STRING END RECORD DEFINE mailtext STRING DEFINE sbmt base.StringBuffer DEFINE i, found INTEGER DEFINE rcpt DYNAMIC ARRAY OF STRING DEFINE attachments DYNAMIC ARRAY OF RECORD fname, sname STRING END RECORD DEFINE nl INTEGER DEFINE nrcpt INTEGER DEFINE nr INTEGER DEFINE extension STRING DEFINE mimeid INTEGER DEFINE mailbody DYNAMIC ARRAY OF STRING LET nl = 1 LET nr = 1 LET nrcpt = 0 # Add mail date MB(mailbody, nl, SFMT("Date: %1 %2 %3\r", TODAY USING "ddd, dd mmm yyyy", TIME(CURRENT), tz)) # First add headers MB(mailbody, nl, SFMT("From: %1\r", senderemail)) # Add user agent MB(mailbody, nl, "User-Agent: maildemo sample program.\r") # Add MIME version MB(mailbody, nl, "MIME-Version: 1.0\r") # Add To: recipients LET found = 0 FOR i = 1 TO recipients.getLength() IF recipients[i].mode == "To" THEN IF found THEN LET mailbody[nl] = mailbody[nl].append(SFMT(", %1", recipients[i].recipient)) ELSE LET mailbody[nl]=SFMT("To: %1", recipients[i].recipient) LET found = 1 END IF MB(rcpt, nr, SFMT("RCPT TO: %1", extract_email(recipients[i].recipient))) END IF END FOR IF found == 1 THEN LET mailbody[nl] = mailbody[nl].append("\r") LET nl = nl + 1 END IF # Add CC: recipients LET found = 0 FOR i = 1 TO recipients.getLength() IF recipients[i].mode == "CC" THEN IF found THEN LET mailbody[nl] = mailbody[nl].append(SFMT(", %1", recipients[i].recipient)) ELSE LET mailbody[nl]=SFMT("CC: %1", recipients[i].recipient) LET found = 1 END IF MB(rcpt, nr, SFMT("RCPT TO: %1", extract_email(recipients[i].recipient))) END IF END FOR IF found == 1 THEN LET mailbody[nl] = mailbody[nl].append("\r") LET nl = nl + 1 END IF # Add CC: recipients LET found = 0 FOR i = 1 TO recipients.getLength() IF recipients[i].mode == "BCC" THEN IF found THEN LET mailbody[nl] = mailbody[nl].append(SFMT(", %1", recipients[i].recipient)) ELSE LET mailbody[nl]=SFMT("BCC: %1", recipients[i].recipient) LET found = 1 END IF MB(rcpt, nr, SFMT("RCPT TO: %1", extract_email(recipients[i].recipient))) END IF END FOR IF found == 1 THEN LET mailbody[nl] = mailbody[nl].append("\r") LET nl = nl + 1 END IF MB(mailbody, nl, SFMT("Subject: %1\r", subject)) # Now add the message content # Multipart message not handled # Should check the body is really 7bit and not line starts with ., ... # To be conformant with 7bit encoding. See RFC for details. # \n.\n is the DATA section end mark. # Should be replaced by \n..\n LET sbmt = base.StringBuffer.create() CALL sbmt.append(mailtext) # Need to do it twice to properly handle following case: \n.\n.\n.\n.\n.\n CALL sbmt.replace("\n.\n", "\n..\n", 0) CALL sbmt.replace("\n.\n", "\n..\n", 0) LET mailtext=sbmt.toString() IF attachments.getLength() == 0 THEN MB(mailbody, nl, SFMT("Content-Type: text/plain; charset=%1\r", "ISO-8859-1")) MB(mailbody, nl, SFMT("Content-Transfer-Encoding: %1\r", "7bit")) MB(mailbody, nl, "\r") MB(mailbody, nl, mailtext) MB(mailbody, nl, "\r") ELSE MB(mailbody, nl, "Content-Type: multipart/mixed;\r") MB(mailbody, nl, ' boundary="' || boundary || '"\r') MB(mailbody, nl, "\r") MB(mailbody, nl, "This is a multi-part message in MIME format.\r") # Add message text MB(mailbody, nl, "--" || boundary || "\r") MB(mailbody, nl, SFMT("Content-Type: text/plain; charset=%1; format=flowed\r", "ISO-8859-1")) MB(mailbody, nl, SFMT("Content-Transfer-Encoding: %1\r", "7bit")) MB(mailbody, nl, "\r") MB(mailbody, nl, mailtext) MB(mailbody, nl, '\r') FOR i = 1 TO attachments.getLength() MB(mailbody, nl, "--" || boundary || "\r") LET extension = os.Path.extension(attachments[i].fname) LET mimeid = getmime(extension) IF mimeid == -1 THEN # Unknown extension. # Default to application/octet-stream, base64 encoded LET mimeid = 1 END IF CASE ext2mime[mimeid].encoding WHEN "base64" MB(mailbody, nl, SFMT("Content-Type: %1;\r", ext2mime[mimeid].mime)) MB(mailbody, nl, ' name="'|| attachments[i].fname || '"\r') MB(mailbody, nl, 'Content-Transfer-Encoding: base64\r') MB(mailbody, nl, 'Content-Disposition: inline;\r') MB(mailbody, nl, ' filename="'|| os.Path.basename(attachments[i].fname) || '"\r') MB(mailbody, nl, '\r') LET nl = file_to_base64(attachments[i].sname, mailbody, nl) WHEN "7bit" MB(mailbody, nl, SFMT("Content-Type: %1;\r", ext2mime[mimeid].mime)) MB(mailbody, nl, ' name="'|| attachments[i].fname || '"\r') MB(mailbody, nl, 'Content-Transfer-Encoding: 7bit\r') MB(mailbody, nl, 'Content-Disposition: inline;\r') MB(mailbody, nl, ' filename="'|| os.Path.basename(attachments[i].fname) || '"\r') MB(mailbody, nl, '\r') LET nl = file_to_7bit(attachments[i].sname, mailbody, nl) OTHERWISE CALL mbox_ok("Attachment encoding", SFMT("Unsupported encoding %1.", ext2mime[mimeid].encoding), "stop") EXIT PROGRAM 1 END CASE END FOR MB(mailbody, nl, "--" || boundary || "--\r") END IF # Add end of message marker MB(mailbody, nl, ".\r") END FUNCTION FUNCTION smtpSend(ch, command) DEFINE ch base.Channel DEFINE command STRING DEFINE smtpStatus INTEGER DEFINE smtpMessage STRING CALL clientWrite(ch, command) CALL readSmtpAnswer(ch) RETURNING smtpStatus, smtpMessage RETURN smtpStatus, smtpMessage END FUNCTION FUNCTION readSmtpAnswer(ch) DEFINE ch base.Channel DEFINE line STRING DEFINE smtpStatus INTEGER DEFINE smtpMessage STRING LET smtpMessage = "" WHILE 1 LET line = serverRead(ch) IF line IS NULL THEN RETURN -1, "COULD NOT READ SMTP ANSWER" END IF IF line MATCHES "[0-9][0-9][0-9] *" THEN IF smtpMessage.getLength() != 0 THEN LET smtpMessage=smtpMessage || "\n" END IF LET smtpMessage=smtpMessage.append(line.subString(4, line.getLength())) LET smtpStatus = line.subString(1,3) RETURN smtpStatus, smtpMessage END IF IF line MATCHES "[0-9][0-9][0-9]-*" THEN IF smtpMessage.getLength() != 0 THEN LET smtpMessage=smtpMessage || "\n" END IF LET smtpMessage=smtpMessage.append(line.subString(4, line.getLength())) END IF END WHILE END FUNCTION FUNCTION sendmail(rcpt, mailbody) DEFINE mailbody DYNAMIC ARRAY OF STRING DEFINE rcpt DYNAMIC ARRAY OF STRING DEFINE smtpStatus INTEGER DEFINE smtpMessage STRING DEFINE mc base.Channel DEFINE i INTEGER LET smtplog = base.StringBuffer.create() LET mc = base.Channel.create() TRY CALL mc.openClientSocket(cnf.smtphost, cnf.smtpport, "ub", cnf.cnxtimeout) CATCH CALL mbox_ok("Socket error", "The connection to the SMTP Server failed.\n", "stop") RETURN FALSE END TRY CALL readSmtpAnswer(mc) RETURNING smtpStatus, smtpMessage CALL smtpSend(mc, "HELO " || cnf.hostname || "\r") RETURNING smtpStatus, smtpMessage IF smtpStatus >= 400 THEN CALL smtpError(mc, smtpMessage) RETURN FALSE END IF CALL smtpSend(mc, SFMT("MAIL FROM: %1\r", extract_email(cnf.senderemail))) RETURNING smtpStatus, smtpMessage IF smtpStatus >= 400 THEN CALL smtpError(mc, smtpMessage) RETURN FALSE END IF FOR i = 1 TO rcpt.getLength() CALL smtpSend(mc, rcpt[i]) RETURNING smtpStatus, smtpMessage IF smtpStatus >= 400 THEN CALL smtpError(mc, smtpMessage) RETURN FALSE END IF END FOR CALL smtpSend(mc, "DATA\r") RETURNING smtpStatus, smtpMessage IF smtpStatus >= 400 THEN CALL smtpError(mc, smtpMessage) RETURN FALSE END IF FOR i = 1 TO mailbody.getLength() CALL clientWrite(mc, mailbody[i]) END FOR CALL readSmtpAnswer(mc) RETURNING smtpStatus, smtpMessage IF smtpStatus >= 400 THEN CALL smtpError(mc, smtpMessage) RETURN FALSE END IF CALL smtpSend(mc, "QUIT\r") RETURNING smtpStatus, smtpMessage IF smtpStatus >= 400 THEN CALL smtpError(mc, smtpMessage) RETURN FALSE END IF CALL mc.close() RETURN TRUE END FUNCTION FUNCTION clientWrite(mc, line) DEFINE mc base.Channel DEFINE line STRING CALL smtplog.append('C> ') CALL smtplog.append(line) CALL smtplog.append("
") LET logtext=smtplog.toString() CALL mc.writeLine(line) END FUNCTION FUNCTION serverRead(mc) DEFINE line STRING DEFINE mc base.Channel LET line = mc.readLine() CALL smtplog.append("S> ") CALL smtplog.append(line) CALL smtplog.append("
") LET logtext=smtplog.toString() RETURN line END FUNCTION # Returns the mime entry index for the extension `ext' # -1 if not entry matching the extension is found. FUNCTION getmime(ext) DEFINE ext STRING DEFINE j INTEGER FOR j = 1 TO ext2mime.getLength() IF ext2mime[j].ext == ext THEN RETURN j END IF END FOR RETURN -1 END FUNCTION # Save configuration FUNCTION save_maildemo_cnf() DEFINE pch base.Channel DEFINE path STRING LET path = os.Path.join(os.Path.homedir(), "maildemo.cnf") IF os.Path.exists(path) THEN IF NOT os.Path.writable(path) THEN CALL mbox_ok("Write configuration", SFMT("The file %1 can not be written", path), "stop") RETURN END IF END IF TRY LET pch=base.channel.create() CALL pch.openFile(path, "w") CALL pch.write([cnf.*]) CALL pch.close() CATCH CALL mbox_ok("Write configuration", SFMT("Failed to save the configuration in the file %1", path), "stop") RETURN END TRY CALL mbox_ok("Save configuration", SFMT("The configuration was successfully saved in %1.", path), "information") END FUNCTION # Load configuration FUNCTION load_maildemo_cnf() DEFINE pch base.Channel DEFINE path STRING LET path = os.Path.join(os.Path.homedir(), "maildemo.cnf") IF NOT os.Path.exists(path) THEN CALL default_maildemo_cnf() RETURN END IF IF NOT os.Path.readable(path) THEN CALL default_maildemo_cnf() RETURN END IF # Read save parameters LET pch = base.channel.create() CALL pch.openFile(path,"r") LET rv = pch.read([cnf.*]) CALL pch.close() IF rv THEN RETURN END IF CALL default_maildemo_cnf() END FUNCTION FUNCTION default_maildemo_cnf() LET cnf.smtpPort = 25 LET cnf.smtpHost = "" LET cnf.senderEmail = "" LET cnf.hostname = "" LET cnf.cnxtimeout = 10 END FUNCTION FUNCTION file_to_base64(fname, mailbody, nl) DEFINE mailbody DYNAMIC ARRAY OF STRING DEFINE fname STRING DEFINE nl INTEGER DEFINE l BYTE DEFINE lfn STRING DEFINE doc om.DomDocument DEFINE node om.DomNode DEFINE lnode om.NodeList LET lfn = fname || ".xml" LOCATE l IN FILE fname CALL fgl_report_set_document_handler(om.XmlWriter.createFileWriter(lfn)) START REPORT gen_base64 TO FILE "dummy_report_file" OUTPUT TO REPORT gen_base64(l) FINISH REPORT gen_base64 # Load the generated XML file LET doc = om.DomDocument.createFromXmlFile(lfn) LET node = doc.getDocumentElement() LET lnode = node.selectByTagName("Item") LET node = lnode.item(1) MB(mailbody, nl, node.getAttribute("value")) IF NOT os.Path.delete(lfn) THEN CALL mbox_ok("Warning", SFMT("The temporary file %1 could not be removed.", lfn), "stop") END IF RETURN nl END FUNCTION # Convert a file to a base64 string REPORT gen_base64(l) DEFINE l BYTE OUTPUT PAGE LENGTH 1 TOP MARGIN 0 BOTTOM MARGIN 0 LEFT MARGIN 0 FORMAT ON EVERY ROW PRINTX name=base64_l l END REPORT # Convert a file to 7bit encoding (fake) FUNCTION file_to_7bit(fname, mailbody, nl) DEFINE fname STRING DEFINE mailbody DYNAMIC ARRAY OF STRING DEFINE ac base.Channel DEFINE line STRING DEFINE nl INTEGER LET ac = base.Channel.create() CALL ac.openFile(fname, "rb") WHILE NOT ac.isEof() LET line = ac.readLine() MB(mailbody, nl, line) END WHILE CALL ac.close() RETURN nl END FUNCTION FUNCTION check_maildemo_cnf() DEFINE smtpStatus INTEGER DEFINE smtpMessage STRING DEFINE mc base.Channel LET smtplog = base.StringBuffer.create() LET mc = base.Channel.create() TRY CALL mc.openClientSocket(cnf.smtphost, cnf.smtpport, "ub", cnf.cnxtimeout) CATCH CALL mbox_ok("Socket error", "The connection to the SMTP Server failed.\n", "stop") RETURN END TRY CALL readSmtpAnswer(mc) RETURNING smtpStatus, smtpMessage CALL smtpSend(mc, "HELO " || cnf.hostname || "\r") RETURNING smtpStatus, smtpMessage IF smtpStatus >= 400 THEN CALL smtpError(mc, smtpMessage) RETURN END IF CALL smtpSend(mc, "QUIT\r") RETURNING smtpStatus, smtpMessage IF smtpStatus >= 400 THEN CALL smtpError(mc, smtpMessage) RETURN END IF CALL mc.close() CALL mbox_ok("SMTP Connection", "The SMTP parameters are correct", "information") RETURN END FUNCTION FUNCTION smtpError(mc, msg) DEFINE msg STRING DEFINE mc base.Channel DEFINE smtpStatus INTEGER DEFINE smtpMessage STRING CALL mbox_ok("SMTP Error", msg, "stop") CALL smtpSend(mc, "QUIT\r") RETURNING smtpStatus, smtpMessage CALL mc.close() END FUNCTION FUNCTION getProgramCmd() DEFINE cmd STRING LET cmd = os.Path.join(fgl_getenv("FGLDIR"), "demo") LET cmd = os.Path.join(cmd, "MultipleDialogs") LET cmd = os.Path.join(cmd, "Mailer.42m") IF NOT os.Path.exists(cmd) THEN LET cmd = os.Path.join(".", "Mailer.42m") IF NOT os.Path.exists(cmd) THEN DISPLAY "ERROR: Could not find Mailer.42m program file." EXIT PROGRAM 1 END IF END IF IF fgl_getenv("WINDIR") THEN LET cmd = "\"" || cmd || "\"" END IF LET cmd = SFMT("fglrun %1 ", cmd) RETURN cmd END FUNCTION FUNCTION showTZ() CALL FGL_SETENV("FGLGUI","0") RUN getProgramCmd() || "DTSHOW" CALL FGL_SETENV("FGLGUI","1") END FUNCTION FUNCTION detect_timezone() DEFINE ch base.Channel DEFINE dnow, dgmt DATETIME YEAR TO MINUTE DEFINE dh INTERVAL MINUTE(5) TO MINUTE DEFINE sminutes STRING DEFINE iminutes INTEGER DEFINE ihours INTEGER CALL FGL_SETENV("FGLGUI","0") LET ch = base.Channel.create() CALL ch.openPipe(getProgramCmd()||" TZLOCAL", "r") LET rv=ch.read(dnow) CALL ch.close() CALL ch.openPipe(getProgramCmd()||" TZGMT", "r") LET rv=ch.read(dgmt) CALL ch.close() LET dh = dnow - dgmt LET sminutes = dh LET iminutes = sminutes IF iminutes MOD 2 == 1 THEN LET iminutes = iminutes + 1 END IF LET ihours = iminutes / 60 LET iminutes = iminutes - ihours * 60 CALL FGL_SETENV("FGLGUI","1") RETURN SFMT("%1%2", ihours USING "+&&", iminutes USING "&&") END FUNCTION { Retrieves attachments from the client. If many attachments should be retrieved, maybe a progress bar could be displayed } FUNCTION retrieve_attachments(attachments) DEFINE attachments DYNAMIC ARRAY OF RECORD fname, sname STRING END RECORD DEFINE i, j INTEGER FOR i = 1 TO attachments.getLength() DISPLAY "Retrieving attachment : ", attachments[i].fname, " TO ", attachments[i].sname TRY CALL fgl_getfile(attachments[i].fname, attachments[i].sname) CATCH CALL mbox_ok("Attachment download", SFMT("Could not retrieve attachment %1", attachments[i].fname), "stop") FOR j = 1 TO i LET rv = os.Path.delete(attachments[j].sname) END FOR RETURN FALSE END TRY END FOR RETURN TRUE END FUNCTION # Deletes temporary attachment files FUNCTION clear_attachments(attachments) DEFINE attachments DYNAMIC ARRAY OF RECORD fname, sname STRING END RECORD DEFINE i INTEGER FOR i = 1 TO attachments.getLength() LET rv = os.Path.delete(attachments[i].sname) END FOR END FUNCTION FUNCTION extract_email(email) DEFINE email STRING DEFINE s, e INTEGER LET s = email.getIndexOf("<", 1) LET e = email.getIndexOf(">", 1) IF s == 0 OR e == 0 OR s >= e THEN RETURN email END IF RETURN email.subString(s + 1, e - 1) END FUNCTION