Bug 1719414 - Init NntpClient.jsm to interact with NNTP server. r=mkmelin
Depends on D119594. Differential Revision: https://phabricator.services.mozilla.com/D119595 --HG-- extra : amend_source : ef03e9baa305539684da74e34296e64ad9f39bf1
This commit is contained in:
Родитель
03830341a1
Коммит
c1c11fbd23
|
@ -0,0 +1,208 @@
|
||||||
|
/* 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 = ["NntpClient"];
|
||||||
|
|
||||||
|
var { CommonUtils } = ChromeUtils.import("resource://services-common/utils.js");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A structure to represent a response received from the server. A response can
|
||||||
|
* be a single status line of a multi-line data block.
|
||||||
|
* @typedef {Object} NntpResponse
|
||||||
|
* @property {number} status - The status code of the response.
|
||||||
|
* @property {string} statusText - The status line of the response excluding the
|
||||||
|
* status code.
|
||||||
|
* @property {string} data - The part of a multi-line data block excluding the
|
||||||
|
* status line.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class to interact with NNTP server.
|
||||||
|
*/
|
||||||
|
class NntpClient {
|
||||||
|
/**
|
||||||
|
* @param {nsINntpIncomingServer} server - The associated server instance.
|
||||||
|
* @param {string} uri - The server uri.
|
||||||
|
*/
|
||||||
|
constructor(server, uri) {
|
||||||
|
this.onOpen = () => {};
|
||||||
|
this.onError = () => {};
|
||||||
|
|
||||||
|
this._server = server;
|
||||||
|
let matches = /.+:\/\/(.+)\/(.+)/.exec(uri);
|
||||||
|
this._host = matches[1];
|
||||||
|
this._group = matches[2];
|
||||||
|
|
||||||
|
this._logger = console.createInstance({
|
||||||
|
prefix: "mailnews.nntp",
|
||||||
|
maxLogLevel: "Warn",
|
||||||
|
maxLogLevelPref: "mailnews.nntp.loglevel",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate a connection to the server
|
||||||
|
*/
|
||||||
|
connect() {
|
||||||
|
let port = this._server.port;
|
||||||
|
let useSecureTransport = this._server.isSecure;
|
||||||
|
this._logger.debug(
|
||||||
|
`Connecting to ${useSecureTransport ? "snews" : "news"}://${
|
||||||
|
this._host
|
||||||
|
}:${port}`
|
||||||
|
);
|
||||||
|
this._socket = new TCPSocket(this._host, port, {
|
||||||
|
binaryType: "arraybuffer",
|
||||||
|
useSecureTransport,
|
||||||
|
});
|
||||||
|
this._socket.onopen = this._onOpen;
|
||||||
|
this._socket.onerror = this._onError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The open event handler.
|
||||||
|
*/
|
||||||
|
_onOpen = () => {
|
||||||
|
this._logger.debug("Connected");
|
||||||
|
this._socket.ondata = this._onData;
|
||||||
|
this._socket.onclose = this._onClose;
|
||||||
|
this.onOpen();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data event handler.
|
||||||
|
* @param {TCPSocketEvent} event - The data event.
|
||||||
|
*/
|
||||||
|
_onData = event => {
|
||||||
|
let stringPayload = CommonUtils.arrayBufferToByteString(
|
||||||
|
new Uint8Array(event.data)
|
||||||
|
);
|
||||||
|
this._logger.debug(`S: ${stringPayload}`);
|
||||||
|
|
||||||
|
let res = this._parse(stringPayload);
|
||||||
|
|
||||||
|
this._nextAction?.(res);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the server response.
|
||||||
|
* @param {string} str - Response received from the server.
|
||||||
|
* @returns {NntpResponse}
|
||||||
|
*/
|
||||||
|
_parse(str) {
|
||||||
|
let matches = /^(\d{3}) (.+)\r\n([^]*)/.exec(str);
|
||||||
|
if (matches) {
|
||||||
|
let [, status, statusText, data] = matches;
|
||||||
|
return { status: Number(status), statusText, data };
|
||||||
|
}
|
||||||
|
return { data: str };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a string to the socket.
|
||||||
|
* @param {string} str - The string to send.
|
||||||
|
*/
|
||||||
|
_sendString(str) {
|
||||||
|
this._logger.debug(`C: ${str}`);
|
||||||
|
this._socket.send(CommonUtils.byteStringToArrayBuffer(str + "\r\n").buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get new articles.
|
||||||
|
* @param {boolean} getOld - Get old articles as well.
|
||||||
|
* @param {nsIUrlListener} urlListener - Callback for the request.
|
||||||
|
* @param {nsIMsgWindow} msgWindow - The associated msg window.
|
||||||
|
*/
|
||||||
|
getNewNews(getOld, urlListener, msgWindow) {
|
||||||
|
this._nextAction = this._actionModeReader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send `MODE READER` request to the server.
|
||||||
|
*/
|
||||||
|
_actionModeReader() {
|
||||||
|
this._sendString("MODE READER");
|
||||||
|
this._nextAction = this._actionGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send `GROUP` request to the server.
|
||||||
|
*/
|
||||||
|
_actionGroup() {
|
||||||
|
this._sendString(`GROUP ${this._group}`);
|
||||||
|
this._nextAction = this._actionXOver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send `XOVER` request to the server.
|
||||||
|
*/
|
||||||
|
_actionXOver(res) {
|
||||||
|
let [, , high] = res.statusText.split(" ");
|
||||||
|
// TODO figure out the range, which depends on the folder state and user
|
||||||
|
// settings.
|
||||||
|
this._sendString(`XOVER ${high - 10}-${high}`);
|
||||||
|
this._nextAction = this._actionXOverResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A transient action to consume the status line of XOVER response.
|
||||||
|
* @param {NntpResponse} res - XOVER response received from the server.
|
||||||
|
*/
|
||||||
|
_actionXOverResponse(res) {
|
||||||
|
this._actionReadXOver(res);
|
||||||
|
this._nextAction = this._actionReadXOver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle XOVER response.
|
||||||
|
* @param {NntpResponse} res - XOVER response received from the server.
|
||||||
|
*/
|
||||||
|
_actionReadXOver({ data }) {
|
||||||
|
// XOVER response can span multiple ondata events, an event's leftover data
|
||||||
|
// is saved in this._xoverData.
|
||||||
|
if (this._xoverData) {
|
||||||
|
data = this._xoverData + data;
|
||||||
|
this._xoverData = null;
|
||||||
|
}
|
||||||
|
while (data) {
|
||||||
|
let index = data.indexOf("\r\n");
|
||||||
|
if (index == -1) {
|
||||||
|
// Not enough data, save it for the next round.
|
||||||
|
this._xoverData = data;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (data == ".\r\n") {
|
||||||
|
// Finished reading XOVER response.
|
||||||
|
this._nextAction = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let parts = data.slice(0, index).split("\t");
|
||||||
|
if (parts.length >= 8) {
|
||||||
|
let [
|
||||||
|
articleNumber,
|
||||||
|
subject,
|
||||||
|
from,
|
||||||
|
date,
|
||||||
|
messageId,
|
||||||
|
references,
|
||||||
|
bytes,
|
||||||
|
lines,
|
||||||
|
...extra
|
||||||
|
] = parts;
|
||||||
|
this._logger.debug({
|
||||||
|
articleNumber,
|
||||||
|
subject,
|
||||||
|
from,
|
||||||
|
date,
|
||||||
|
messageId,
|
||||||
|
references,
|
||||||
|
bytes,
|
||||||
|
lines,
|
||||||
|
extra,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
data = data.slice(index + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,14 @@
|
||||||
|
|
||||||
const EXPORTED_SYMBOLS = ["NntpService"];
|
const EXPORTED_SYMBOLS = ["NntpService"];
|
||||||
|
|
||||||
|
const { XPCOMUtils } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/XPCOMUtils.jsm"
|
||||||
|
);
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||||
|
NntpClient: "resource:///modules/NntpClient.jsm",
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the mailnews.nntp.jsmodule pref to true to use this module.
|
* Set the mailnews.nntp.jsmodule pref to true to use this module.
|
||||||
*
|
*
|
||||||
|
@ -12,7 +20,14 @@ const EXPORTED_SYMBOLS = ["NntpService"];
|
||||||
class NntpService {
|
class NntpService {
|
||||||
QueryInterface = ChromeUtils.generateQI(["nsINntpService"]);
|
QueryInterface = ChromeUtils.generateQI(["nsINntpService"]);
|
||||||
|
|
||||||
getNewNews(server, uri, getOld, urlListener, msgWindow) {}
|
getNewNews(server, uri, getOld, urlListener, msgWindow) {
|
||||||
|
let client = new NntpClient(server, uri);
|
||||||
|
client.connect();
|
||||||
|
|
||||||
|
client.onOpen = () => {
|
||||||
|
client.getNewNews(getOld, urlListener, msgWindow);
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NntpService.prototype.classID = Components.ID(
|
NntpService.prototype.classID = Components.ID(
|
||||||
|
|
|
@ -20,6 +20,7 @@ SOURCES += [
|
||||||
|
|
||||||
EXTRA_JS_MODULES += [
|
EXTRA_JS_MODULES += [
|
||||||
"NewsAutoCompleteSearch.jsm",
|
"NewsAutoCompleteSearch.jsm",
|
||||||
|
"NntpClient.jsm",
|
||||||
"NntpModuleLoader.jsm",
|
"NntpModuleLoader.jsm",
|
||||||
"NntpService.jsm",
|
"NntpService.jsm",
|
||||||
]
|
]
|
||||||
|
|
Загрузка…
Ссылка в новой задаче