123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- import { iterateReader } from "https://deno.land/std@0.133.0/streams/conversion.ts";
- async function* newlineIterator(iterator: AsyncIterableIterator<Uint8Array>) {
- const decoder = new TextDecoder();
- let buffer = "";
- for await (const chunk of iterator) {
- let chunkString = decoder.decode(chunk);
- const newlinePos = chunkString.indexOf("\r\n");
- if (newlinePos >= 0) {
- const out = buffer + chunkString.substring(0, newlinePos);
- buffer = chunkString.substring(newlinePos + 2);
- yield out;
- } else {
- buffer += chunkString;
- }
- let pos: number;
- while ((pos = buffer.indexOf("\r\n")) >= 0) {
- const out = buffer.substring(0, pos);
- buffer = buffer.substring(pos + 2);
- yield out;
- }
- }
- }
- enum State {
- WaitingHello,
- WaitingMail,
- WaitingRecipient,
- WaitingData,
- ReceivingData,
- }
- export type OnReceive = (
- recipient: string,
- sender: string,
- data: string,
- ) => void;
- export class Session {
- private state: State;
- private encoder: TextEncoder;
- private data: string;
- private recipient: string;
- private sender: string;
- constructor(readonly conn: Deno.Conn, readonly onReceive: OnReceive) {
- this.state = State.WaitingHello;
- this.encoder = new TextEncoder();
- this.data = "";
- this.recipient = "";
- this.sender = "";
- }
- async start() {
- this.output("220 smtp2rss");
- for await (const chunk of newlineIterator(iterateReader(this.conn))) {
- this.input(chunk);
- }
- }
- input(data: string) {
- switch (this.state) {
- case State.WaitingHello:
- return this.waitingHello(data);
- case State.WaitingMail:
- return this.waitingMail(data);
- case State.WaitingRecipient:
- return this.waitingRecipient(data);
- case State.WaitingData:
- return this.waitingData(data);
- case State.ReceivingData:
- return this.receivingData(data);
- }
- }
- waitingHello(data: string) {
- const command = data.substring(0, 4);
- if (command === "HELO" || command === "EHLO") {
- this.state = State.WaitingMail;
- this.output("250 OK");
- } else if (command === "QUIT") {
- this.output("221 smtp2rss closing");
- this.conn.close();
- } else {
- this.output("500 Invalid state");
- }
- }
- waitingMail(data: string) {
- const command = data.substring(0, 4);
- if (command === "MAIL") {
- const match = data.match(/^MAIL FROM:<([^>]*)>.*$/);
- if (match !== null) {
- this.sender = match[1];
- this.state = State.WaitingRecipient;
- this.output("250 OK");
- } else {
- this.output("500 Invalid sender");
- }
- } else {
- this.output("500 Invalid state");
- }
- }
- waitingRecipient(data: string) {
- const command = data.substring(0, 4);
- if (command === "RCPT") {
- const match = data.match(/^RCPT TO:<([^>]*)>.*$/);
- if (match !== null) {
- this.recipient = match[1];
- this.state = State.WaitingData;
- this.output("250 OK");
- } else {
- this.output("500 Invalid sender");
- }
- } else {
- this.output("500 Invalid state");
- }
- }
- waitingData(data: string) {
- const command = data.substring(0, 4);
- if (command === "DATA") {
- this.state = State.ReceivingData;
- this.output("354 Waiting for data");
- } else {
- this.output("500 Invalid state");
- }
- }
- receivingData(data: string) {
- if (data === ".") {
- this.state = State.WaitingMail;
- this.output("250 OK");
- this.onReceive(this.recipient, this.sender, this.data);
- this.recipient = "";
- this.sender = "";
- this.data = "";
- } else {
- this.data += data;
- }
- }
- output(response: string) {
- this.conn.write(this.encoder.encode(`${response}\r\n`));
- }
- }
- export async function handleSmtp(conn: Deno.Conn, onReceive: OnReceive) {
- const session = new Session(conn, onReceive);
- await session.start();
- }
|