Monday, November 08, 2010 at 12:06 AM.
system.verbs.builtins.tcp.getMail
on getMail (host, username, password, adrMsgTable, deleteMessages="", timeOutTicks="", flMessages="", adrDeleteCallback=nil) { <<Changes: <<9/24/03; 2:50:06 AM by JES <<Added changes from JY Stervinou to handle attachments from a wider vareity of email clients. <<5/8/03; 5:56:41 PM by JES <<New optional parameter, adrDeleteCallback. If deleteMessages is false, then this callback is called with the address of each message table. If the callback returns true, then the message is deleted from the server -- if false, then the message is not deleted. <<2/28/01; 5:20:26 PM by PBS <<Bottleneck closing and aborting the stream so the connection counter doesn't try to go negative. <<Also: fixed a scriptError message that began "Can't send mail" -- now it begins "Can't get mail." local { stream; msgCount = 0; theMessage; i; response = ""; buffer = ""; flStreamOpen = true}; bundle { //set defaults if deleteMessages == "" { if defined (user.prefs.getMail.deleteMessages) { deleteMessages = user.prefs.getMail.deleteMessages} else { deleteMessages = false}}; if timeOutTicks == "" { if defined (user.prefs.getMail.timeOutTicks) { timeOutTicks = user.prefs.getMail.timeOutTicks} else { timeOutTicks = 600}}; if flMessages == "" { if defined (user.prefs.getMail.flMessages) { flMessages = user.prefs.getMail.flMessages} else { flMessages = false}}}; on closeStream () { //PBS 02/28/01: bottleneck if flStreamOpen { try {tcp.closeStream (stream)}}; flStreamOpen = false}; on abortStream () { //PBS 02/28/01: bottleneck if flStreamOpen { try {tcp.abortStream (stream)}}; flStreamOpen = false}; on parseMsg2 (s="",adrMsgTable=nil, adrMsg=false, parseSMTP=true, poundNameCompatibility=true) { <<This script courtesy of Eric Soroos with modifications by Alan German, April 2000 <<It carries Eric's original name <<AG added the bundle at the bottom of script that does some rough bursting of multi-part messages <<It's called with poundNameCompatibility:false so that it does not put the '#' character in front of the header names. <<This script parses the message into the table specified by adrTable. <<s is the message to be parsed. This is a compatibility option, and will <<probably not be supported past this release <<It is fundamentally incompatible with adrMsg <<I know it's the first option, but. <<That means it can be dropped in for parseMsg. <<AdrMsg, if defined, will be copied into the table at adrMsgTable. <<parseSMTP will take out the period encoding <<pound name compatibility will make the name of the header items <<#to <<#subject <<etc... <<otherwise it will be <<to <<subject <<etc... <<Fri, Mar 12, 1999 at 4:10:28 PM by ES <<Replaces earlier work by Gavin Eadie and Tom Clifton <<Well, Not really. This is a lot more sparse, and uses userland's code instead. <<This is essentially a wrapper around userland's webserver.parsehttpresponse. local { headerLocation = 0; msgTable}; if (adrMsgTable == nil) { new(tableType,@msgTable); adrMsgTable= @msgTable}; if sizeOF(s) >0 { adrMsgTable^.msg = s}; if not(adrMsg == false) { <<if they passed in an address for the message, then move the <<message to the appropriate spot adrMsgTable^.msg = adrMsg}; adrMsg = @adrMsgTable^.msg; // set adrMsg appropriately bundle { //split out the header headerLocation = string.patternMatch("\r\n\r\n",adrMsg^); adrMsgTable^.text = string.delete(adrMsg^,1,headerLocation+3); adrMsgTable^.fullHeader = string.delete(adrMsg^,headerLocation,infinity)}; <<text may be pretty big, as could be msg. We are keeping msg for <<pop purposes. We're keeping text for further mime processing. <<Fullheader is around for showing what the headers were. <<I like them, but some people don't. if (parseSMTP) { adrMsgTable^.text = string.replaceAll(adrMsgTable^.text,CR+LF+".",CR+LF)}; bundle { // unroll the header. <<lines in the header conatin the following possibilities. <<cr lf whitespace -> continuation of previous header. <<cr lf character -> new header <<we'll do the best we can with the string verbs... cause I can't get the regex to work adrMsgTable^.fullHeader =string.replaceAll(adrMsgTable^.fullHeader,"\t"," "); adrMsgTable^.fullHeader = string.replaceAll(adrMsgTable^.fullHeader,"\r\n "," ")}; if sizeOf(adrMsgTable^.fullHeader) > 0 { bundle { // split out the headers local { headers; i}; <<webserver.util.parseHeaders(adrMsgTable^.fullHeader+cr+lf+cr+lf, @headers) string.httpResultSplit(adrMsgTable^.fullHeader+cr+lf+cr+lf, @headers); for i = 1 to sizeOf(headers) { if (poundNameCompatibility) { adrMsgTable^.["#"+nameof(headers[i])]=headers[i]} else { adrMsgTable^.[nameOf(headers[i])] = headers[i]}}}}; bundle { // added 4.13.00 by asg - bust out mime parts; no decoding done here // find out if multi-part or not local (contenttype="Content-Type"); if poundNameCompatibility { contenttype = "#"+contenttype}; if not defined (adrMsgTable^.[contenttype]) { return}; // there is no content-type header? local (tempcontenttype = string.lower (adrMsgTable^.[contenttype])); if not (tempcontenttype contains "multipart/" and tempcontenttype contains "boundary") { return}; // no multipart boundary, no need to burst local (boundary = adrMsgTable^.[contenttype]); // start with what Eric parsed as Content-Type local (partslist, aPart); bundle { // determine boundary and split into parts bundle { //new code by JY Stervinou handles a wider vareity of email clients local (matchtable); if not regex.easySearch ("boundary *= *\"?[^\"]*\"?", boundary, @matchtable) { return}; // can't find boundary definition if not regex.easySearch ("= *\"?([^\"]*)", matchtable.matchstring, @matchtable) { return}; // can't determine boundary definition matchtable.matchstring = string.popLeading (matchtable.matchstring, '='); // pop leading = matchtable.matchstring = string.popLeading (matchtable.matchstring, ' '); // pop leading spaces matchtable.matchstring = string.popLeading (matchtable.matchstring, '"'); // pop leading quote boundary = "\r\n--" + matchtable.matchstring; // leading crlf is part of boundary boundary = string.replaceAll (boundary, "(", "\\("); boundary = string.replaceAll (boundary, ")", "\\)"); boundary = string.replaceAll (boundary, "?", "\\?"); boundary = string.replaceAll (boundary, "+", "\\+"); boundary = string.replaceAll (boundary, ".", "\\."); partslist = regex.split (boundary, @adrMsgTable^.text)}}; <<bundle //original code <<local (matchtable) <<if not regex.easySearch ("boundary *= *\"[^\"]*\"", boundary, @matchtable) <<return // can't find boundary definition <<if not regex.easySearch ("\"([^\"]*)", matchtable.matchstring, @matchtable) <<return // can't determine boundary definition <<matchtable.matchstring = string.popLeading (matchtable.matchstring, '"') // pop leading quote <<boundary = "\r\n--" + matchtable.matchstring // leading crlf is part of boundary << <<boundary = string.replaceAll (boundary, "(", "\\(") <<boundary = string.replaceAll (boundary, ")", "\\)") <<boundary = string.replaceAll (boundary, "?", "\\?") <<boundary = string.replaceAll (boundary, "+", "\\+") <<boundary = string.replaceAll (boundary, ".", "\\.") << <<partslist = regex.split (boundary, @adrMsgTable^.text) <<adrMsgTable^.text = "" // replaces Eric's text for aPart in partslist { if sizeOf (aPart) == 0 { // regex puts an empty one on the front continue}; if aPart beginsWith "--\r\n" { // this marks where the last part ends and we're done break}; local (partinfo, infoend); if aPart beginsWith "\r\n\r\n" { // the part begins with a blank line-- assume text/plain aPart = string.delete (aPart, 1, 4); // strip cr+lf+cr+lf adrMsgTable^.text = aPart; // replaces Eric's text continue}; if not (aPart beginsWith "\r\n") { // This is the preamble area of a multipart message. Mail readers that understand multipart format should ignore this preamble. continue}; aPart = string.delete (aPart, 1, 2); // strip cr+lf infoend = string.patternMatch ("\r\n\r\n", aPart); // blank line separates part headers and part partinfo = string.mid (aPart, 1, infoend+3); // usually at least content type aPart = aPart - partinfo; partinfo = partinfo - "\r\n\r\n"; if string.lower (partinfo) contains "text/plain" { // this is the msgText adrMsgTable^.text = aPart} // replaces Eric's text else { if not defined (adrMsgTable^.parts) { // create part table new (tableType, @adrMsgTable^.parts)}; local (partstorage = table.uniqueName ("", @adrMsgTable^.parts, 4)); new (tableType, partstorage); partstorage^.["Content-Type"] = partinfo; partstorage^.data = aPart; if flMessages { msg ("")}}}}; // it's an part? return(adrMsgTable)}; on displayMsg (s) { msg ("tcp.getMail: " + s)}; on sendCommandToServer (stream, command) { //AR 10/27/1999 if not (command endsWith cr+lf) { command = command + cr + lf}; try { tcp.writeStringToStream (stream, command, 4096, timeOutTicks / 60)} else { if flMessages { //clean up the message area msg ("")}; <<try {tcp.abortStream (stream)} abortStream (); scriptError ("Can't get mail because a connection error occured: " + tryerror)}; return (true)}; on receiveServerResponse (stream) { //asg 04/11/00 local (response = ""); try { tcp.readStreamUntil (stream, "\r\n", timeOutTicks / 60, @response); return (response - "\r\n")} else { <<try {tcp.abortStream (stream)} abortStream (); if flMessages { //clean up the message area msg ("")}; scriptError ("Can't get mail because a connection error occured: " + tryerror)}}; on executeCommand (command, expectResponse = nil) { //send a command to the server and handle the response local (statusline=""); sendCommandToServer (stream, command + "\r\n"); statusline = receiveServerResponse (stream); if flMessages { displayMsg (statusline)}; if (expectResponse != nil) and not (statusline beginsWith expectResponse) { closeConnection ("Can't get mail because of an unexpected server response. I said \"" + command + "\" and the server replied \"" + statusline + "\".")}; return (true)}; on closeConnection (errorstring = "") { //also called in case of unexpected server responses try {executeCommand ("QUIT")}; <<try {tcp.closeStream (stream)} closeStream (); if flMessages { //clean up the message area msg ("")}; if errorstring != "" { //possibly (re)throw scripterrors scriptError (errorstring)}}; new (tableType, adrMsgTable); try { // try to connect to server stream = tcp.openStream (host, 110); flStreamOpen = true} else { // fatal error scriptError ("Can't get mail because the connection to " + host + " could not be opened: " + tryError)}; bundle { //wait for server's initial connection greeting local (statusline=""); statusline = receiveServerResponse (stream); if flMessages { displayMsg (statusline)}; if not (statusline beginsWith "+OK") { closeConnection ("Can't get mail because the server, " + host + ", did not send a valid connection greeting. It said, \"" + statusline + "\".")}}; try { // trap any errors from here on and bail out if we get any bundle { // log on to server <<// branch here if apop (not yet implemented) <<if apop // user wants to use apop <<local (temp, timestring) <<timestring = response <<try <<temp = libMD5.digestAsHex (timestring + password) <<else <<tcpcmd.interfaces.closeStream (aSession) <<scriptError ("tcpcmd's APOP support requires the libMD5 DLL") <<else // put the lines for 'normal' logon below under this else executeCommand ("USER " + username, "+OK"); executeCommand ("PASS " + password, "+OK")}; bundle { // get count of messages local (statusline=""); sendCommandToServer (stream, "STAT"); statusline = receiveServerResponse (stream); if not (statusline beginsWith "+OK") { closeConnection ("Can't get mail because the server, " + host + ", did not send a valid STAT response. It said, \"" + statusline + "\".")}; msgCount = number (string.nthField (statusline, ' ', 2))}; bundle { // loop and get each message for i = 1 to msgCount { if flMessages { displayMsg ("Retrieving message " + i)}; local (ourTableName); sendCommandToServer (stream, "RETR " + i); tcp.readStreamUntil (stream, "\r\n.\r\n", timeOutTicks / 60, @buffer); ourTableName = string.padWithZeros (i, 4); new (tableType, @adrMsgTable^.[ourTableName]); parseMsg2 (buffer, @adrMsgTable^.[ourTableName], poundNameCompatibility:false); if deleteMessages { // we can delete the message right here! executeCommand ("DELE " + i, "+OK")} else { //see if the callback tells us to delete the message if adrDeleteCallback != nil { if adrDeleteCallback^ (@adrMsgTable^.[ourTableName]) { executeCommand ("DELE " + i, "+OK")}}}; buffer = ""}}; bundle { // log off and disconnect try {executeCommand ("QUIT")}; <<try {tcp.closeStream (stream)} closeStream ()}} else { closeConnection ("Can't get mail because there was an error: \"" + tryError + "\".")}; if flMessages { //clean up the message area msg ("")}}
This listing is for code that runs in the OPML Editor environment. I created these listings because I wanted the search engines to index it, so that when I want to look up something in my codebase I don't have to use the much slower search functionality in my object database. Dave Winer.