PK rH2o׋ +content/messenger-newsblog/toolbar-icon.xul PK 4B!'content/messenger-newsblog/edittree.xml 0 -1 null null = 0) this._assignValueToCell(txt.value,true); if (cellnode && cellnode.getAttribute("readonly")) return; txt.removeAttribute("hidden"); var treeBox = this.treeBoxObject; var outx = {}, outy = {}, outwidth = {}, outheight = {}; var coords = treeBox.getCoordsForCellItem(x,y,"cell",outx,outy,outwidth,outheight); this._editRow = x; this._editCol = y; txt.setAttribute("left",outx.value-3); txt.setAttribute("top",outy.value-3); txt.setAttribute("height",outheight.value); txt.setAttribute("width",outwidth.value - outy.value); this._editOriginalValue = originalValue; if (cellnode) cellnode.setAttribute("label",""); this.view.setCellText(x,y,""); txt.value = originalValue; txt.select(); this.setAttribute("editing","true"); txt.addEventListener("keypress", this.fieldKeyDown, false); txt.addEventListener("blur", this.fieldChange, true); } else { this.removeAttribute("editing"); txt.setAttribute("hidden","true"); txt.removeEventListener("keypress", this.fieldKeyDown, false); txt.removeEventListener("blur", this.fieldChange, true); txt.blur(); } ]]> var view; try { view = this.contentView; } catch (ex){} if (view){ var elem = view.getItemAtIndex(row); if (elem){ var pos = ((document.getElementById(col).ordinal - 1) >> 1); return elem.firstChild.childNodes[pos]; } } return null; PK P06HG)content/messenger-newsblog/debug-utils.jsfunction enumerateInterfaces(obj) { var interfaces = new Array(); for (i in Components.interfaces) { try { obj.QueryInterface(Components.interfaces[i]); interfaces.push(i); } catch(e) {} } return interfaces; } function enumerateProperties(obj, excludeComplexTypes) { var properties = ""; for (p in obj) { try { if (excludeComplexTypes && (typeof obj[p] == 'object' || typeof obj[p] == 'function')) next; properties += p + " = " + obj[p] + "\n"; } catch(e) { properties += p + " = " + e + "\n"; } } return properties; } // minimal implementation of nsIOutputStream for use by dumpRDF, adapted from // http://groups.google.com/groups?selm=20011203111618.C1302%40erde.jan.netgaroo.de var DumpOutputStream = { write: function(buf, count) { dump(buf); return count; } }; function dumpRDF( aDS ) { var serializer = Components.classes["@mozilla.org/rdf/xml-serializer;1"] .createInstance( Components.interfaces.nsIRDFXMLSerializer ); serializer.init( aDS ); serializer.QueryInterface( Components.interfaces.nsIRDFXMLSource ) .Serialize( DumpOutputStream ); } PK }rh6 xLP88"content/messenger-newsblog/Feed.js//@line 37 "/builds/tinderbox/Tb-Mozilla1.8-Release/Linux_2.4.18-14_Depend/mozilla/mail/extensions/newsblog/content/Feed.js" // error codes used to inform the consumer about attempts to download a feed const kNewsBlogSuccess = 0; const kNewsBlogInvalidFeed = 1; // usually means there was an error trying to parse the feed... const kNewsBlogRequestFailure = 2; // generic networking failure when trying to download the feed. const kNewsBlogFeedIsBusy = 3; const kNewsBlogNoNewItems = 4; // there are no new articles for this feed // Cache for all of the feeds currently being downloaded, indexed by URL, so the load event listener // can access the Feed objects after it finishes downloading the feed. var FeedCache = { mFeeds: new Array(), putFeed: function (aFeed) { this.mFeeds[this.normalizeHost(aFeed.url)] = aFeed; }, getFeed: function (aUrl) { return this.mFeeds[this.normalizeHost(aUrl)]; }, removeFeed: function (aUrl) { delete this.mFeeds[this.normalizeHost(aUrl)]; }, normalizeHost: function (aUrl) { var ioService = Components.classes["@mozilla.org/network/io-service;1"]. getService(Components.interfaces.nsIIOService); var normalizedUrl = ioService.newURI(aUrl, null, null); normalizedUrl.host = normalizedUrl.host.toLowerCase(); return normalizedUrl.spec; } }; function Feed(aResource, aRSSServer) { this.resource = aResource.QueryInterface(Components.interfaces.nsIRDFResource); this.server = aRSSServer; } Feed.prototype = { description: null, author: null, request: null, server: null, downloadCallback: null, resource: null, items: new Array(), mFolder: null, get folder() { if (!this.mFolder) { try { this.mFolder = this.server.rootMsgFolder.getChildNamed(this.name); } catch (ex) {} } return this.mFolder; }, set folder (aFolder) { this.mFolder = aFolder; }, get name() { var name = this.title || this.description || this.url; if (!name) throw("couldn't compute feed name, as feed has no title, description, or URL."); // Make sure the feed name doesn't have any line breaks, since we're going // to use it as the name of the folder in the filesystem. This may not // be necessary, since Mozilla's mail code seems to handle other forbidden // characters in filenames and can probably handle these as well. name = name.replace(/[\n\r\t]+/g, " "); // Make sure the feed doesn't end in a period to work around bug 117840. name = name.replace(/\.+$/, ""); return name; }, download: function(aParseItems, aCallback) { this.downloadCallback = aCallback; // may be null // Whether or not to parse items when downloading and parsing the feed. // Defaults to true, but setting to false is useful for obtaining // just the title of the feed when the user subscribes to it. this.parseItems = aParseItems == null ? true : aParseItems ? true : false; // Before we do anything...make sure the url is an http url. This is just a sanity check // so we don't try opening mailto urls, imap urls, etc. that the user may have tried to subscribe to // as an rss feed.. var uri = Components.classes["@mozilla.org/network/standard-url;1"]. createInstance(Components.interfaces.nsIURI); uri.spec = this.url; if (!(uri.schemeIs("http") || uri.schemeIs("https"))) return this.onParseError(this); // simulate an invalid feed error // Before we try to download the feed, make sure we aren't already processing the feed // by looking up the url in our feed cache if (FeedCache.getFeed(this.url)) { if (this.downloadCallback) this.downloadCallback.downloaded(this, kNewsBlogFeedIsBusy); return ; // don't do anything, the feed is already in use } this.request = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] .createInstance(Components.interfaces.nsIXMLHttpRequest); this.request.onprogress = this.onProgress; // must be set before calling .open this.request.open("GET", this.url, true); var lastModified = this.lastModified; if (lastModified) this.request.setRequestHeader("If-Modified-Since", lastModified); this.request.overrideMimeType("text/xml"); this.request.onload = this.onDownloaded; this.request.onerror = this.onDownloadError; FeedCache.putFeed(this); this.request.send(null); }, onDownloaded: function(aEvent) { var request = aEvent.target; var url = request.channel.originalURI.spec; debug(url + " downloaded"); var feed = FeedCache.getFeed(url); if (!feed) throw("error after downloading " + url + ": couldn't retrieve feed from request"); // if the request has a Last-Modified header on it, then go ahead and remember // that as a property on the feed so we can use it when making future requests. var lastModifiedHeader = request.getResponseHeader('Last-Modified'); if (lastModifiedHeader) this.lastModified = lastModifiedHeader; feed.parse(); // parse will asynchronously call the download callback when it is done }, onProgress: function(aEvent) { var request = aEvent.target; var url = request.channel.originalURI.spec; var feed = FeedCache.getFeed(url); if (feed.downloadCallback) feed.downloadCallback.onProgress(feed, aEvent.position, aEvent.totalSize); }, onDownloadError: function(aEvent) { var request = aEvent.target; var url = request.channel.originalURI.spec; var feed = FeedCache.getFeed(url); if (feed.downloadCallback) { // if the http status code is a 304, then the feed has not been modified since we last downloaded it. var error = kNewsBlogRequestFailure; try { if (request.status == 304) error = kNewsBlogNoNewItems; } catch (ex) {} feed.downloadCallback.downloaded(feed, error); } FeedCache.removeFeed(url); }, onParseError: function(aFeed) { if (aFeed && aFeed.downloadCallback) { if (aFeed.downloadCallback) aFeed.downloadCallback.downloaded(aFeed, aFeed.request && aFeed.request.status == 304 ? kNewsBlogNoNewItems : kNewsBlogInvalidFeed); FeedCache.removeFeed(aFeed.url); } }, get url() { var ds = getSubscriptionsDS(this.server); var url = ds.GetTarget(this.resource, DC_IDENTIFIER, true); if (url) url = url.QueryInterface(Components.interfaces.nsIRDFLiteral).Value; else url = this.resource.Value; return url; }, get title() { var ds = getSubscriptionsDS(this.server); var title = ds.GetTarget(this.resource, DC_TITLE, true); if (title) title = title.QueryInterface(Components.interfaces.nsIRDFLiteral).Value; return title; }, set title (aNewTitle) { if (!aNewTitle) return; var ds = getSubscriptionsDS(this.server); aNewTitle = rdf.GetLiteral(aNewTitle); var old_title = ds.GetTarget(this.resource, DC_TITLE, true); if (old_title) ds.Change(this.resource, DC_TITLE, old_title, aNewTitle); else ds.Assert(this.resource, DC_TITLE, aNewTitle, true); }, get lastModified() { var ds = getSubscriptionsDS(this.server); var lastModified = ds.GetTarget(this.resource, DC_LASTMODIFIED, true); if (lastModified) lastModified = lastModified.QueryInterface(Components.interfaces.nsIRDFLiteral).Value; return lastModified; }, set lastModified(aLastModified) { var ds = getSubscriptionsDS(this.server); aLastModified = rdf.GetLiteral(aLastModified); var old_lastmodified = ds.GetTarget(this.resource, DC_LASTMODIFIED, true); if (old_lastmodified) ds.Change(this.resource, DC_LASTMODIFIED, old_lastmodified, aLastModified); else ds.Assert(this.resource, DC_LASTMODIFIED, aLastModified, true); // do we need to flush every time this property changes? ds = ds.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource); ds.Flush(); }, get quickMode () { var ds = getSubscriptionsDS(this.server); var quickMode = ds.GetTarget(this.resource, FZ_QUICKMODE, true); if (quickMode) { quickMode = quickMode.QueryInterface(Components.interfaces.nsIRDFLiteral); quickMode = quickMode.Value; quickMode = eval(quickMode); } return quickMode; }, set quickMode (aNewQuickMode) { var ds = getSubscriptionsDS(this.server); aNewQuickMode = rdf.GetLiteral(aNewQuickMode); var old_quickMode = ds.GetTarget(this.resource, FZ_QUICKMODE, true); if (old_quickMode) ds.Change(this.resource, FZ_QUICKMODE, old_quickMode, aNewQuickMode); else ds.Assert(this.resource, FZ_QUICKMODE, aNewQuickMode, true); }, get link () { var ds = getSubscriptionsDS(this.server); var link = ds.GetTarget(this.resource, RSS_LINK, true); if(link) link = link.QueryInterface(Components.interfaces.nsIRDFLiteral).Value; return link; }, set link (aNewLink) { if (!aNewLink) return; var ds = getSubscriptionsDS(this.server); aNewLink = rdf.GetLiteral(aNewLink); var old_link = ds.GetTarget(this.resource, RSS_LINK, true); if (old_link) ds.Change(this.resource, RSS_LINK, old_link, aNewLink); else ds.Assert(this.resource, RSS_LINK, aNewLink, true); }, parse: function() { // Figures out what description language (RSS, Atom) and version this feed // is using and calls a language/version-specific feed parser. debug("parsing feed " + this.url); if (!this.request.responseText) return this.onParseError(this); // create a feed parser which will parse the feed for us var parser = new FeedParser(); this.itemsToStore = parser.parseFeed(this, this.request.responseText, this.request.responseXML, this.request.channel.URI); // storeNextItem will iterate through the parsed items, storing each one. this.itemsToStoreIndex = 0; this.storeNextItem(); }, invalidateItems: function () { var ds = getItemsDS(this.server); debug("invalidating items for " + this.url); var items = ds.GetSources(FZ_FEED, this.resource, true); var item; while (items.hasMoreElements()) { item = items.getNext(); item = item.QueryInterface(Components.interfaces.nsIRDFResource); debug("invalidating " + item.Value); var valid = ds.GetTarget(item, FZ_VALID, true); if (valid) ds.Unassert(item, FZ_VALID, valid, true); } }, removeInvalidItems: function() { var ds = getItemsDS(this.server); debug("removing invalid items for " + this.url); var items = ds.GetSources(FZ_FEED, this.resource, true); var item; while (items.hasMoreElements()) { item = items.getNext(); item = item.QueryInterface(Components.interfaces.nsIRDFResource); if (ds.HasAssertion(item, FZ_VALID, RDF_LITERAL_TRUE, true)) continue; debug("removing " + item.Value); ds.Unassert(item, FZ_FEED, this.resource, true); if (ds.hasArcOut(item, FZ_FEED)) debug(item.Value + " is from more than one feed; only the reference to this feed removed"); else removeAssertions(ds, item); } }, createFolder: function() { if (!this.folder) this.server.rootMsgFolder.createSubfolder(this.name, null /* supposed to be a msg window */); }, // gets the next item from gItemsToStore and forces that item to be stored // to the folder. If more items are left to be stored, fires a timer for the next one. // otherwise it triggers a download done notification to the UI storeNextItem: function() { if (!this.itemsToStore || !this.itemsToStore.length) { this.createFolder(); return this.cleanupParsingState(this); } var item = this.itemsToStore[this.itemsToStoreIndex]; item.store(); item.markValid(); // if the listener is tracking progress for storing each item, report it here... if (item.feed.downloadCallback && item.feed.downloadCallback.onFeedItemStored) item.feed.downloadCallback.onFeedItemStored(item.feed, this.itemsToStoreIndex, this.itemsToStore.length); this.itemsToStoreIndex++ // eventually we'll report individual progress here.... if (this.itemsToStoreIndex < this.itemsToStore.length) { if (!this.storeItemsTimer) this.storeItemsTimer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer); this.storeItemsTimer.initWithCallback(this, 50, Components.interfaces.nsITimer.TYPE_ONE_SHOT); } else { // we have just finished downloading one or more feed items into the destination folder, // if the folder is still listed as having new messages in it, then we should set the biff state on the folder // so the right RDF UI changes happen in the folder pane to indicate new mail. if (item.feed.folder.hasNewMessages) item.feed.folder.biffState = Components.interfaces.nsIMsgFolder.nsMsgBiffState_NewMail; this.cleanupParsingState(item.feed); } }, cleanupParsingState: function(aFeed) { // now that we are done parsing the feed, remove the feed from our feed cache FeedCache.removeFeed(aFeed.url); aFeed.removeInvalidItems(); // let's be sure to flush any feed item changes back to disk var ds = getItemsDS(aFeed.server); ds.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource).Flush(); // flush any changes if (aFeed.downloadCallback) aFeed.downloadCallback.downloaded(aFeed, kNewsBlogSuccess); this.request = null; // force the xml http request to go away. This helps reduce some nasty assertions on shut down. this.itemsToStore = ""; this.itemsToStoreIndex = 0; this.storeItemsTimer = null; }, notify: function(aTimer) { this.storeNextItem(); }, QueryInterface: function(aIID) { if (aIID.equals(Components.interfaces.nsITimerCallback) || aIID.equals(Components.interfaces.nsISupports)) return this; Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE; return null; } }; PK k5o#<<&content/messenger-newsblog/FeedItem.js//@line 37 "/builds/tinderbox/Tb-Mozilla1.8-Release/Linux_2.4.18-14_Depend/mozilla/mail/extensions/newsblog/content/FeedItem.js" // Handy conversion values. const HOURS_TO_MINUTES = 60; const MINUTES_TO_SECONDS = 60; const SECONDS_TO_MILLISECONDS = 1000; const MINUTES_TO_MILLISECONDS = MINUTES_TO_SECONDS * SECONDS_TO_MILLISECONDS; const HOURS_TO_MILLISECONDS = HOURS_TO_MINUTES * MINUTES_TO_MILLISECONDS; const MSG_FLAG_NEW = 0x10000; const ENCLOSURE_BOUNDARY_PREFIX = "--------------"; // 14 dashes const ENCLOSURE_HEADER_BOUNDARY_PREFIX = "------------"; // 12 dashes const MESSAGE_TEMPLATE = "\n\ \n\ \n\ %TITLE%\n\ \n\ \n\ \n\ \n\ %CONTENT_TEMPLATE%\n\ \n\ \n\ "; const REMOTE_CONTENT_TEMPLATE = "\n\ \n\ "; const REMOTE_STYLE = "\n\ body {\n\ margin: 0;\n\ border: none;\n\ padding: 0;\n\ }\n\ iframe {\n\ position: fixed;\n\ top: 0;\n\ left: 0;\n\ width: 100%;\n\ height: 100%;\n\ border: none;\n\ }\n\ "; // Unlike remote content, which is locked within a fixed position iframe, // local content goes is positioned according to the normal rules of flow. // The problem with this is that the message pane itself provides a scrollbar // if necessary, and that scrollbar appears next to the toolbar as well as // the content being scrolled. The solution is to lock local content within // a fixed position div and set its overflow property to auto so that the div // itself provides the scrollbar. Unfortunately we can't do that because of // Mozilla bug 97283, which makes it hard to scroll an auto overflow div. const LOCAL_CONTENT_TEMPLATE = "\n\ %CONTENT%\n\ "; // no local style overrides at this time const LOCAL_STYLE = "\n"; function FeedItem() { this.mDate = new Date().toString(); this.mUnicodeConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"] .createInstance(Components.interfaces.nsIScriptableUnicodeConverter); } FeedItem.prototype = { isStoredWithId: false, // we currently only do this for IETF Atom. RSS2 with GUIDs should do this as well. xmlContentBase: null, // only for IETF Atom id: null, feed: null, description: null, content: null, enclosure: null, // we currently only support one enclosure per feed item... title: "(no subject)", // TO DO: this needs to be localized author: "anonymous", mURL: null, characterSet: "", get url() { return this.mURL; }, set url(aVal) { var uri = Components.classes["@mozilla.org/network/standard-url;1"].getService(Components.interfaces["nsIStandardURL"]); uri.init(1, 80, aVal, null, null); var uri = uri.QueryInterface(Components.interfaces.nsIURI); this.mURL = uri.spec; }, get date() { return this.mDate; }, set date (aVal) { this.mDate = aVal; }, get identity () { return this.feed.name + ": " + this.title + " (" + this.id + ")" }, get messageID() { var messageID = this.id || this.mURL || this.title; // Escape occurrences of message ID meta characters <, >, and @. messageID.replace(//g, "%3E"); messageID.replace(/@/g, "%40"); messageID = messageID + "@" + "localhost.localdomain"; return messageID; }, get itemUniqueURI() { var theURI; if(this.isStoredWithId && this.id) theURI = "urn:" + this.id; else theURI = this.mURL || ("urn:" + this.id); return theURI; }, get contentBase() { if(this.xmlContentBase) return this.xmlContentBase else return this.mURL; }, store: function() { this.mUnicodeConverter.charset = this.characterSet; if (this.isStored()) debug(this.identity + " already stored; ignoring"); else if (this.content) { debug(this.identity + " has content; storing"); var content = MESSAGE_TEMPLATE; content = content.replace(/%CONTENT_TEMPLATE%/, LOCAL_CONTENT_TEMPLATE); content = content.replace(/%STYLE%/, LOCAL_STYLE); content = content.replace(/%TITLE%/, this.title); content = content.replace(/%BASE%/, this.contentBase); content = content.replace(/%URL%/g, this.mURL); content = content.replace(/%CONTENT%/, this.content); this.content = content; // XXX store it elsewhere, f.e. this.page this.writeToFolder(); } else if (this.feed.quickMode) { debug(this.identity + " in quick mode; storing"); this.content = this.description || this.title; var content = MESSAGE_TEMPLATE; content = content.replace(/%CONTENT_TEMPLATE%/, LOCAL_CONTENT_TEMPLATE); content = content.replace(/%STYLE%/, LOCAL_STYLE); content = content.replace(/%BASE%/, this.contentBase); content = content.replace(/%TITLE%/, this.title); content = content.replace(/%URL%/g, this.mURL); content = content.replace(/%CONTENT%/, this.content); this.content = content; // XXX store it elsewhere, f.e. this.page this.writeToFolder(); } else { //debug(this.identity + " needs content; downloading"); debug(this.identity + " needs content; creating and storing"); var content = MESSAGE_TEMPLATE; content = content.replace(/%CONTENT_TEMPLATE%/, REMOTE_CONTENT_TEMPLATE); content = content.replace(/%STYLE%/, REMOTE_STYLE); content = content.replace(/%TITLE%/, this.title); content = content.replace(/%BASE%/, this.contentBase); content = content.replace(/%URL%/g, this.mURL); content = content.replace(/%DESCRIPTION%/, this.description || this.title); this.content = content; // XXX store it elsewhere, f.e. this.page this.writeToFolder(); } }, isStored: function() { // Checks to see if the item has already been stored in its feed's message folder. debug(this.identity + " checking to see if stored"); var server = this.feed.server; var folder = this.feed.folder; if (!folder) { debug(this.feed.name + " folder doesn't exist; creating"); debug("creating " + this.feed.name + "as child of " + server.rootMsgFolder + "\n"); server.rootMsgFolder.createSubfolder(this.feed.name, null /* supposed to be a msg window */); folder = server.rootMsgFolder.FindSubFolder(this.feed.name); debug(this.identity + " not stored (folder didn't exist)"); return false; } var ds = getItemsDS(server); var itemURI = this.itemUniqueURI; var itemResource = rdf.GetResource(itemURI); var downloaded = ds.GetTarget(itemResource, FZ_STORED, true); // Backward compatibility: we might have stored this item before isStoredWithId // has been turned on for RSS 2.0 (bug 354345). Check whether this item has been // stored with its URL. if (!downloaded && itemURI != this.mURL) { itemResource = rdf.GetResource(this.mURL); downloaded = ds.GetTarget(itemResource, FZ_STORED, true); } if (!downloaded || downloaded.QueryInterface(Components.interfaces.nsIRDFLiteral).Value == "false") { // HACK ALERT: before we give up, try to work around an entity escaping bug in RDF // See Bug #258465 for more details itemURI = itemURI.replace(/</g, '<'); itemURI = itemURI.replace(/>/g, '>'); itemURI = itemURI.replace(/"/g, '"'); itemURI = itemURI.replace(/&/g, '&'); debug('Failed to find item, trying entity replacement version: ' + itemURI); itemResource = rdf.GetResource(itemURI); downloaded = ds.GetTarget(itemResource, FZ_STORED, true); if (downloaded) { debug(this.identity + " not stored"); return true; } debug(this.identity + " not stored"); return false; } else { debug(this.identity + " stored"); return true; } }, markValid: function() { debug("validating " + this.mURL); var ds = getItemsDS(this.feed.server); var itemURI = this.itemUniqueURI; var resource = rdf.GetResource(itemURI); // Backward compatibility: we might have stored this item before isStoredWithId // has been turned on for RSS 2.0 (bug 354345). Check whether this item has been // stored with its URL. if (!ds.GetTarget(resource, FZ_STORED, true) && itemURI != this.mURL) resource = rdf.GetResource(this.mURL); if (!ds.HasAssertion(resource, FZ_FEED, rdf.GetResource(this.feed.url), true)) ds.Assert(resource, FZ_FEED, rdf.GetResource(this.feed.url), true); if (ds.hasArcOut(resource, FZ_VALID)) { var currentValue = ds.GetTarget(resource, FZ_VALID, true); ds.Change(resource, FZ_VALID, currentValue, RDF_LITERAL_TRUE); } else ds.Assert(resource, FZ_VALID, RDF_LITERAL_TRUE, true); }, markStored: function() { var ds = getItemsDS(this.feed.server); var itemURI = this.itemUniqueURI; var resource = rdf.GetResource(itemURI); if (!ds.HasAssertion(resource, FZ_FEED, rdf.GetResource(this.feed.url), true)) ds.Assert(resource, FZ_FEED, rdf.GetResource(this.feed.url), true); var currentValue; if (ds.hasArcOut(resource, FZ_STORED)) { currentValue = ds.GetTarget(resource, FZ_STORED, true); ds.Change(resource, FZ_STORED, currentValue, RDF_LITERAL_TRUE); } else ds.Assert(resource, FZ_STORED, RDF_LITERAL_TRUE, true); }, mimeEncodeSubject: function(aSubject, aCharset) { // get the mime header encoder service var mimeEncoder = Components.classes["@mozilla.org/messenger/mimeconverter;1"].getService(Components.interfaces.nsIMimeConverter); // this routine sometimes throws exceptions for mis-encoded data so wrap it // with a try catch for now.. var newSubject; try { newSubject = mimeEncoder.encodeMimePartIIStr(this.mUnicodeConverter.ConvertFromUnicode(aSubject), false, aCharset, 9, 72); } catch (ex) { newSubject = aSubject; } return newSubject; }, writeToFolder: function() { debug(this.identity + " writing to message folder" + this.feed.name + "\n"); var server = this.feed.server; this.mUnicodeConverter.charset = this.characterSet; // If the sender isn't a valid email address, quote it so it looks nicer. if (this.author && this.author.indexOf('@') == -1) this.author = '<' + this.author + '>'; // Convert the title to UTF-16 before performing our HTML entity replacement // reg expressions. var title = this.title; // the subject may contain HTML entities. // Convert these to their unencoded state. i.e. & becomes '&' title = title.replace(/</g, '<'); title = title.replace(/>/g, '>'); title = title.replace(/"/g, '"'); title = title.replace(/&/g, '&'); // Compress white space in the subject to make it look better. title = title.replace(/[\t\r\n]+/g, " "); this.title = this.mimeEncodeSubject(title, this.characterSet); // If the date looks like it's in W3C-DTF format, convert it into // an IETF standard date. Otherwise assume it's in IETF format. if (this.mDate.search(/^\d\d\d\d/) != -1) this.mDate = W3CToIETFDate(this.mDate); // Escape occurrences of "From " at the beginning of lines of content // per the mbox standard, since "From " denotes a new message, and add // a line break so we know the last line has one. this.content = this.content.replace(/([\r\n]+)(>*From )/g, "$1>$2"); this.content += "\n"; // The opening line of the message, mandated by standards to start with // "From ". It's useful to construct this separately because we not only // need to write it into the message, we also need to use it to calculate // the offset of the X-Mozilla-Status lines from the front of the message // for the statusOffset property of the DB header object. var openingLine = 'From - ' + this.mDate + '\n'; var source = openingLine + 'X-Mozilla-Status: 0000\n' + 'X-Mozilla-Status2: 00000000\n' + 'X-Mozilla-Keys: \n' + 'Date: ' + this.mDate + '\n' + 'Message-Id: <' + this.messageID + '>\n' + 'From: ' + this.author + '\n' + 'MIME-Version: 1.0\n' + 'Subject: ' + this.title + '\n' + 'Content-Transfer-Encoding: 8bit\n' + 'Content-Base: ' + this.mURL + '\n'; if (this.enclosure && this.enclosure.mFileName) { var boundaryID = source.length + this.enclosure.mLength; source += 'Content-Type: multipart/mixed;\n boundary="' + ENCLOSURE_HEADER_BOUNDARY_PREFIX + boundaryID + '"' + '\n\n' + 'This is a multi-part message in MIME format.\n' + ENCLOSURE_BOUNDARY_PREFIX + boundaryID + '\n' + 'Content-Type: text/html; charset=' + this.characterSet + '\n' + 'Content-Transfer-Encoding: 8bit\n' + this.content; source += this.enclosure.convertToAttachment(boundaryID); } else { source += 'Content-Type: text/html; charset=' + this.characterSet + '\n' + '\n' + this.content; } debug(this.identity + " is " + source.length + " characters long"); // Get the folder and database storing the feed's messages and headers. folder = this.feed.folder.QueryInterface(Components.interfaces.nsIMsgLocalMailFolder); var msgFolder = folder.QueryInterface(Components.interfaces.nsIMsgFolder); msgFolder.gettingNewMessages = true; // source is a unicode string, we want to save a char * string in the original charset. So convert back folder.addMessage(this.mUnicodeConverter.ConvertFromUnicode(source)); msgFolder.gettingNewMessages = false; this.markStored(); } }; // A feed enclosure is to RSS what an attachment is for e-mail. We make enclosures look // like attachments in the UI. function FeedEnclosure(aURL, aContentType, aLength) { this.mURL = aURL; this.mContentType = aContentType; this.mLength = aLength; // generate a fileName from the URL if (this.mURL) { var ioService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService); var enclosureURL = ioService.newURI(this.mURL, null, null).QueryInterface(Components.interfaces.nsIURL); if (enclosureURL) this.mFileName = enclosureURL.fileName; } } FeedEnclosure.prototype = { mURL: "", mContentType: "", mLength: 0, mFileName: "", // returns a string that looks like an e-mail attachment // which represents the enclosure. convertToAttachment: function(aBoundaryID) { return '\n' + ENCLOSURE_BOUNDARY_PREFIX + aBoundaryID + '\n' + 'Content-Type: ' + this.mContentType + '; name="' + this.mFileName + '"\n' + 'X-Mozilla-External-Attachment-URL: ' + this.mURL + '\n' + 'Content-Disposition: attachment; filename="' + this.mFileName + '"\n\n' + 'This MIME attachment is stored separately from the message.\n' + ENCLOSURE_BOUNDARY_PREFIX + aBoundaryID + '--' + '\n'; } }; PK rh6QQ)content/messenger-newsblog/feed-parser.js/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the RSS Parsing Engine * * The Initial Developer of the Original Code is * The Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2004 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the * ***** END LICENSE BLOCK ***** */ // The feed parser depends on FeedItems.js, Feed.js. var rdfcontainer = Components.classes["@mozilla.org/rdf/container-utils;1"].getService(Components.interfaces.nsIRDFContainerUtils); var rdfparser = Components.classes["@mozilla.org/rdf/xml-parser;1"].createInstance(Components.interfaces.nsIRDFXMLParser); var serializer = Components.classes["@mozilla.org/xmlextras/xmlserializer;1"].createInstance(Components.interfaces.nsIDOMSerializer); function FeedParser() {} FeedParser.prototype = { // parseFeed returns an array of parsed items ready for processing // it is currently a synchronous operation. If there was an error parsing the feed, // parseFeed returns an empty feed in addition to calling aFeed.onParseError parseFeed: function (aFeed, aSource, aDOM, aBaseURI) { if (!aSource || !(aDOM instanceof Components.interfaces.nsIDOMXMLDocument)) { aFeed.onParseError(aFeed); return new Array(); } else if((aDOM.documentElement.namespaceURI == "http://www.w3.org/1999/02/22-rdf-syntax-ns#") && (aDOM.documentElement.getElementsByTagNameNS("http://purl.org/rss/1.0/", "channel")[0])) { debug(aFeed.url + " is an RSS 1.x (RDF-based) feed"); // aSource can be misencoded (XMLHttpRequest converts to UTF-8 by default), // but the DOM is almost always right because it uses the hints in the XML file. // This is slower, but not noticably so. Mozilla doesn't have the // XMLHttpRequest.responseBody property that IE has, which provides access // to the unencoded response. var xmlString=serializer.serializeToString(aDOM.documentElement); return this.parseAsRSS1(aFeed, xmlString, aBaseURI); } else if (aDOM.documentElement.namespaceURI == ATOM_03_NS) { debug(aFeed.url + " is an Atom 0.3 feed"); return this.parseAsAtom(aFeed, aDOM); } else if (aDOM.documentElement.namespaceURI == ATOM_IETF_NS) { debug(aFeed.url + " is an IETF Atom feed"); return this.parseAsAtomIETF(aFeed, aDOM); } else if (aSource.search(/"http:\/\/my\.netscape\.com\/rdf\/simple\/0\.9\/"/) != -1) { debug(aFeed.url + " is an 0.90 feed"); return this.parseAsRSS2(aFeed, aDOM); } // XXX Explicitly check for RSS 2.0 instead of letting it be handled by the // default behavior (who knows, we may change the default at some point). else { // We don't know what kind of feed this is; let's pretend it's RSS 0.9x // and hope things work out for the best. In theory even RSS 1.0 feeds // could be parsed by the 0.9x parser if the RSS namespace was the default. debug(aFeed.url + " is of unknown format; assuming an RSS 0.9x feed"); return this.parseAsRSS2(aFeed, aDOM); } }, parseAsRSS2: function (aFeed, aDOM) { // Get the first channel (assuming there is only one per RSS File). var parsedItems = new Array(); var channel = aDOM.getElementsByTagName("channel")[0]; if (!channel) return aFeed.onParseError(aFeed); //usually the empty string, unless this is RSS .90 var nsURI = channel.namespaceURI || ""; debug("channel NS: '" + nsURI +"'"); aFeed.title = aFeed.title || getNodeValue(this.childrenByTagNameNS(channel, nsURI, "title")[0]); aFeed.description = getNodeValue(this.childrenByTagNameNS(channel, nsURI, "description")[0]); aFeed.link = getNodeValue(this.childrenByTagNameNS(channel, nsURI, "link")[0]); if (!aFeed.parseItems) return parsedItems; aFeed.invalidateItems(); // XXX use getElementsByTagNameNS for now // childrenByTagNameNS would be better, but RSS .90 is still with us var itemNodes = aDOM.getElementsByTagNameNS(nsURI,"item"); for (var i=0; i < itemNodes.length; i++) { var itemNode = itemNodes[i]; var item = new FeedItem(); item.feed = aFeed; item.characterSet = "UTF-8"; var link = getNodeValue(this.childrenByTagNameNS(itemNode, nsURI, "link")[0]); var guidNode = this.childrenByTagNameNS(itemNode, nsURI, "guid")[0]; var guid; var isPermaLink; if (guidNode) { guid = getNodeValue(guidNode); isPermaLink = guidNode.getAttribute('isPermaLink') == 'false' ? false : true; } item.isStoredWithId = true; item.url = link ? link : (guid && isPermaLink) ? guid : null; item.id = guid; item.description = getNodeValue(this.childrenByTagNameNS(itemNode, nsURI, "description")[0]); item.title = getNodeValue(this.childrenByTagNameNS(itemNode, nsURI, "title")[0]) || (item.description ? (this.stripTags(item.description).substr(0, 150)) : null) || item.title; item.author = getNodeValue(this.childrenByTagNameNS(itemNode, nsURI, "author")[0] || this.childrenByTagNameNS(itemNode, DC_NS, "creator")[0]) || aFeed.title || item.author; item.date = getNodeValue(this.childrenByTagNameNS(itemNode, nsURI, "pubDate")[0] || this.childrenByTagNameNS(itemNode, DC_NS, "date")[0]) || item.date; // If the date is invalid, users will see the beginning of the epoch // unless we reset it here, so they'll see the current time instead. // This is typical aggregator behavior. if(item.date) { item.date = trimString(item.date); if(!isValidRFC822Date(item.date)) { // XXX Use this on the other formats as well item.date = dateRescue(item.date); } } var content = getNodeValue(this.childrenByTagNameNS(itemNode, RSS_CONTENT_NS, "encoded")[0]); if(content) item.content = content; // Handle an enclosure (if present) var enclosureNode = this.childrenByTagNameNS(itemNode, nsURI, "enclosure")[0]; if (enclosureNode) item.enclosure = new FeedEnclosure(enclosureNode.getAttribute("url"), enclosureNode.getAttribute("type"), enclosureNode.getAttribute("length")); parsedItems[i] = item; } return parsedItems; }, parseAsRSS1 : function(aFeed, aSource, aBaseURI) { var parsedItems = new Array(); // RSS 1.0 is valid RDF, so use the RDF parser/service to extract data. // Create a new RDF data source and parse the feed into it. var ds = Components.classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"] .createInstance(Components.interfaces.nsIRDFDataSource); rdfparser.parseString(ds, aBaseURI, aSource); // Get information about the feed as a whole. var channel = ds.GetSource(RDF_TYPE, RSS_CHANNEL, true); aFeed.title = aFeed.title || getRDFTargetValue(ds, channel, RSS_TITLE) || aFeed.url; aFeed.description = getRDFTargetValue(ds, channel, RSS_DESCRIPTION) || ""; aFeed.link = getRDFTargetValue(ds, channel, RSS_LINK) || aFeed.url; if (!aFeed.parseItems) return parsedItems; aFeed.invalidateItems(); var items = ds.GetTarget(channel, RSS_ITEMS, true); if (items) items = rdfcontainer.MakeSeq(ds, items).GetElements(); // If the channel doesn't list any items, look for resources of type "item" // (a hacky workaround for some buggy feeds). if (!items || !items.hasMoreElements()) items = ds.GetSources(RDF_TYPE, RSS_ITEM, true); var index = 0; while (items.hasMoreElements()) { var itemResource = items.getNext().QueryInterface(Components.interfaces.nsIRDFResource); var item = new FeedItem(); item.feed = aFeed; item.characterSet = "UTF-8"; // Prefer the value of the link tag to the item URI since the URI could be // a relative URN. var uri = itemResource.Value; var link = getRDFTargetValue(ds, itemResource, RSS_LINK); // XXX // check for bug258465 -- entities appear escaped // in the value returned by getRDFTargetValue when they shouldn't //debug("link comparison\n" + " uri: " + uri + "\nlink: " + link); item.url = link || uri; item.id = item.url; item.description = getRDFTargetValue(ds, itemResource, RSS_DESCRIPTION); item.title = getRDFTargetValue(ds, itemResource, RSS_TITLE) || getRDFTargetValue(ds, itemResource, DC_SUBJECT) || (item.description ? (this.stripTags(item.description).substr(0, 150)) : null) || item.title; item.author = getRDFTargetValue(ds, itemResource, DC_CREATOR) || getRDFTargetValue(ds, channel, DC_CREATOR) || aFeed.title || item.author; item.date = getRDFTargetValue(ds, itemResource, DC_DATE) || item.date; item.content = getRDFTargetValue(ds, itemResource, RSS_CONTENT_ENCODED); parsedItems[index++] = item; } return parsedItems; }, parseAsAtom: function(aFeed, aDOM) { var parsedItems = new Array(); // Get the first channel (assuming there is only one per Atom File). var channel = aDOM.getElementsByTagName("feed")[0]; if (!channel) { aFeed.onParseError(aFeed); return parsedItems; } aFeed.title = aFeed.title || this.stripTags(getNodeValue(this.childrenByTagNameNS(channel, ATOM_03_NS, "title")[0])); aFeed.description = getNodeValue(this.childrenByTagNameNS(channel, ATOM_03_NS, "tagline")[0]); aFeed.link = this.findAtomLink("alternate",this.childrenByTagNameNS(channel, ATOM_03_NS, "link")); if (!aFeed.parseItems) return parsedItems; aFeed.invalidateItems(); var items = this.childrenByTagNameNS(channel, ATOM_03_NS, "entry"); debug("Items to parse: " + items.length); for (var i=0; i < items.length; i++) { var itemNode = items[i]; var item = new FeedItem(); item.feed = aFeed; item.characterSet = "UTF-8"; var url; url = this.findAtomLink("alternate",this.childrenByTagNameNS(itemNode, ATOM_03_NS, "link")); item.url = url; item.id = getNodeValue(this.childrenByTagNameNS(itemNode, ATOM_03_NS, "id")[0]); item.description = getNodeValue(this.childrenByTagNameNS(itemNode, ATOM_03_NS, "summary")[0]); item.title = getNodeValue(this.childrenByTagNameNS(itemNode, ATOM_03_NS, "title")[0]) || (item.description ? item.description.substr(0, 150) : null) || item.title; var authorEl = this.childrenByTagNameNS(itemNode, ATOM_03_NS, "author")[0] || this.childrenByTagNameNS(itemNode, ATOM_03_NS, "contributor")[0] || this.childrenByTagNameNS(channel, ATOM_03_NS, "author")[0]; var author = ""; if (authorEl) { var name = getNodeValue(this.childrenByTagNameNS(authorEl, ATOM_03_NS, "name")[0]); var email = getNodeValue(this.childrenByTagNameNS(authorEl, ATOM_03_NS, "email")[0]); if (name) author = name + (email ? " <" + email + ">" : ""); else if (email) author = email; } item.author = author || item.author || aFeed.title; item.date = getNodeValue(this.childrenByTagNameNS(itemNode, ATOM_03_NS, "modified")[0] || this.childrenByTagNameNS(itemNode, ATOM_03_NS, "issued")[0] || this.childrenByTagNameNS(itemNode, ATOM_03_NS, "created")[0]) || item.date; // XXX We should get the xml:base attribute from the content tag as well // and use it as the base HREF of the message. // XXX Atom feeds can have multiple content elements; we should differentiate // between them and pick the best one. // Some Atom feeds wrap the content in a CTYPE declaration; others use // a namespace to identify the tags as HTML; and a few are buggy and put // HTML tags in without declaring their namespace so they look like Atom. // We deal with the first two but not the third. var content; var contentNode = this.childrenByTagNameNS(itemNode, ATOM_03_NS, "content")[0]; if (contentNode) { content = ""; for (var j=0; j < contentNode.childNodes.length; j++) { var node = contentNode.childNodes.item(j); if (node.nodeType == node.CDATA_SECTION_NODE) content += node.data; else content += serializer.serializeToString(node); } if (contentNode.getAttribute('mode') == "escaped") { content = content.replace(/</g, "<"); content = content.replace(/>/g, ">"); content = content.replace(/&/g, "&"); } if (content == "") content = null; } item.content = content; parsedItems[i] = item; } return parsedItems; }, parseAsAtomIETF: function(aFeed, aDOM) { var parsedItems = new Array(); // Get the first channel (assuming there is only one per Atom File). var channel = this.childrenByTagNameNS(aDOM,ATOM_IETF_NS,"feed")[0]; if (!channel) { aFeed.onParseError(aFeed); return parsedItems; } aFeed.title = aFeed.title || this.stripTags(this.serializeTextConstruct(this.childrenByTagNameNS(channel,ATOM_IETF_NS,"title")[0])); aFeed.description = this.serializeTextConstruct(this.childrenByTagNameNS(channel,ATOM_IETF_NS,"subtitle")[0]); aFeed.link = this.findAtomLink("alternate", this.childrenByTagNameNS(channel,ATOM_IETF_NS,"link")); if (!aFeed.parseItems) return parsedItems; aFeed.invalidateItems(); var items = this.childrenByTagNameNS(channel,ATOM_IETF_NS,"entry"); debug("Items to parse: " + items.length); for (var i=0; i < items.length; i++) { var itemNode = items[i]; var item = new FeedItem(); item.feed = aFeed; item.characterSet = "UTF-8"; item.isStoredWithId = true; item.url = this.findAtomLink("alternate", this.childrenByTagNameNS(itemNode, ATOM_IETF_NS, "link")) || aFeed.link; item.id = getNodeValue(this.childrenByTagNameNS(itemNode, ATOM_IETF_NS, "id")[0]); item.description = this.serializeTextConstruct(this.childrenByTagNameNS(itemNode, ATOM_IETF_NS, "summary")[0]); item.title = this.stripTags(this.serializeTextConstruct(this.childrenByTagNameNS(itemNode, ATOM_IETF_NS, "title")[0]) || (item.description ? item.description.substr(0, 150) : null) || item.title); // XXX Support multiple authors var source = this.childrenByTagNameNS(itemNode, ATOM_IETF_NS, "source")[0]; var authorEl = this.childrenByTagNameNS(itemNode, ATOM_IETF_NS, "author")[0] || (source ? this.childrenByTagNameNS(source, ATOM_IETF_NS, "author")[0] : null) || this.childrenByTagNameNS(channel, ATOM_IETF_NS, "author")[0]; var author = ""; if (authorEl) { var name = getNodeValue(this.childrenByTagNameNS(authorEl, ATOM_IETF_NS, "name")[0]); var email = getNodeValue(this.childrenByTagNameNS(authorEl, ATOM_IETF_NS, "email")[0]); if (name) author = name + (email ? " <" + email + ">" : ""); else if (email) author = email; } item.author = author || item.author || aFeed.title; item.date = getNodeValue(this.childrenByTagNameNS(itemNode, ATOM_IETF_NS, "updated")[0] || this.childrenByTagNameNS(itemNode, ATOM_IETF_NS, "published")[0]) || item.date; item.content = this.serializeTextConstruct(this.childrenByTagNameNS(itemNode, ATOM_IETF_NS, "content")[0]); if(item.content) item.xmlContentBase = this.childrenByTagNameNS(itemNode, ATOM_IETF_NS, "content")[0].baseURI; else if(item.description) item.xmlContentBase = this.childrenByTagNameNS(itemNode, ATOM_IETF_NS, "summary")[0].baseURI; else item.xmlContentBase = itemNode.baseURI; parsedItems[i] = item; } return parsedItems; }, serializeTextConstruct: function(textElement) { var content = ""; if (textElement) { var textType = textElement.getAttribute('type'); // Atom spec says consider it "text" if not present if(!textType) textType = "text"; // There could be some strange content type we don't handle if((textType != "text") && (textType != "html") && (textType != "xhtml")) return null; for (var j=0; j < textElement.childNodes.length; j++) { var node = textElement.childNodes.item(j); if (node.nodeType == node.CDATA_SECTION_NODE) content += this.xmlEscape(node.data); else content += serializer.serializeToString(node); } if (textType == "html") content = this.xmlUnescape(content); } // other parts of the code depend on this being null // if there's no content return content ? content : null; }, // finds elements that are direct children of the first arg childrenByTagNameNS: function(aElement, aNamespace, aTagName) { var matches = aElement.getElementsByTagNameNS(aNamespace, aTagName); var matchingChildren = new Array(); for (var i = 0; i < matches.length; i++) { if(matches[i].parentNode == aElement) matchingChildren.push(matches[i]) } return matchingChildren; }, findAtomLink: function(linkRel, linkElements) { // XXX Need to check for MIME type and hreflang for ( var j=0 ; j]+>/g,"") : someHTML; }, xmlUnescape: function(s) { s = s.replace(/</g, "<"); s = s.replace(/>/g, ">"); s = s.replace(/&/g, "&"); return s; }, xmlEscape: function(s) { s = s.replace(/&/g, "&"); s = s.replace(/>/g, ">"); s = s.replace(/, original author * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* notice that these valuse are octal. */ const PERM_IRWXU = 00700; /* read, write, execute/search by owner */ const PERM_IRUSR = 00400; /* read permission, owner */ const PERM_IWUSR = 00200; /* write permission, owner */ const PERM_IXUSR = 00100; /* execute/search permission, owner */ const PERM_IRWXG = 00070; /* read, write, execute/search by group */ const PERM_IRGRP = 00040; /* read permission, group */ const PERM_IWGRP = 00020; /* write permission, group */ const PERM_IXGRP = 00010; /* execute/search permission, group */ const PERM_IRWXO = 00007; /* read, write, execute/search by others */ const PERM_IROTH = 00004; /* read permission, others */ const PERM_IWOTH = 00002; /* write permission, others */ const PERM_IXOTH = 00001; /* execute/search permission, others */ const MODE_RDONLY = 0x01; const MODE_WRONLY = 0x02; const MODE_RDWR = 0x04; const MODE_CREATE = 0x08; const MODE_APPEND = 0x10; const MODE_TRUNCATE = 0x20; const MODE_SYNC = 0x40; const MODE_EXCL = 0x80; const PICK_OK = Components.interfaces.nsIFilePicker.returnOK; const PICK_CANCEL = Components.interfaces.nsIFilePicker.returnCancel; const PICK_REPLACE = Components.interfaces.nsIFilePicker.returnReplace; const FILTER_ALL = Components.interfaces.nsIFilePicker.filterAll; const FILTER_HTML = Components.interfaces.nsIFilePicker.filterHTML; const FILTER_TEXT = Components.interfaces.nsIFilePicker.filterText; const FILTER_IMAGES = Components.interfaces.nsIFilePicker.filterImages; const FILTER_XML = Components.interfaces.nsIFilePicker.filterXML; const FILTER_XUL = Components.interfaces.nsIFilePicker.filterXUL; // evald f = fopen("/home/rginda/foo.txt", MODE_WRONLY | MODE_CREATE) // evald f = fopen("/home/rginda/vnk.txt", MODE_RDONLY) var futils = new Object(); futils.umask = PERM_IWOTH | PERM_IWGRP; futils.MSG_SAVE_AS = "Save As"; futils.MSG_OPEN = "Open"; futils.getPicker = function futils_nosepicker(initialPath, typeList, attribs) { const classes = Components.classes; const interfaces = Components.interfaces; const PICKER_CTRID = "@mozilla.org/filepicker;1"; const LOCALFILE_CTRID = "@mozilla.org/file/local;1"; const nsIFilePicker = interfaces.nsIFilePicker; const nsILocalFile = interfaces.nsILocalFile; var picker = classes[PICKER_CTRID].createInstance(nsIFilePicker); if (typeof attribs == "object") { for (var a in attribs) picker[a] = attribs[a]; } else throw "bad type for param |attribs|"; if (initialPath) { var localFile; if (typeof initialPath == "string") { localFile = classes[LOCALFILE_CTRID].createInstance(nsILocalFile); localFile.initWithPath(initialPath); } else { if (!(initialPath instanceof nsILocalFile)) throw "bad type for argument |initialPath|"; localFile = initialPath; } picker.displayDirectory = localFile } if (typeof typeList == "string") typeList = typeList.split(" "); if (typeList instanceof Array) { for (var i in typeList) { switch (typeList[i]) { case "$all": picker.appendFilters(FILTER_ALL); break; case "$html": picker.appendFilters(FILTER_HTML); break; case "$text": picker.appendFilters(FILTER_TEXT); break; case "$images": picker.appendFilters(FILTER_IMAGES); break; case "$xml": picker.appendFilters(FILTER_XML); break; case "$xul": picker.appendFilters(FILTER_XUL); break; case "$opml": var newsBlogBundle = document.getElementById("bundle_newsblog"); picker.appendFilter( newsBlogBundle.getString("subscribe-OPMLExportOPMLFilesFilterText"), "*.opml"); break; default: picker.appendFilter(typeList[i], typeList[i]); break; } } } return picker; } function pickSaveAs (title, typeList, defaultFile, defaultDir) { if (!defaultDir && "lastSaveAsDir" in futils) defaultDir = futils.lastSaveAsDir; var picker = futils.getPicker (defaultDir, typeList, {defaultString: defaultFile}); picker.init (window, title ? title : futils.MSG_SAVE_AS, Components.interfaces.nsIFilePicker.modeSave); var rv = picker.show(); if (rv != PICK_CANCEL) futils.lastSaveAsDir = picker.file.parent; return {reason: rv, file: picker.file, picker: picker}; } function pickOpen (title, typeList, defaultFile, defaultDir) { if (!defaultDir && "lastOpenDir" in futils) defaultDir = futils.lastOpenDir; var picker = futils.getPicker (defaultDir, typeList, {defaultString: defaultFile}); picker.init (window, title ? title : futils.MSG_OPEN, Components.interfaces.nsIFilePicker.modeOpen); var rv = picker.show(); if (rv != PICK_CANCEL) futils.lastOpenDir = picker.file.parent; return {reason: rv, file: picker.file, picker: picker}; } function fopen (path, mode, perms, tmp) { return new LocalFile(path, mode, perms, tmp); } function LocalFile(file, mode, perms, tmp) { const classes = Components.classes; const interfaces = Components.interfaces; const LOCALFILE_CTRID = "@mozilla.org/file/local;1"; const FILEIN_CTRID = "@mozilla.org/network/file-input-stream;1"; const FILEOUT_CTRID = "@mozilla.org/network/file-output-stream;1"; const SCRIPTSTREAM_CTRID = "@mozilla.org/scriptableinputstream;1"; const nsIFile = interfaces.nsIFile; const nsILocalFile = interfaces.nsILocalFile; const nsIFileOutputStream = interfaces.nsIFileOutputStream; const nsIFileInputStream = interfaces.nsIFileInputStream; const nsIScriptableInputStream = interfaces.nsIScriptableInputStream; if (typeof perms == "undefined") perms = 0666 & ~futils.umask; if (typeof file == "string") { this.localFile = classes[LOCALFILE_CTRID].createInstance(nsILocalFile); this.localFile.initWithPath(file); } else if (file instanceof nsILocalFile) { this.localFile = file; } else if (file instanceof Array && file.length > 0) { this.localFile = classes[LOCALFILE_CTRID].createInstance(nsILocalFile); this.localFile.initWithPath(file.shift()); while (file.length > 0) this.localFile.appendRelativePath(file.shift()); } else { throw "bad type for argument |file|."; } if (mode & (MODE_WRONLY | MODE_RDWR)) { this.outputStream = classes[FILEOUT_CTRID].createInstance(nsIFileOutputStream); this.outputStream.init(this.localFile, mode, perms, 0); } if (mode & (MODE_RDONLY | MODE_RDWR)) { var is = classes[FILEIN_CTRID].createInstance(nsIFileInputStream); is.init(this.localFile, mode, perms, tmp); this.inputStream = classes[SCRIPTSTREAM_CTRID].createInstance(nsIScriptableInputStream); this.inputStream.init(is); } } LocalFile.prototype.write = function fo_write(buf) { if (!("outputStream" in this)) throw "file not open for writing."; return this.outputStream.write(buf, buf.length); } LocalFile.prototype.read = function fo_read(max) { if (!("inputStream" in this)) throw "file not open for reading."; var av = this.inputStream.available(); if (typeof max == "undefined") max = av; if (!av) return null; var rv = this.inputStream.read(max); return rv; } LocalFile.prototype.close = function fo_close() { if ("outputStream" in this) this.outputStream.close(); if ("inputStream" in this) this.inputStream.close(); } LocalFile.prototype.flush = function fo_close() { return this.outputStream.flush(); } PK 5b_444#content/messenger-newsblog/utils.js//@line 39 "/builds/tinderbox/Tb-Mozilla1.8-Release/Linux_2.4.18-14_Depend/mozilla/mail/extensions/newsblog/content/utils.js" // Whether or not to dump debugging messages to the console. const DEBUG = false; var debug; if (DEBUG) debug = function(msg) { dump(' -- FZ -- : ' + msg + '\n'); } else debug = function() {} var rdf = Components.classes["@mozilla.org/rdf/rdf-service;1"].getService(Components.interfaces.nsIRDFService); const RDF_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; const RDF_TYPE = rdf.GetResource(RDF_NS + "type"); const RSS_NS = "http://purl.org/rss/1.0/"; const RSS_CHANNEL = rdf.GetResource(RSS_NS + "channel"); const RSS_TITLE = rdf.GetResource(RSS_NS + "title"); const RSS_DESCRIPTION = rdf.GetResource(RSS_NS + "description"); const RSS_ITEMS = rdf.GetResource(RSS_NS + "items"); const RSS_ITEM = rdf.GetResource(RSS_NS + "item"); const RSS_LINK = rdf.GetResource(RSS_NS + "link"); const RSS_CONTENT_NS = "http://purl.org/rss/1.0/modules/content/"; const RSS_CONTENT_ENCODED = rdf.GetResource(RSS_CONTENT_NS + "encoded"); const DC_NS = "http://purl.org/dc/elements/1.1/"; const DC_CREATOR = rdf.GetResource(DC_NS + "creator"); const DC_SUBJECT = rdf.GetResource(DC_NS + "subject"); const DC_DATE = rdf.GetResource(DC_NS + "date"); const DC_TITLE = rdf.GetResource(DC_NS + "title"); const DC_LASTMODIFIED = rdf.GetResource(DC_NS + "lastModified"); const DC_IDENTIFIER = rdf.GetResource(DC_NS + "identifier"); const FZ_NS = "urn:forumzilla:"; const FZ_ROOT = rdf.GetResource(FZ_NS + "root"); const FZ_FEEDS = rdf.GetResource(FZ_NS + "feeds"); const FZ_FEED = rdf.GetResource(FZ_NS + "feed"); const FZ_QUICKMODE = rdf.GetResource(FZ_NS + "quickMode"); const FZ_DESTFOLDER = rdf.GetResource(FZ_NS + "destFolder"); const FZ_STORED = rdf.GetResource(FZ_NS + "stored"); const FZ_VALID = rdf.GetResource(FZ_NS + "valid"); const RDF_LITERAL_TRUE = rdf.GetLiteral("true"); const RDF_LITERAL_FALSE = rdf.GetLiteral("false"); // Atom constants const ATOM_03_NS = "http://purl.org/atom/ns#"; const ATOM_IETF_NS = "http://www.w3.org/2005/Atom"; // XXX There's a containerutils in forumzilla.js that this should be merged with. var containerUtils = Components.classes["@mozilla.org/rdf/container-utils;1"] .getService(Components.interfaces.nsIRDFContainerUtils); var fileHandler = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService) .getProtocolHandler("file").QueryInterface(Components.interfaces.nsIFileProtocolHandler); // helper routine that checks our subscriptions list array and returns true if the url // is already in our list. This is used to prevent the user from subscribing to the same // feed multiple times for the same server... function feedAlreadyExists(aUrl, aServer) { var feeds = getSubscriptionsList(aServer); return feeds.IndexOf(rdf.GetResource(aUrl)) != -1; } function addFeed(url, title, destFolder) { var ds = getSubscriptionsDS(destFolder.server); var feeds = getSubscriptionsList(destFolder.server); // Generate a unique ID for the feed. var id = url; var i = 1; while (feeds.IndexOf(rdf.GetResource(id)) != -1 && ++i < 1000) id = url + i; if (id == 1000) throw("couldn't generate a unique ID for feed " + url); // Add the feed to the list. id = rdf.GetResource(id); feeds.AppendElement(id); ds.Assert(id, RDF_TYPE, FZ_FEED, true); ds.Assert(id, DC_IDENTIFIER, rdf.GetLiteral(url), true); if (title) ds.Assert(id, DC_TITLE, rdf.GetLiteral(title), true); ds.Assert(id, FZ_DESTFOLDER, destFolder, true); ds = ds.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource); ds.Flush(); } // updates the "feedUrl" property in the message database for the folder in question. var kFeedUrlDelimiter = '|'; // the delimiter used to delimit feed urls in the msg folder database "feedUrl" property function updateFolderFeedUrl(aFolder, aFeedUrl, aRemoveUrl) { var msgdb = aFolder.QueryInterface(Components.interfaces.nsIMsgFolder).getMsgDatabase(null); var folderInfo = msgdb.dBFolderInfo; var oldFeedUrl = folderInfo.getCharPtrProperty("feedUrl"); if (aRemoveUrl) { // remove our feed url string from the list of feed urls var newFeedUrl = oldFeedUrl.replace(kFeedUrlDelimiter + aFeedUrl, ""); folderInfo.setCharPtrProperty("feedUrl", newFeedUrl); } else folderInfo.setCharPtrProperty("feedUrl", oldFeedUrl + kFeedUrlDelimiter + aFeedUrl); // commit the db to preserve our changes msgdb.Close(true); } function getNodeValue(node) { if (node && node.textContent) return node.textContent; else if (node && node.firstChild) { var ret = ""; for (var child = node.firstChild; child; child = child.nextSibling) { var value = getNodeValue(child); if (value) ret += value; } if (ret) return ret; } return null; } function getRDFTargetValue(ds, source, property) { var node = ds.GetTarget(source, property, true); if (node) { try{ node = node.QueryInterface(Components.interfaces.nsIRDFLiteral); if (node) return node.Value; }catch(e){ // if the RDF was bogus, do nothing. rethrow if it's some other problem if(!((e instanceof Components.interfaces.nsIXPCException) && (e.result==Components.results.NS_ERROR_NO_INTERFACE))) throw e; } } return null; } function getSubscriptionsDS(server) { var file = getSubscriptionsFile(server); var url = fileHandler.getURLSpecFromFile(file); // GetDataSourceBlocking has a cache, so it's cheap to do this again // once we've already done it once. var ds = rdf.GetDataSourceBlocking(url); if (!ds) throw("can't get subscriptions data source"); return ds; } function getSubscriptionsList(server) { var ds = getSubscriptionsDS(server); var list = ds.GetTarget(FZ_ROOT, FZ_FEEDS, true); //list = feeds.QueryInterface(Components.interfaces.nsIRDFContainer); list = list.QueryInterface(Components.interfaces.nsIRDFResource); list = containerUtils.MakeSeq(ds, list); return list; } function getSubscriptionsFile(server) { server.QueryInterface(Components.interfaces.nsIRssIncomingServer); var file = server.subscriptionsDataSourcePath; // If the file doesn't exist, create it. if (!file.exists()) createSubscriptionsFile(file); return file; } function createSubscriptionsFile(file) { file = new LocalFile(file, MODE_WRONLY | MODE_CREATE); file.write('\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ '); file.close(); } function getItemsDS(server) { var file = getItemsFile(server); var url = fileHandler.getURLSpecFromFile(file); // GetDataSourceBlocking has a cache, so it's cheap to do this again // once we've already done it once. var ds = rdf.GetDataSourceBlocking(url); if (!ds) throw("can't get subscriptions data source"); // Note that it this point the datasource may not be loaded yet. // You have to QueryInterface it to nsIRDFRemoteDataSource and check // its "loaded" property to be sure. You can also attach an observer // which will get notified when the load is complete. return ds; } function getItemsFile(server) { server.QueryInterface(Components.interfaces.nsIRssIncomingServer); var file = server.feedItemsDataSourcePath; // If the file doesn't exist, create it. if (!file.exists()) { var newfile = new LocalFile(file, MODE_WRONLY | MODE_CREATE); newfile.write('\ \n\ \n\ \n\ '); newfile.close(); } return file; } function removeAssertions(ds, resource) { var properties = ds.ArcLabelsOut(resource); var property; while (properties.hasMoreElements()) { property = properties.getNext(); var values = ds.GetTargets(resource, property, true); var value; while (values.hasMoreElements()) { value = values.getNext(); ds.Unassert(resource, property, value, true); } } } // Date validator for RSS feeds const FZ_RFC822_RE = "^(((Mon)|(Tue)|(Wed)|(Thu)|(Fri)|(Sat)|(Sun)), *)?\\d\\d?" + " +((Jan)|(Feb)|(Mar)|(Apr)|(May)|(Jun)|(Jul)|(Aug)|(Sep)|(Oct)|(Nov)|(Dec))" + " +\\d\\d(\\d\\d)? +\\d\\d:\\d\\d(:\\d\\d)? +(([+-]?\\d\\d\\d\\d)|(UT)|(GMT)" + "|(EST)|(EDT)|(CST)|(CDT)|(MST)|(MDT)|(PST)|(PDT)|\\w)$"; function isValidRFC822Date(pubDate) { var regex = new RegExp(FZ_RFC822_RE); return regex.test(pubDate); } function dateRescue(dateString) { // Deal with various kinds of invalid dates if(!isNaN(parseInt(dateString))) { //It's an integer, so maybe it's a timestamp var d = new Date(parseInt(dateString)*1000); var now = new Date(); var yeardiff = now.getFullYear()-d.getFullYear(); debug("Rescue Timestamp date: " + d.toString() + "\nYear diff:" + yeardiff + "\n"); if((yeardiff >= 0) && (yeardiff<3)) { //it's quite likely the correct date return d.toString(); } } if(dateString.search(/^\d\d\d\d/) != -1) //Could be a ISO8601/W3C date return W3CToIETFDate(dateString); // Can't help. Set to current time. return (new Date()).toString(); } // Could be a prototype on String, but I don't know the policy on that function trimString(s) { return(s.replace(/^\s+/,'').replace(/\s+$/,'')); } function W3CToIETFDate(dateString) { // Converts a W3C-DTF (subset of ISO 8601) date string to an IETF date string. // W3C-DTF is described in this note: http://www.w3.org/TR/NOTE-datetime // IETF is obtained via the Date object's toUTCString() method. The object's // toString() method is insufficient because it spells out timezones on Win32 // (f.e. "Pacific Standard Time" instead of "PST"), which Mail doesn't grok. // For info, see http://lxr.mozilla.org/mozilla/source/js/src/jsdate.c#1526. var parts = dateString.match(/(\d\d\d\d)(-(\d\d))?(-(\d\d))?(T(\d\d):(\d\d)(:(\d\d)(\.(\d+))?)?(Z|([+-])(\d\d):(\d\d))?)?/); debug("date parts: " + parts); // Here's an example of a W3C-DTF date string and what .match returns for it. // date: 2003-05-30T11:18:50.345-08:00 // date.match returns array values: // 0: 2003-05-30T11:18:50-08:00, // 1: 2003, // 2: -05, // 3: 05, // 4: -30, // 5: 30, // 6: T11:18:50-08:00, // 7: 11, // 8: 18, // 9: :50, // 10: 50, // 11: .345, // 12: 345, // 13: -08:00, // 14: -, // 15: 08, // 16: 00 // Create a Date object from the date parts. Note that the Date object // apparently can't deal with empty string parameters in lieu of numbers, // so optional values (like hours, minutes, seconds, and milliseconds) // must be forced to be numbers. var date = new Date(parts[1], parts[3]-1, parts[5], parts[7] || 0, parts[8] || 0, parts[10] || 0, parts[12] || 0); // We now have a value that the Date object thinks is in the local timezone // but which actually represents the date/time in the remote timezone // (f.e. the value was "10:00 EST", and we have converted it to "10:00 PST" // instead of "07:00 PST"). We need to correct that. To do so, we're going // to add the offset between the remote timezone and UTC (to convert the value // to UTC), then add the offset between UTC and the local timezone (to convert // the value to the local timezone). // Ironically, W3C-DTF gives us the offset between UTC and the remote timezone // rather than the other way around, while the getTimezoneOffset() method // of a Date object gives us the offset between the local timezone and UTC // rather than the other way around. Both of these are the additive inverse // (i.e. -x for x) of what we want, so we have to invert them to use them // by multipying by -1 // (f.e. if "the offset between UTC and the remote timezone" is -5 hours, // then "the offset between the remote timezone and UTC" is -5*-1 = 5 hours). // Note that if the timezone portion of the date/time string is absent // (which violates W3C-DTF, although ISO 8601 allows it), we assume the value // to be in UTC. // The offset between the remote timezone and UTC in milliseconds. var remoteToUTCOffset = 0; if (parts[13] && parts[13] != "Z") { var direction = (parts[14] == "+" ? 1 : -1); if (parts[15]) remoteToUTCOffset += direction * parts[15] * HOURS_TO_MILLISECONDS; if (parts[16]) remoteToUTCOffset += direction * parts[16] * MINUTES_TO_MILLISECONDS; } remoteToUTCOffset = remoteToUTCOffset * -1; // invert it debug("date remote to UTC offset: " + remoteToUTCOffset); // The offset between UTC and the local timezone in milliseconds. var UTCToLocalOffset = date.getTimezoneOffset() * MINUTES_TO_MILLISECONDS; UTCToLocalOffset = UTCToLocalOffset * -1; // invert it debug("date UTC to local offset: " + UTCToLocalOffset); date.setTime(date.getTime() + remoteToUTCOffset + UTCToLocalOffset); debug("date string: " + date.toUTCString()); return date.toUTCString(); } PK H5v.content/messenger-newsblog/feed-properties.xul