Skip to content

Commit

Permalink
Implement HDR, OVER, LAST, NEXT, NEWNEWS commands
Browse files Browse the repository at this point in the history
  • Loading branch information
rlidwka committed Apr 25, 2017
1 parent 4c4648f commit dbee980
Show file tree
Hide file tree
Showing 22 changed files with 897 additions and 74 deletions.
44 changes: 37 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
// TODO
// - STARTTLS
// - newnews
// - xhdr
// - xover
//
// - Session.write(stream) + general rework
// - Optimize Range headers get.

Expand Down Expand Up @@ -129,7 +125,7 @@ Nntp.prototype._authenticate = function (/*session*/) {
*
* - message_id: number-like string or '<message_identifier>'
*/
Nntp.prototype._getMessage = function (/*session, message_id*/) {
Nntp.prototype._getArticle = function (/*session, message_id*/) {
return Promise.resolve(null);
};

Expand Down Expand Up @@ -175,10 +171,44 @@ Nntp.prototype._buildHead = function (/*session, message*/) {
Nntp.prototype._buildBody = function (/*session, message*/) {
};


/*
* Generate header content
*
* NNTP server user may request any field using HDR command,
* and in addition to that the following fields are used internally by
* nntp-server:
*
* - subject
* - from
* - date
* - message-id
* - references
* - :bytes
* - :lines
*/
Nntp.prototype._buildHeaderField = function (/*session, message, field*/) {
};


/*
* Get list of messages newer than specified timestamp
* in NNTP groups selected by a wildcard.
*
* - time - minimal last update time
* - wildmat - name filter
*/
Nntp.prototype._getNewNews = function (/*session, time, wildmat*/) {
};


/*
* Generate message id - '<xxxxx@xxxx.xxx>'
* Get previous/next article from selected group
*
* - index - currently selected article number
* - forward - direction flag (`true` - next article, `false` - previous)
*/
Nntp.prototype._buildId = function (/*session, message*/) {
Nntp.prototype._getNext = function (/*session, index, forward*/) {
};


Expand Down
19 changes: 14 additions & 5 deletions lib/commands/article.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,34 @@ module.exports = {

run(session, cmd) {
let match = cmd.match(CMD_RE);
let id;

// Requests without id not implemented (no need, not used by clients)
if (!match[1]) return status._501_SYNTAX_ERROR;
if (!match[1]) {
let cursor = session.current_article;

if (cursor < 0) return status._420_ARTICLE_NOT_SLCTD;

id = cursor.toString();
} else {
id = match[2];
}

let id = match[2];
let by_identifier = id[0] === '<';

if (!by_identifier && !session.group.name) {
return status._412_GRP_NOT_SLCTD;
}

return session.server._getMessage(session, id)
return session.server._getArticle(session, id)
.then(msg => {
if (!msg) {
if (by_identifier) return status._430_NO_ARTICLE_BY_ID;
return status._423_NO_ARTICLE_BY_NUM;
}

let msg_id = session.server._buildId(session, msg);
if (!by_identifier) session.current_article = msg.index;

let msg_id = session.server._buildHeaderField(session, msg, 'message-id');
let msg_index = by_identifier ? 0 : id;

return [
Expand Down
19 changes: 14 additions & 5 deletions lib/commands/body.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,34 @@ module.exports = {

run(session, cmd) {
let match = cmd.match(CMD_RE);
let id;

// Requests without id not implemented (no need, not used by clients)
if (!match[1]) return status._501_SYNTAX_ERROR;
if (!match[1]) {
let cursor = session.current_article;

if (cursor < 0) return status._420_ARTICLE_NOT_SLCTD;

id = cursor.toString();
} else {
id = match[2];
}

let id = match[2];
let by_identifier = id[0] === '<';

if (!by_identifier && !session.group.name) {
return status._412_GRP_NOT_SLCTD;
}

return session.server._getMessage(session, id)
return session.server._getArticle(session, id)
.then(msg => {
if (!msg) {
if (by_identifier) return status._430_NO_ARTICLE_BY_ID;
return status._423_NO_ARTICLE_BY_NUM;
}

let msg_id = session.server._buildId(session, msg);
if (!by_identifier) session.current_article = msg.index;

let msg_id = session.server._buildHeaderField(session, msg, 'message-id');
let msg_index = by_identifier ? 0 : id;

return [
Expand Down
15 changes: 7 additions & 8 deletions lib/commands/capabilities.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// https://tools.ietf.org/html/rfc3977#section-5.2
//
'use strict';


const status = require('../status');
const CRLF = '\r\n';


module.exports = {
Expand Down Expand Up @@ -38,12 +38,11 @@ module.exports = {
}
});

return [
status._101_CAPABILITY_LIST,
Object.keys(uniq)
.map(k => [ k ].concat(uniq[k]).join(' '))
.join(CRLF),
'.'
];
return [ status._101_CAPABILITY_LIST ]
.concat(
Object.keys(uniq)
.map(k => [ k ].concat(uniq[k]).join(' '))
)
.concat('.');
}
};
18 changes: 11 additions & 7 deletions lib/commands/group.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,20 @@ module.exports = {
head: 'GROUP',
validate: CMD_RE,

run(session, cmd) {
async run(session, cmd) {
let name = cmd.match(CMD_RE)[1];

return session.server._selectGroup(session, name)
.then(ok => {
if (!ok) return status._411_GRP_NOT_FOUND;
let ok = await session.server._selectGroup(session, name);

let g = session.group;
if (!ok) return status._411_GRP_NOT_FOUND;

return `${status._211_GRP_SELECTED} ${g.total} ${g.min_index} ${g.max_index} ${name}`;
});
let g = session.group;

// reset cursor
let first_msg = await session.server._getNext(session, g.min_index - 1, true);

session.current_article = first_msg ? first_msg.index : -1;

return `${status._211_GRP_SELECTED} ${g.total} ${g.min_index} ${g.max_index} ${name}`;
}
};
75 changes: 75 additions & 0 deletions lib/commands/hdr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// https://tools.ietf.org/html/rfc3977#section-8.5
//
'use strict';


const status = require('../status');


const CMD_RE = /^X?HDR ([^\s]+)(?: (?:(\d{1,15})(-(\d{1,15})?)?|(<[^\s<>]+>))?)?$/i;

module.exports = {
head: 'HDR',
validate: CMD_RE,

async run(session, cmd) {
let [ , field, first, dash, last, message_id ] = cmd.match(CMD_RE);

field = field.toLowerCase();

let result = [ status._225_HEADERS_FOLLOW ];
let list;

if (typeof message_id !== 'undefined') {
let msg = await session.server._getArticle(session, message_id);

if (!msg) return status._430_NO_ARTICLE_BY_ID;

list = [ msg ];

} else if (typeof first !== 'undefined') {
first = +first;

if (!dash) {
last = first;
} else {
last = typeof last === 'undefined' ? session.group.max_index : +last;
}

if (!session.group.name) return status._412_GRP_NOT_SLCTD;

list = await session.server._getRange(session, first, last);

// TODO: status text is slightly wrong
if (!list.length) return status._423_NO_ARTICLE_BY_NUM;

} else {
let msg = await session.server._getArticle(session, String(session.current_article));

if (!msg) return status._420_ARTICLE_NOT_SLCTD;

list = [ msg ];
}

result = result.concat(list.map(msg => {
let index;

if (typeof message_id !== 'undefined') {
index = '0';
} else {
index = msg.index.toString();
}

let content = (session.server._buildHeaderField(session, msg, field) || '');

// unfolding + replacing invalid characters, see RFC 3977 section 8.3.2
content = content.replace(/\r?\n/g, '').replace(/[\0\t\r\n]/g, ' ');

return index + ' ' + content;
}));

result.push('.');

return result;
}
};
19 changes: 14 additions & 5 deletions lib/commands/head.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,34 @@ module.exports = {

run(session, cmd) {
let match = cmd.match(CMD_RE);
let id;

// Requests without id not implemented (no need, not used by clients)
if (!match[1]) return status._501_SYNTAX_ERROR;
if (!match[1]) {
let cursor = session.current_article;

if (cursor < 0) return status._420_ARTICLE_NOT_SLCTD;

id = cursor.toString();
} else {
id = match[2];
}

let id = match[2];
let by_identifier = id[0] === '<';

if (!by_identifier && !session.group.name) {
return status._412_GRP_NOT_SLCTD;
}

return session.server._getMessage(session, id)
return session.server._getArticle(session, id)
.then(msg => {
if (!msg) {
if (by_identifier) return status._430_NO_ARTICLE_BY_ID;
return status._423_NO_ARTICLE_BY_NUM;
}

let msg_id = session.server._buildId(session, msg);
if (!by_identifier) session.current_article = msg.index;

let msg_id = session.server._buildHeaderField(session, msg, 'message-id');
let msg_index = by_identifier ? 0 : id;

return [
Expand Down
15 changes: 10 additions & 5 deletions lib/commands/help.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@ module.exports = {
validate: /^HELP$/i,
pipeline: true,

run() {
return [
status._100_HELP_FOLLOWS,
'.'
];
run(session/*, cmd*/) {
// Collect list of all commands
//
// TODO: need a description/syntax for each command here
//
let commands = Object.keys(session.server.options.commands);

return [ status._100_HELP_FOLLOWS ]
.concat(commands.map(cmd => ' ' + cmd))
.concat('.');
}
};
41 changes: 41 additions & 0 deletions lib/commands/last.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// https://tools.ietf.org/html/rfc3977#section-6.1.3
//
'use strict';


const status = require('../status');


const CMD_RE = /^LAST$/i;


module.exports = {
head: 'LAST',
validate: CMD_RE,
pipeline: true,

run(session/*, cmd*/) {
if (!session.group.name) {
return status._412_GRP_NOT_SLCTD;
}

let cursor = session.current_article;

if (cursor < 0) {
return status._420_ARTICLE_NOT_SLCTD;
}

return session.server._getNext(session, cursor, false)
.then(msg => {
if (!msg) {
return status._422_NO_LAST_ARTICLE;
}

session.current_article = msg.index;

let msg_id = session.server._buildHeaderField(session, msg, 'message-id');

return `${status._223_ARTICLE_EXISTS} ${msg.index} ${msg_id}`;
});
}
};
Loading

0 comments on commit dbee980

Please sign in to comment.