diff options
author | Emery Hemingway <git@spam.works> | 2017-03-07 11:42:37 -0600 |
---|---|---|
committer | Emery Hemingway <git@spam.works> | 2017-03-07 12:10:18 -0600 |
commit | fecad72e02256c947e1c16cd003ceca62a3633e5 (patch) | |
tree | 3918930d4c8fd68835e8e2924d478096f806bf2f /lib | |
parent | 7dc8dcb581a2de06472c35868504c7aafe69ca81 (diff) | |
download | Nim-fecad72e02256c947e1c16cd003ceca62a3633e5.tar.gz |
SMTP sync/async deduplication
Deduplicate synchronous and asynchronous code with the multisync pragma. Pass address and port at connect rather than ``new(Async)Smtp``.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/pure/smtp.nim | 172 |
1 files changed, 58 insertions, 114 deletions
diff --git a/lib/pure/smtp.nim b/lib/pure/smtp.nim index 87865c005..08e6c8112 100644 --- a/lib/pure/smtp.nim +++ b/lib/pure/smtp.nim @@ -20,7 +20,8 @@ ## var msg = createMessage("Hello from Nim's SMTP", ## "Hello!.\n Is this awesome or what?", ## @["foo@gmail.com"]) -## var smtpConn = connect("smtp.gmail.com", Port 465, true, true) +## let smtpConn = newSmtp(useSsl = true, debug=true) +## smtpConn.connect("smtp.gmail.com", Port 465) ## smtpConn.auth("username", "password") ## smtpConn.sendmail("username@gmail.com", @["foo@gmail.com"], $msg) ## @@ -34,10 +35,6 @@ import asyncnet, asyncdispatch export Port type - Smtp* = object - sock: Socket - debug: bool - Message* = object msgTo: seq[string] msgCc: seq[string] @@ -47,37 +44,29 @@ type ReplyError* = object of IOError - AsyncSmtp* = ref object - sock: AsyncSocket - address: string - port: Port - useSsl: bool + SmtpBase[SocketType] = ref object + sock: SocketType debug: bool + Smtp* = SmtpBase[Socket] + AsyncSmtp* = SmtpBase[AsyncSocket] + {.deprecated: [EInvalidReply: ReplyError, TMessage: Message, TSMTP: Smtp].} -proc debugSend(smtp: Smtp, cmd: string) = +proc debugSend(smtp: Smtp | AsyncSmtp, cmd: string) {.multisync.} = if smtp.debug: echo("C:" & cmd) - smtp.sock.send(cmd) - -proc debugRecv(smtp: var Smtp): TaintedString = - var line = TaintedString"" - smtp.sock.readLine(line) + await smtp.sock.send(cmd) +proc debugRecv(smtp: Smtp | AsyncSmtp): Future[TaintedString] {.multisync.} = + result = await smtp.sock.recvLine() if smtp.debug: - echo("S:" & line.string) - return line + echo("S:" & result.string) proc quitExcpt(smtp: Smtp, msg: string) = smtp.debugSend("QUIT") raise newException(ReplyError, msg) -proc checkReply(smtp: var Smtp, reply: string) = - var line = smtp.debugRecv() - if not line.string.startswith(reply): - quitExcpt(smtp, "Expected " & reply & " reply, got: " & line.string) - const compiledWithSsl = defined(ssl) when not defined(ssl): @@ -86,63 +75,6 @@ when not defined(ssl): else: let defaultSSLContext = newContext(verifyMode = CVerifyNone) -proc connect*(address: string, port = Port(25), - ssl = false, debug = false, - sslContext = defaultSSLContext): Smtp = - ## Establishes a connection with a SMTP server. - ## May fail with ReplyError or with a socket error. - result.sock = newSocket() - if ssl: - when compiledWithSsl: - sslContext.wrapSocket(result.sock) - else: - raise newException(ESystem, - "SMTP module compiled without SSL support") - result.sock.connect(address, port) - result.debug = debug - - result.checkReply("220") - result.debugSend("HELO " & address & "\c\L") - result.checkReply("250") - -proc auth*(smtp: var Smtp, username, password: string) = - ## Sends an AUTH command to the server to login as the `username` - ## using `password`. - ## May fail with ReplyError. - - smtp.debugSend("AUTH LOGIN\c\L") - smtp.checkReply("334") # TODO: Check whether it's asking for the "Username:" - # i.e "334 VXNlcm5hbWU6" - smtp.debugSend(encode(username) & "\c\L") - smtp.checkReply("334") # TODO: Same as above, only "Password:" (I think?) - - smtp.debugSend(encode(password) & "\c\L") - smtp.checkReply("235") # Check whether the authentification was successful. - -proc sendmail*(smtp: var Smtp, fromaddr: string, - toaddrs: seq[string], msg: string) = - ## Sends `msg` from `fromaddr` to `toaddr`. - ## Messages may be formed using ``createMessage`` by converting the - ## Message into a string. - - smtp.debugSend("MAIL FROM:<" & fromaddr & ">\c\L") - smtp.checkReply("250") - for address in items(toaddrs): - smtp.debugSend("RCPT TO:<" & address & ">\c\L") - smtp.checkReply("250") - - # Send the message - smtp.debugSend("DATA " & "\c\L") - smtp.checkReply("354") - smtp.debugSend(msg & "\c\L") - smtp.debugSend(".\c\L") - smtp.checkReply("250") - -proc close*(smtp: Smtp) = - ## Disconnects from the SMTP server and closes the socket. - smtp.debugSend("QUIT\c\L") - smtp.sock.close() - proc createMessage*(mSubject, mBody: string, mTo, mCc: seq[string], otherHeaders: openarray[tuple[name, value: string]]): Message = ## Creates a new MIME compliant message. @@ -178,81 +110,94 @@ proc `$`*(msg: Message): string = result.add("\c\L") result.add(msg.msgBody) -proc newAsyncSmtp*(address: string, port: Port, useSsl = false, +proc newSmtp*(useSsl = false, debug=false, + sslContext = defaultSslContext): Smtp = + ## Creates a new ``Smtp`` instance. + new result + result.debug = debug + + result.sock = newSocket() + if useSsl: + when compiledWithSsl: + sslContext.wrapSocket(result.sock) + else: + raise newException(SystemError, + "SMTP module compiled without SSL support") + +proc newAsyncSmtp*(useSsl = false, debug=false, sslContext = defaultSslContext): AsyncSmtp = ## Creates a new ``AsyncSmtp`` instance. new result - result.address = address - result.port = port - result.useSsl = useSsl + result.debug = debug result.sock = newAsyncSocket() if useSsl: when compiledWithSsl: sslContext.wrapSocket(result.sock) else: - raise newException(ESystem, + raise newException(SystemError, "SMTP module compiled without SSL support") proc quitExcpt(smtp: AsyncSmtp, msg: string): Future[void] = var retFuture = newFuture[void]() - var sendFut = smtp.sock.send("QUIT") + var sendFut = smtp.debugSend("QUIT") sendFut.callback = proc () = # TODO: Fix this in async procs. raise newException(ReplyError, msg) return retFuture -proc checkReply(smtp: AsyncSmtp, reply: string) {.async.} = - var line = await smtp.sock.recvLine() - if not line.string.startswith(reply): - await quitExcpt(smtp, "Expected " & reply & " reply, got: " & line.string) +proc checkReply(smtp: Smtp | AsyncSmtp, reply: string) {.multisync.} = + var line = await smtp.debugRecv() + if not line.startswith(reply): + await quitExcpt(smtp, "Expected " & reply & " reply, got: " & line) -proc connect*(smtp: AsyncSmtp) {.async.} = +proc connect*(smtp: Smtp | AsyncSmtp, + address: string, port: Port) {.multisync.} = ## Establishes a connection with a SMTP server. ## May fail with ReplyError or with a socket error. - await smtp.sock.connect(smtp.address, smtp.port) + await smtp.sock.connect(address, port) await smtp.checkReply("220") - await smtp.sock.send("HELO " & smtp.address & "\c\L") + await smtp.debugSend("HELO " & address & "\c\L") await smtp.checkReply("250") -proc auth*(smtp: AsyncSmtp, username, password: string) {.async.} = +proc auth*(smtp: Smtp | AsyncSmtp, username, password: string) {.multisync.} = ## Sends an AUTH command to the server to login as the `username` ## using `password`. ## May fail with ReplyError. - await smtp.sock.send("AUTH LOGIN\c\L") + await smtp.debugSend("AUTH LOGIN\c\L") await smtp.checkReply("334") # TODO: Check whether it's asking for the "Username:" # i.e "334 VXNlcm5hbWU6" - await smtp.sock.send(encode(username) & "\c\L") + await smtp.debugSend(encode(username) & "\c\L") await smtp.checkReply("334") # TODO: Same as above, only "Password:" (I think?) - await smtp.sock.send(encode(password) & "\c\L") + await smtp.debugSend(encode(password) & "\c\L") await smtp.checkReply("235") # Check whether the authentification was successful. -proc sendMail*(smtp: AsyncSmtp, fromAddr: string, - toAddrs: seq[string], msg: string) {.async.} = +proc sendMail*(smtp: Smtp | AsyncSmtp, fromAddr: string, + toAddrs: seq[string], msg: string) {.multisync.} = ## Sends ``msg`` from ``fromAddr`` to the addresses specified in ``toAddrs``. ## Messages may be formed using ``createMessage`` by converting the ## Message into a string. - await smtp.sock.send("MAIL FROM:<" & fromAddr & ">\c\L") + await smtp.debugSend("MAIL FROM:<" & fromAddr & ">\c\L") await smtp.checkReply("250") for address in items(toAddrs): - await smtp.sock.send("RCPT TO:<" & address & ">\c\L") + await smtp.debugSend("RCPT TO:<" & address & ">\c\L") await smtp.checkReply("250") # Send the message - await smtp.sock.send("DATA " & "\c\L") + await smtp.debugSend("DATA " & "\c\L") await smtp.checkReply("354") await smtp.sock.send(msg & "\c\L") - await smtp.sock.send(".\c\L") + await smtp.debugSend(".\c\L") await smtp.checkReply("250") -proc close*(smtp: AsyncSmtp) {.async.} = +proc close*(smtp: Smtp | AsyncSmtp) {.multisync.} = ## Disconnects from the SMTP server and closes the socket. - await smtp.sock.send("QUIT\c\L") + await smtp.debugSend("QUIT\c\L") smtp.sock.close() when not defined(testing) and isMainModule: @@ -278,25 +223,24 @@ when not defined(testing) and isMainModule: proc async_test() {.async.} = let client = newAsyncSmtp( - conf["smtphost"], - conf["port"].parseInt.Port, - conf["use_tls"].parseBool + conf["use_tls"].parseBool, + debug=true ) - await client.connect() + await client.connect(conf["smtphost"], conf["port"].parseInt.Port) await client.auth(conf["username"], conf["password"]) await client.sendMail(conf["sender"], @[conf["recipient"]], $msg) await client.close() echo "async email sent" proc sync_test() = - var smtpConn = connect( - conf["smtphost"], - conf["port"].parseInt.Port, + var smtpConn = newSmtp( conf["use_tls"].parseBool, - true, # debug + debug=true ) + smtpConn.connect(conf["smtphost"], conf["port"].parseInt.Port) smtpConn.auth(conf["username"], conf["password"]) - smtpConn.sendmail(conf["sender"], @[conf["recipient"]], $msg) + smtpConn.sendMail(conf["sender"], @[conf["recipient"]], $msg) + smtpConn.close() echo "sync email sent" waitFor async_test() |