summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/news/src/NntpService.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/news/src/NntpService.jsm')
-rw-r--r--comm/mailnews/news/src/NntpService.jsm250
1 files changed, 250 insertions, 0 deletions
diff --git a/comm/mailnews/news/src/NntpService.jsm b/comm/mailnews/news/src/NntpService.jsm
new file mode 100644
index 0000000000..cae1cd9002
--- /dev/null
+++ b/comm/mailnews/news/src/NntpService.jsm
@@ -0,0 +1,250 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const EXPORTED_SYMBOLS = ["NntpService"];
+
+const { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+/**
+ * @implements {nsINntpService}
+ */
+class NntpService {
+ QueryInterface = ChromeUtils.generateQI(["nsINntpService"]);
+
+ get cacheStorage() {
+ if (!this._cacheStorage) {
+ this._cacheStorage = Services.cache2.memoryCacheStorage(
+ Services.loadContextInfo.custom(false, {})
+ );
+ }
+ return this._cacheStorage;
+ }
+
+ generateNewsHeaderValsForPosting(
+ newsgroupsList,
+ outNewsgroupsHeader,
+ outNewsHostHeader
+ ) {
+ let groups = newsgroupsList.split(",");
+ outNewsgroupsHeader.value = newsgroupsList;
+ let hosts = groups.map(name => this._findHostFromGroupName(name));
+ hosts = [...new Set(hosts)].filter(Boolean);
+ let host = hosts[0];
+ if (!host) {
+ outNewsHostHeader.value = "";
+ return;
+ }
+ if (hosts.length > 1) {
+ throw Components.Exception(
+ `Cross posting not allowed, hosts=${hosts.join(",")}`,
+ Cr.NS_ERROR_ILLEGAL_VALUE
+ );
+ }
+ outNewsHostHeader.value = host;
+ }
+
+ postMessage(messageFile, groupNames, accountKey, urlListener, msgWindow) {
+ let server = MailServices.accounts.getAccount(accountKey)?.incomingServer;
+ if (!server) {
+ // If no matching server, find the first news server and use it.
+ server = MailServices.accounts.findServer("", "", "nntp");
+ }
+ server = server.QueryInterface(Ci.nsINntpIncomingServer);
+
+ server.wrappedJSObject.withClient(client => {
+ client.startRunningUrl(urlListener, msgWindow);
+
+ client.onOpen = () => {
+ client.post();
+ };
+
+ client.onReadyToPost = () => {
+ let fstream = Cc[
+ "@mozilla.org/network/file-input-stream;1"
+ ].createInstance(Ci.nsIFileInputStream);
+ // PR_RDONLY
+ fstream.init(messageFile, 0x01, 0, 0);
+ let lineInputStream = fstream.QueryInterface(Ci.nsILineInputStream);
+ let hasMore;
+ do {
+ let outLine = {};
+ hasMore = lineInputStream.readLine(outLine);
+ let line = outLine.value;
+ if (line.startsWith(".")) {
+ // Dot stuffing, see rfc3977#section-3.1.1.
+ line = "." + line;
+ }
+ client.send(line + "\r\n");
+ } while (hasMore);
+ fstream.close();
+ client.send(".\r\n");
+ };
+ });
+ }
+
+ getNewNews(server, uri, getOld, urlListener, msgWindow) {
+ if (Services.io.offline) {
+ const NS_MSG_ERROR_OFFLINE = 0x80550014;
+ // @see nsMsgNewsFolder::UpdateFolder
+ throw Components.Exception(
+ "Cannot get news while offline",
+ NS_MSG_ERROR_OFFLINE
+ );
+ }
+ // The uri is in the form of news://news.mozilla.org/mozilla.accessibility
+ let matches = /.+:\/\/([^:]+):?(\d+)?\/(.+)?/.exec(uri);
+ let groupName = decodeURIComponent(matches[3]);
+
+ let runningUri = Services.io
+ .newURI(uri)
+ .QueryInterface(Ci.nsIMsgMailNewsUrl);
+ server.wrappedJSObject.withClient(client => {
+ client.startRunningUrl(urlListener, msgWindow, runningUri);
+ client.onOpen = () => {
+ client.getNewNews(groupName, getOld);
+ };
+ });
+
+ return runningUri;
+ }
+
+ getListOfGroupsOnServer(server, msgWindow, getOnlyNew) {
+ server.wrappedJSObject.withClient(client => {
+ client.startRunningUrl(null, msgWindow);
+ client.onOpen = () => {
+ client.getListOfGroups(getOnlyNew);
+ };
+
+ client.onData = data => {
+ server.addNewsgroupToList(data.split(" ")[0]);
+ };
+ });
+ }
+
+ fetchMessage(folder, key, msgWindow, consumer, urlListener) {
+ let streamListener, inputStream, outputStream;
+ if (consumer instanceof Ci.nsIStreamListener) {
+ streamListener = consumer;
+ let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+ pipe.init(true, true, 0, 0);
+ inputStream = pipe.inputStream;
+ outputStream = pipe.outputStream;
+ }
+
+ let server = folder.server.QueryInterface(Ci.nsINntpIncomingServer);
+ server.wrappedJSObject.withClient(client => {
+ client.startRunningUrl(urlListener, msgWindow);
+
+ client.onOpen = () => {
+ client.getArticleByArticleNumber(folder.name, key);
+ streamListener?.onStartRequest(null);
+ };
+ client.onData = data => {
+ outputStream?.write(data, data.length);
+ streamListener?.onDataAvailable(null, inputStream, 0, data.length);
+ };
+ client.onDone = () => {
+ streamListener?.onStopRequest(null, Cr.NS_OK);
+ };
+ });
+ }
+
+ cancelMessage(cancelUrl, messageUri, consumer, urlListener, msgWindow) {
+ if (Services.prefs.getBoolPref("news.cancel.confirm")) {
+ let bundle = Services.strings.createBundle(
+ "chrome://messenger/locale/news.properties"
+ );
+ let result = Services.prompt.confirmEx(
+ msgWindow?.domWindow,
+ null,
+ bundle.GetStringFromName("cancelConfirm"),
+ Ci.nsIPrompt.STD_YES_NO_BUTTONS,
+ null,
+ null,
+ null,
+ null,
+ { value: false }
+ );
+ if (result != 0) {
+ // Cancelled.
+ return;
+ }
+ }
+ // The cancelUrl is in the form of "news://host/message-id?cancel"
+ let url = new URL(cancelUrl);
+ let messageId = "<" + decodeURIComponent(url.pathname.slice(1)) + ">";
+ let server = MailServices.accounts
+ .findServer("", url.host, "nntp")
+ .QueryInterface(Ci.nsINntpIncomingServer);
+ let groupName = new URL(messageUri).pathname.slice(1);
+ let messageKey = messageUri.split("#")[1];
+ let newsFolder = server.findGroup(groupName);
+ let from = MailServices.accounts.getFirstIdentityForServer(server).email;
+ let bundle = Services.strings.createBundle(
+ "chrome://branding/locale/brand.properties"
+ );
+
+ server.wrappedJSObject.withClient(client => {
+ let runningUrl = client.startRunningUrl(urlListener, msgWindow);
+ runningUrl.msgWindow = msgWindow;
+
+ client.onOpen = () => {
+ client.cancelArticle(groupName);
+ };
+
+ client.onReadyToPost = () => {
+ let content = [
+ `From: ${from}`,
+ `Newsgroups: ${groupName}`,
+ `Subject: cancel ${messageId}`,
+ `References: ${messageId}`,
+ `Control: cancel ${messageId}`,
+ "MIME-Version: 1.0",
+ "Content-Type: text/plain",
+ "", // body separator
+ `This message was cancelled from within ${bundle.GetStringFromName(
+ "brandFullName"
+ )}`,
+ ];
+ client.send(content.join("\r\n"));
+ client.send("\r\n.\r\n");
+
+ newsFolder.removeMessage(messageKey);
+ newsFolder.cancelComplete();
+ };
+ });
+ }
+
+ downloadNewsgroupsForOffline(msgWindow, urlListener) {
+ let { NewsDownloader } = ChromeUtils.importESModule(
+ "resource:///modules/NewsDownloader.sys.mjs"
+ );
+ let downloader = new NewsDownloader(msgWindow, urlListener);
+ downloader.start();
+ }
+
+ /**
+ * Find the hostname of a NNTP server from a group name.
+ *
+ * @param {string} groupName - The group name.
+ * @returns {string} The corresponding server host.
+ */
+ _findHostFromGroupName(groupName) {
+ for (let server of MailServices.accounts.allServers) {
+ if (
+ server instanceof Ci.nsINntpIncomingServer &&
+ server.containsNewsgroup(groupName)
+ ) {
+ return server.hostName;
+ }
+ }
+ return "";
+ }
+}
+
+NntpService.prototype.classID = Components.ID(
+ "{b13db263-a219-4168-aeaf-8266f001087e}"
+);