smtp.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import { iterateReader } from "https://deno.land/std@0.133.0/streams/conversion.ts";
  2. async function* newlineIterator(iterator: AsyncIterableIterator<Uint8Array>) {
  3. const decoder = new TextDecoder();
  4. let buffer = "";
  5. for await (const chunk of iterator) {
  6. let chunkString = decoder.decode(chunk);
  7. const newlinePos = chunkString.indexOf("\r\n");
  8. if (newlinePos >= 0) {
  9. const out = buffer + chunkString.substring(0, newlinePos);
  10. buffer = chunkString.substring(newlinePos + 2);
  11. yield out;
  12. } else {
  13. buffer += chunkString;
  14. }
  15. let pos: number;
  16. while ((pos = buffer.indexOf("\r\n")) >= 0) {
  17. const out = buffer.substring(0, pos);
  18. buffer = buffer.substring(pos + 2);
  19. yield out;
  20. }
  21. }
  22. }
  23. enum State {
  24. WaitingHello,
  25. WaitingMail,
  26. WaitingRecipient,
  27. WaitingData,
  28. ReceivingData,
  29. }
  30. export type OnReceive = (
  31. recipient: string,
  32. sender: string,
  33. data: string,
  34. ) => void;
  35. export class Session {
  36. private state: State;
  37. private encoder: TextEncoder;
  38. private data: string;
  39. private recipient: string;
  40. private sender: string;
  41. constructor(readonly conn: Deno.Conn, readonly onReceive: OnReceive) {
  42. this.state = State.WaitingHello;
  43. this.encoder = new TextEncoder();
  44. this.data = "";
  45. this.recipient = "";
  46. this.sender = "";
  47. }
  48. async start() {
  49. await this.output("220 smtp2rss");
  50. for await (const chunk of newlineIterator(iterateReader(this.conn))) {
  51. if (await this.input(chunk)) {
  52. break;
  53. }
  54. }
  55. await this.output("221 smtp2rss closing");
  56. this.conn.close();
  57. }
  58. input(data: string) {
  59. switch (this.state) {
  60. case State.WaitingHello:
  61. return this.waitingHello(data);
  62. case State.WaitingMail:
  63. return this.waitingMail(data);
  64. case State.WaitingRecipient:
  65. return this.waitingRecipient(data);
  66. case State.WaitingData:
  67. return this.waitingData(data);
  68. case State.ReceivingData:
  69. return this.receivingData(data);
  70. }
  71. }
  72. async waitingHello(data: string) {
  73. const command = data.substring(0, 4);
  74. if (command === "HELO" || command === "EHLO") {
  75. this.state = State.WaitingMail;
  76. await this.output("250 OK");
  77. } else {
  78. await this.output("500 Invalid state");
  79. }
  80. }
  81. async waitingMail(data: string) {
  82. const command = data.substring(0, 4);
  83. if (command === "MAIL") {
  84. const match = data.match(/^MAIL FROM:<([^>]*)>.*$/);
  85. if (match !== null) {
  86. this.sender = match[1];
  87. this.state = State.WaitingRecipient;
  88. await this.output("250 OK");
  89. } else {
  90. await this.output("500 Invalid sender");
  91. }
  92. } else if (command === "QUIT") {
  93. return true;
  94. } else {
  95. await this.output("500 Invalid state");
  96. }
  97. }
  98. async waitingRecipient(data: string) {
  99. const command = data.substring(0, 4);
  100. if (command === "RCPT") {
  101. const match = data.match(/^RCPT TO:<([^>]*)>.*$/);
  102. if (match !== null) {
  103. this.recipient = match[1];
  104. this.state = State.WaitingData;
  105. await this.output("250 OK");
  106. } else {
  107. await this.output("500 Invalid sender");
  108. }
  109. } else {
  110. await this.output("500 Invalid state");
  111. }
  112. }
  113. async waitingData(data: string) {
  114. const command = data.substring(0, 4);
  115. if (command === "DATA") {
  116. this.state = State.ReceivingData;
  117. await this.output("354 Waiting for data");
  118. } else {
  119. await this.output("500 Invalid state");
  120. }
  121. }
  122. async receivingData(data: string) {
  123. if (data === ".") {
  124. this.state = State.WaitingMail;
  125. await this.output("250 OK");
  126. this.onReceive(this.recipient, this.sender, this.data);
  127. this.recipient = "";
  128. this.sender = "";
  129. this.data = "";
  130. } else {
  131. this.data += data + "\r\n";
  132. }
  133. }
  134. output(response: string): Promise<number> {
  135. return this.conn.write(this.encoder.encode(`${response}\r\n`));
  136. }
  137. }
  138. export async function handleSmtp(conn: Deno.Conn, onReceive: OnReceive) {
  139. const session = new Session(conn, onReceive);
  140. await session.start();
  141. }