Serence Inc. © 2004-2006 Serence Inc. info@serence.com http://www.serence.com POP3 Email EXc312AzsGWheOqjsDXFZneg78f5lMx1WxAjbsojlSzHF3NKoKnPDCEwwn1e0MtVuezJbccBDCmRvsp7Ck9vDxo35Xt9uAo3Vl77GUCr7QI+2sWcr3zuekFbOYKNsH8IcwEwDr6Wbqud+jpPw9/ORqFgEr2FHUpE2O+BR+BXUtU= 3.04 2006.08.14:1139 This Klip will notify you when new email has been delivered to your POP3 email account. email pop pop3 new message incoming received http://www.serence.com/serence_klips/pics/email/email_klip_icon.png http://www.serence.com/serence_klips/pics/email/pop_klip_banner.jpg http://www.serence.com/serence_klips/email_pop3.klip No new mail. -- extended 65001 true 0) { result += "~|~"; } result += prop+": "+message[prop]; } return result; } function serializedHeaderValue(messageStr, key) { var result = ""; var key_re = new RegExp("(?:^|~\\|~)" + key + ":\\s([^]*?)(?:$|~\\|~)"); var match = messageStr.match(key_re); if (match) { result = match[1]; } return result; } function formatItem(item) { if (KlipFolio.build < 5840) { var messageStr = item.extra; var text = ""; for (var j = 0; j < headerFields.length; j++) { if (text.length > 0) { text += delim; } text += serializedHeaderValue(messageStr, headerFields[j]); } item.text = text; } else { for (var j = 0; j < headerFields.length; j++) { item.setData("col" + (j+1), ""+item.getData(headerFields[j])); } for (var j = headerFields.length; j < 3; j++) { item.setData("col" + (j+1), ""); } } } function formatChange(index) { Prefs.setPref ("format", "" + index); headerFields = column_maps[index].split(','); for (var i = 0; i < Items.length; i++) { formatItem(Items[i]); } } function refreshRateChange (index) { Prefs.refreshrate = refresh_rate_array[index]-0; Prefs.setPref("refreshrate", "" + c_refresh_rate.selected); } function loadPrefs () { refreshRateChange(c_refresh_rate.selected); } function savePrefs () { if (c_user.value && c_user.value.length) { Items.altA = Prefs.title + " (" + c_user.value + ")"; } else { Items.altA = Prefs.title; } if (c_server.value != Prefs.getPref ("server") || c_user.value != Prefs.getPref ("user") ) { Prefs.setPref ("account_changed", "true"); } Prefs.setPref("server", c_server.value); Prefs.setPref("user", c_user.value); Prefs.setPref("pass", garble(c_pass.value)); Prefs.setPref("format", "" + c_format.selected); Prefs.setPref("refreshrate", "" + c_refresh_rate.selected); Prefs.setPref("getbody", "" + c_get_body.checked); Prefs.setPref("dashboard", "" + c_dashboard.checked); } function prefsValid() { return (c_server.value.length && c_user.value.length && c_pass.value.length); } function introMessage() { // create temporary items to educate the user Items.clear (true); var food = ""; food += newElement("title", 'To configure this email Klip, click here and sign in to the account you wish to monitor.'); food += newElement("body", 'This Klip continuously monitors your email account for new messages.'); food += newElement("uidl", 'intro'); food += ""; var saveCreate = Engines.KlipFood.onCreate; var saveUpdate = Engines.KlipFood.onUpdate; Engines.KlipFood.onCreate = null; Engines.KlipFood.onUpdate = null; try { Engines.KlipFood.process(food); } finally { Engines.KlipFood.onCreate = saveCreate; Engines.KlipFood.onUpdate = saveUpdate; } idx = Items.length - 1; Items[idx].canvisit = false; Items[idx].candelete = false; Items[idx].onClick = fn_configure_item_click; Items.B = " "; } function fn_configure_item_click () { Setup.open (0); } function newElement(tag, value) { return "<" + tag + ">" + value + ""; } function check(item) { formatItem(item); item.candelete = (!c_dashboard.checked); item.canvisit = false; return true; } function createItem(newItem) { newItem.touched = true; return check(newItem); } function updateItem(oldItem, properties) { oldItem.touched = true; return check(properties); } function processData30(message) { var food = ""; for (prop in message) { if (message[prop].length) { food += newElement(prop, message[prop]); } } food += newElement("col1", ""); food += newElement("col2", ""); food += newElement("col3", ""); if (message.content_type && (message.content_type.indexOf("multipart/mixed") >= 0)) { food += newElement("icon", "http://www.serence.com/serence_klips/pics/email/email-unread-attach.png"); } else { food += newElement("icon", "http://www.serence.com/serence_klips/pics/email/email-unread.png"); } food += ""; Engines.KlipFood.process(food); } function processData26(message) { var idx = Items.create (message.uidl, "", message.uidl); if (idx >= 0) { var item = Items[idx]; item.pubdate = message.pubdate; item.note = //"Message UIDL: " + message.udil + "\r\n" + "From: " + message.from_long + "\r\n" + "Sent: " + message.date_long + "\r\n" + "To: " + message.to + "\r\n" + (message.cc != null?"CC: " + message.cc + "\r\n":"") + (message.subject_full != null ? "Subject: " + message.subject_full + "\r\n\r\n":"") + (message.body != null?message.body + "\r\n\r\n":"") + "Size: " + message.size ; item.extra = serializeHeader(message); item.candelete = (!c_dashboard.checked); item.canvisit = false; formatItem(item); Items.sort ("pubdate"); } } function processData(message) { if (KlipFolio.build < 5840) { processData26(message); } else { processData30(message); } } ///////////////////////////////////////////////////////////////////////////// // onRefresh() ///////////////////////////////////////////////////////////////////////////// function setStatus (status, alert) { Klip.progressmessage = status; } function onRefresh () { var result = false; var last_refresh_complete = (Prefs.getPref("lrc") == "true"); Prefs.setPref ("lrc", "false"); savePrefs (); if (Prefs.getPref ("account_changed") == "true") { Prefs.setPref ("account_changed", "false"); Items.clear (true); Items.Deleted.clear (); } if (!prefsValid()) { introMessage(); return false; } else { Items.B = ""; } Engines.KlipFood.onCreate = createItem; Engines.KlipFood.onUpdate = updateItem; setStatus("Connecting..."); var conn = Engines.TCP.newConnection(); conn.host = c_server.value; conn.port = 110; if (openConnection(conn)) { setStatus ("Authenticating..."); if (authenticateUser(conn)) { var count = getMessageCount(conn); if (count < 0) { setStatus ("Error: could not count messages"); } else { // Items.purge(true); if (Items.length && Items[0].getData ("uidl") == "intro") { Items.remove (0, true); } var i; for (i = 0; i < Items.length; ++i) { Items[i].touched = false; } if (count == 0) { result = true; } else { if (c_dashboard.checked) { var new_messages = enumerateMessages(conn); if (new_messages != null) { for (i = new_messages.length-1; i >= 0; i--) { setStatus ("Downloading message " + (count - i) + " of " + count); if (downloadMessage(conn, i+1, new_messages[i])) { processData(new_messages[i]); } } result = true; for (i = 0; i < Items.length; ++i) { if (!Items[i].touched) { Items.remove (i); --i; } } } } else { // grab the last UIDL in the messagedrop var last_uidl = getMessageUIDL(conn, count); if (last_uidl != null) { // check to see if we've got this message already if (last_refresh_complete && (findUIDL(last_uidl) >= 0)) { // we must have all the messages currently in the messagedrop so abort this fetch result = true; } else { var new_messages = enumerateMessages(conn); if (new_messages != null) { for (var i = new_messages.length - 1; i >= 0; i--) { if (findUIDL(new_messages[i].uidl) < 0) { setStatus ("Downloading message " + (count - i) + " of " + count); if (downloadMessage(conn, i+1, new_messages[i])) { processData(new_messages[i]); } } } result = true; } } } } } savePrefs (); Prefs.setPref ("lrc", result); } closeConnection(conn); } else { if (c_user.value.length || c_pass.length) { setStatus ("Error: could not authenticate"); } } conn.close(); } return result; } ///////////////////////////////////////////////////////////////////////////// // POP3 protocol implementation ///////////////////////////////////////////////////////////////////////////// var message_stat_re = new RegExp ("+OK (\\d+) (\\d+)"); var message_multi_list_re = new RegExp ("^(\\d+) (\\d+)", "gm"); var message_uidl_re = new RegExp ("+OK (\\d+) (.*)"); var message_multi_uidl_re = new RegExp ("^(\\d+) (.*?)$", "gm"); var email_address_re = new RegExp ("<(.*?)>"); var header_re_text = "^{header}:\\s*([^]*?)\\r\\n[^\\s]"; function openConnection (conn) { var data; // open the TCP connection if (conn.open()) { // validate initial server ready response data = fetchData(conn, false); if (data != null && data.indexOf ("+OK") == 0) { // we've got a valid one-line hello return true; } } return false; } function authenticateUser(conn) { var data; // send the username var command = "USER " + c_user.value + "\r\n"; if (conn.send(command, command.length)) { // validate the response data = fetchData(conn, false); if (data != null && data.indexOf ("+OK") == 0) { // send the password command = "PASS " + c_pass.value + "\r\n"; if (conn.send(command, command.length)) { // validate the response data = fetchData (conn, false); if (data != null && data.indexOf ("+OK") == 0) { return true; } } } } if (data != null) { trace("Email authentication failed: " + data + "\r\n"); } else { trace("Email authentication failed!\r\n"); } return false; } function getMessageCount(conn) { var data; // request the maildrop status var command = "STAT\r\n"; if (conn.send(command, command.length)) { // validate the response data = fetchData (conn, false); if (data != null && data.indexOf ("+OK") == 0) { // parse out the message count var m; if (m = message_stat_re.exec(data)) { // return the message count as a number return m[1] - 0; } } } return -1; } function getMessageUIDL(conn, message_num) { var data; // request the UIDL of a given message var command = "UIDL " + message_num + "\r\n"; if (conn.send(command, command.length)) { // validate the response data = fetchData(conn, false); if (data != null && data.indexOf ("+OK") == 0) { // parse out the message count var m; if (m = message_uidl_re.exec(data)) { // return the message uidl return trim(m[2]); } } } return null; } function enumerateMessages(conn) { // request the list of message unique ids var command = "UIDL\r\n"; if (conn.send(command, command.length)) { // store the response var uidl = fetchData(conn, true); if (uidl != null) { // request the list of message sizes command = "LIST\r\n"; if (conn.send(command, command.length)) { // store the response var list = fetchData(conn, true); if (list != null) { var messages = new Array(); var m; while (m = message_multi_uidl_re.exec(uidl)) { var message = new Message(m[2]); message.itemIdx = findUIDL(m[2]); if ((message.itemIdx >= 0) && (message.itemIdx < Items.length)) { if (!Items[message.itemIdx]) message.body = headerValue(Items[message.itemIdx], "body"); } messages[messages.length] = message; } var i = 0; while (m = message_multi_list_re.exec(list)) { if (i >= messages.length) { break; } messages[i].size = translateSize(m[2]); i++; } return messages; } } } } return null; } function downloadMessage(conn, idx, message) { if (message != null) { var command = "TOP " + idx + " " + (c_get_body.checked?100:0) + "\r\n"; if (conn.send (command, command.length)) { // validate the response var data = fetchData (conn, true); if (data != null && data.indexOf ("+OK") == 0) { var bodidx = data.indexOf ("\r\n\r\n"); var header = data.substring (0, bodidx) + "\r\n."; // parse out the headers var h_from = decodeHeader("from", header); var h_to = decodeHeader("to", header); var h_cc = decodeHeader("cc", header); var h_subject = decodeHeader("subject", header); var h_date = decodeHeader("date", header, new Date().toString()); var h_content_type = decodeHeader("content-type", header, "text/plain; charset=us-ascii"); // prepare date var date, pubdate; if (h_date.length > 0) { pubdate = Engines.MIME.decodeDate(h_date); } else { pubdate = new Date().getTime() / 1000; pubdate = Math.round(pubdate); } date = new Date(pubdate * 1000); parseFromHeader(h_from, message); message.subject_full = h_subject; message.subject_short = truncateString(h_subject, 60); message.date_long = date.toString(); message.date_short = months[date.getMonth()] + " " + date.getDate(); message.to = unbracketEmail(h_to); message.cc = unbracketEmail(h_cc); message.pubdate = pubdate; message.content_type = h_content_type; if (c_get_body.checked && (message.body.length == 0)) { body = extractBody(data); if (body.length) { body = body.substring(0, 200) + (body.length > 200 ? "..." : ""); } else { body = "[" + h_content_type.substring(0, h_content_type.indexOf(";")) + "]"; } message.body = body; } return true; } } } return false; } function extractBody(data) { var body = ""; if (data && data.length) { var bodidx = data.indexOf ("\r\n\r\n"); var endidx = data.indexOf ("\r\n."); var header = data.substring (0, bodidx) + "\r\n."; var body = data.substring (bodidx + 4, (endidx >= 0 ? endidx : data.length)); var h_content_type = decodeHeader("content-type", header); if (h_content_type.indexOf("text/") >= 0) { var h_transfer_encoding = decodeHeader("content-transfer-encoding", header); if (h_transfer_encoding.toLowerCase().indexOf("base64") >= 0) { body = base64decode(body.replace (/\r\n/g, "")); } if (h_transfer_encoding.toLowerCase().indexOf("quoted-printable") >= 0) { body = Engines.MIME.unquoteText(body); } var h_charset = getAttribute(h_content_type, "charset"); if (h_charset.length) { body = Engines.MIME.convertText(body, Engines.MIME.charsetToCodepage(h_charset), 65001); } body = stripConsecutiveNewlines(Klip.stripTags(Klip.processEntities(body.replace(//gi, "")))); } else if (h_content_type.indexOf("multipart/") >= 0) { var boundary = getAttribute(h_content_type, "boundary"); if (boundary.length) { body = body.substring(body.indexOf("--" + boundary)); var parts = body.split("--"+boundary); for (var i = 0; i < parts.length; i++) { var body = extractBody(parts[i]); if (body.length) { break; } } } } } return body; } function closeConnection (conn) { // gracefully close the connection var command = "QUIT\r\n"; if (conn.send(command, command.length)) { // validate the response var data = fetchData (conn, false); if (data != null && data.indexOf ("+OK") == 0) { return true; } } return false; } function fetchData(conn, multiline) { var fetchbuffer = ""; var fetch; // work until the connection is closed while ((fetch = conn.receive(1024)) != null) { if (fetch.length) { // accumulate data fetchbuffer += fetch; // check for a terminating linefeed if (fetchbuffer[fetchbuffer.length - 1] == '\n') { // if we're multiline, look for a single '.' character on the last line if (multiline) { if (fetchbuffer.lastIndexOf("\r\n.\r\n") == fetchbuffer.length - 5) { return fetchbuffer; } } else { return fetchbuffer; } } } else { delay (100); } } return null; } function buildNameList (text) { return text.replace (/\s+/g, ' ').replace (/(^\s+|\s+$|")/g, '').split (','); } function unbracketEmail(data) { var email_re = new RegExp('(?:^|,)\\s*?"?([^,]*?)"?\\s*,]*)>?', "g"); var email; var result = ""; while (email = email_re.exec(data)) { if (result.length > 0) { result += ", "; } if (email[1].length > 0) { result += email[1] + " (" + email[2] +")"; } else { result += email[2]; } } return result; } function decodeHeader(header, data, default_value) { var result = ""; var header_re = new RegExp(header_re_text.replace(/{header}/, header), "mi"); var match = header_re.exec(data); if (match) { result = Engines.MIME.decodeHeader(match[1]); } else { if (default_value) { result = default_value; } } return normalize(result); } function parseFromHeader(h_from, message) { var from_name = ""; var from_email = ""; var from_long = ""; var m; var ltidx; if (h_from.length > 0) { if (h_from.indexOf (',') >= 0) { var namelist = buildNameList(h_from); for (var i = 0; i < namelist.length; i++) { if ((ltidx = namelist[i].indexOf ('<')) >= 0) { if (m = email_address_re.exec (namelist[i])) { from_email += (from_email.length ? ", " : "") + m[1]; } from_name += (from_name.length ? ", " : "") + (ltidx == 0 && m != null ? m[1] : namelist[i].substring (0, ltidx - 1)); } } } else { from_name = h_from; if ((ltidx = h_from.indexOf ('<')) >= 0) { if (m = email_address_re.exec (h_from)) { from_email = m[1]; } from_name = (ltidx == 0 ? from_email : h_from.substring (0, ltidx - 1)); } } from_long = unbracketEmail(h_from); } message.from_name = from_name.replace(/"/g, ""); message.from_email = from_email; message.from_long = from_long; } function getAttribute(headStr, attribute) { var attribute_re = new RegExp(attribute+'=(?:"([^"]*)"|([^\\s";]*))'); var match = attribute_re.exec(headStr); if (match) { if (match[1].length && match[1]) { return match[1]; } else if (match[2]) { return match[2]; } } return ""; } ///////////////////////////////////////////////////////////////////////////// // Utility methods ///////////////////////////////////////////////////////////////////////////// function headerValue(item, header) { if (KlipFolio.build < 5840) { return (trim(serializedHeaderValue(item.extra, header))); } else { return (trim(item.getData(header))); } } function findUIDL(uidl) { uidl = trim(uidl); for (var i = 0; i < Items.length; i++) { if (headerValue(Items[i], "uidl") == uidl) { return i; } } for (var i = 0; i < Items.Deleted.length; i++) { if (headerValue(Items.Deleted[i], "uidl") == uidl) { return Items.length + i; } } return -1; } function truncateString(str, len) { var result = ""; if (str && str.length) { result = str.substring(0, len); if (str.length > len) { result += "[...]"; } } return result; } function normalize(data) { return data.replace(/\s+/g, " ").replace(/(^\s+|\s+$)/g, ""); } function trim(s) { return s.replace(/^\s+|\s+$/g, ""); } function translateSize (size) { var b = size - 0; var kb = b / 1024; var mb = kb / 1024; if (mb > 1) { return mb.toFixed (2) + " Mb"; } else if (kb > 1) { return kb.toFixed (1) + " kb"; } return b + " bytes"; } function stripConsecutiveNewlines (text) { var result = text; result.replace(/\r\n/g, " "); result = normalize(result); return result; } ]]>