smtp.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  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. this.output("220 smtp2rss");
  50. for await (const chunk of newlineIterator(iterateReader(this.conn))) {
  51. this.input(chunk);
  52. }
  53. }
  54. input(data: string) {
  55. switch (this.state) {
  56. case State.WaitingHello:
  57. return this.waitingHello(data);
  58. case State.WaitingMail:
  59. return this.waitingMail(data);
  60. case State.WaitingRecipient:
  61. return this.waitingRecipient(data);
  62. case State.WaitingData:
  63. return this.waitingData(data);
  64. case State.ReceivingData:
  65. return this.receivingData(data);
  66. }
  67. }
  68. waitingHello(data: string) {
  69. const command = data.substring(0, 4);
  70. if (command === "HELO" || command === "EHLO") {
  71. this.state = State.WaitingMail;
  72. this.output("250 OK");
  73. } else if (command === "QUIT") {
  74. this.output("221 smtp2rss closing");
  75. this.conn.close();
  76. } else {
  77. this.output("500 Invalid state");
  78. }
  79. }
  80. waitingMail(data: string) {
  81. const command = data.substring(0, 4);
  82. if (command === "MAIL") {
  83. const match = data.match(/^MAIL FROM:<([^>]*)>.*$/);
  84. if (match !== null) {
  85. this.sender = match[1];
  86. this.state = State.WaitingRecipient;
  87. this.output("250 OK");
  88. } else {
  89. this.output("500 Invalid sender");
  90. }
  91. } else {
  92. this.output("500 Invalid state");
  93. }
  94. }
  95. waitingRecipient(data: string) {
  96. const command = data.substring(0, 4);
  97. if (command === "RCPT") {
  98. const match = data.match(/^RCPT TO:<([^>]*)>.*$/);
  99. if (match !== null) {
  100. this.recipient = match[1];
  101. this.state = State.WaitingData;
  102. this.output("250 OK");
  103. } else {
  104. this.output("500 Invalid sender");
  105. }
  106. } else {
  107. this.output("500 Invalid state");
  108. }
  109. }
  110. waitingData(data: string) {
  111. const command = data.substring(0, 4);
  112. if (command === "DATA") {
  113. this.state = State.ReceivingData;
  114. this.output("354 Waiting for data");
  115. } else {
  116. this.output("500 Invalid state");
  117. }
  118. }
  119. receivingData(data: string) {
  120. if (data === ".") {
  121. this.state = State.WaitingMail;
  122. this.output("250 OK");
  123. this.onReceive(this.recipient, this.sender, this.data);
  124. this.recipient = "";
  125. this.sender = "";
  126. this.data = "";
  127. } else {
  128. this.data += data;
  129. }
  130. }
  131. output(response: string) {
  132. this.conn.write(this.encoder.encode(`${response}\r\n`));
  133. }
  134. }
  135. export async function handleSmtp(conn: Deno.Conn, onReceive: OnReceive) {
  136. const session = new Session(conn, onReceive);
  137. await session.start();
  138. }