1.0.2 • Published 6 years ago

lineparse v1.0.2

Weekly downloads
-
License
ISC
Repository
-
Last release
6 years ago

lineparse

This is a small line parser module for NodeJS.

API

Load the module:

const LineParse = require("lineparse");

Create an instance with a stream.Readable:

const lp = new LineParse(process.stdin);
lp.on("end", () => console.log("got EOF"));
lp.on("line", line => console.log("got line:", line));

Restart with a new input stream, e.g. after STARTTLS:

lp.restart(tlssocket);

Example

This example connects to a IMAP server, reads responses, and switches to TLS.

Environment variables IMAP_HOST, USER and PASSWORD should contain the hostname, username and password.

const LineParse = require("lineparse");

const net = require("net");
const tls = require("tls");
const crypto = require("crypto");

let socket = net.createConnection(143, process.env.IMAP_HOST);
socket.on("error", err => console.log("tcp: error:", err));
socket.on("close", () => console.log("tcp: close"));

let lp = new LineParse(socket);
lp.on("end", () => console.log("line: close"));
lp.on("line", input);

let state = 0;

function input(line)
{
	console.log(">", line);
	if (state == 0) {
		if (!line.startsWith("* OK "))
			fatal("greeting failed");
		console.log("< A1 STARTTLS");
		socket.write("A1 STARTTLS\r\n");
		state = 1;
		return;
	}
	if (state == 1) {
		if (!line.startsWith("A1 OK "))
			fatal("starttls failed");
		socket = tls.connect({socket: socket, rejectUnauthorized: false});
		socket.on("error", err => console.log("tls: error:", err));
		socket.on("close", () => console.log("tls: close"));
		socket.on("secureConnect", () => {
			console.log("< A2 AUTHENTICATE CRAM-MD5");
			socket.write("A2 AUTHENTICATE CRAM-MD5\r\n");
			state = 3;
		});
		lp.restart(socket);
		state = 2;
		return;
	}
	if (state == 3) {
		if (line.startsWith("+ ")) {
			line = line.substring(2);
			const rc = cramMD5(line);
			console.log(`< ${rc}`);
			socket.write(`${rc}\r\n`);
			return;
		}
		if (!line.startsWith("A2 OK "))
			fatal("authenticate failed");
		console.log("< A3 SELECT INBOX");
		socket.write("A3 SELECT INBOX\r\n");
		state = 4;
		return;
	}
	if (state == 4) {
		if (line.startsWith("* "))
			return;
		if (!line.startsWith("A3 OK "))
			fatal("select failed");
		console.log("< A4 LOGOUT");
		socket.write("A4 LOGOUT\r\n");
		state = 5;
		return;
	}
	if (state == 5) {
		if (line.startsWith("* "))
			return;
		if (!line.startsWith("A4 OK "))
			fatal("logout failed");
		state = 6;
		return;
	}
	if (state == 6)
		fatal("unexpected input");
}

function fatal(...args)
{
	console.error.apply(null, args);
	console.error("terminating");
	process.exit(1);
}

function cramMD5(challenge)
{
	const hmac = crypto.createHmac("md5", process.env.PASSWORD);
	hmac.update(Buffer.from(challenge, "base64").toString("utf8"));
	return(Buffer.from(process.env.USER + " " + hmac.digest().toString("hex")).toString("base64"));
}
1.0.2

6 years ago

1.0.1

6 years ago

1.0.0

6 years ago