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.