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 { 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.
|
||||
*
|
||||
|
@ -12,7 +20,14 @@ const EXPORTED_SYMBOLS = ["NntpService"];
|
|||
class NntpService {
|
||||
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(
|
||||
|
|
|
@ -20,6 +20,7 @@ SOURCES += [
|
|||
|
||||
EXTRA_JS_MODULES += [
|
||||
"NewsAutoCompleteSearch.jsm",
|
||||
"NntpClient.jsm",
|
||||
"NntpModuleLoader.jsm",
|
||||
"NntpService.jsm",
|
||||
]
|
||||
|
|
Загрузка…
Ссылка в новой задаче