]> gitweb.factorcode.org Git - factor.git/commitdiff
Add support for AUTH PLAIN to smtp library
authorSlava Pestov <slava@slava-pestovs-macbook-pro.local>
Sun, 30 Nov 2008 16:12:08 +0000 (10:12 -0600)
committerSlava Pestov <slava@slava-pestovs-macbook-pro.local>
Sun, 30 Nov 2008 16:12:08 +0000 (10:12 -0600)
Update documentation
The smtp.server used for testing now starts on a random port instead of hard-coding 4321

basis/smtp/server/server.factor
basis/smtp/smtp-docs.factor
basis/smtp/smtp-tests.factor
basis/smtp/smtp.factor

index a6a8bb2ccaa28ced9355e514ce1dbf6c35ce9543..31b67b38a68c0d3770ab7c4b19f17c4bd83b5ca3 100644 (file)
@@ -2,12 +2,12 @@
 ! See http://factorcode.org/license.txt for BSD license.
 USING: combinators kernel prettyprint io io.timeouts
 sequences namespaces io.sockets continuations calendar
-io.encodings.ascii io.streams.duplex destructors ;
+io.encodings.ascii io.streams.duplex destructors
+locals concurrency.promises threads accessors ;
 IN: smtp.server
 
 ! Mock SMTP server for testing purposes.
 
-! Usage: 4321 mock-smtp-server
 ! $ telnet 127.0.0.1 4321
 ! Trying 127.0.0.1...
 ! Connected to localhost.
@@ -62,13 +62,16 @@ SYMBOL: data-mode
         ]
     } cond nip [ process ] when ;
 
-: mock-smtp-server ( port -- )
-    "Starting SMTP server on port " write dup . flush
-    "127.0.0.1" swap <inet4> ascii <server> [
-        accept drop [
-            1 minutes timeouts
-            "220 hello\r\n" write flush
-            process
-            global [ flush ] bind
-        ] with-stream
-    ] with-disposal ;
+:: mock-smtp-server ( promise -- )
+    #! Store the port we are running on in the promise.
+    [
+        "127.0.0.1" 0 <inet4> ascii <server> [
+        dup addr>> port>> promise fulfill
+            accept drop [
+                1 minutes timeouts
+                "220 hello\r\n" write flush
+                process
+                global [ flush ] bind
+            ] with-stream
+        ] with-disposal
+    ] in-thread ;
index c1c2d1c1f8f151a9884c2ec5e673097966c00e51..e8820b1be8d9b55e0f2e37b2e2a57fc953f333fb 100644 (file)
@@ -5,20 +5,49 @@ io.sockets strings calendar ;
 IN: smtp
 
 HELP: smtp-domain
-{ $description "The name of the machine that is sending the email.  This variable will be filled in by the " { $link host-name } " word if not set by the user." } ;
+{ $var-description "The name of the machine that is sending the email.  This variable will be filled in by the " { $link host-name } " word if not set by the user." } ;
 
 HELP: smtp-server
-{ $description "Holds an " { $link inet } " object with the address of an SMTP server." } ;
+{ $var-description "Holds an " { $link inet } " object with the address of an SMTP server." } ;
 
 HELP: smtp-read-timeout
-{ $description "Holds an " { $link duration } " object that specifies how long to wait for a response from the SMTP server." } ;
+{ $var-description "Holds a " { $link duration } " object that specifies how long to wait for a response from the SMTP server." } ;
 
-HELP: esmtp?
-{ $description "Set true by default, determines whether the SMTP client is using the Extended SMTP protocol." } ;
+HELP: smtp-auth
+{ $var-description "Holds either " { $link no-auth } " or an instance of " { $link plain-auth } ", specifying how to authenticate with the SMTP server. Set to " { $link no-auth } " by default." } ;
+
+HELP: no-auth
+{ $class-description "If the " { $link smtp-auth } " variable is set to this value, no authentication will be performed." } ;
+
+HELP: plain-auth
+{ $class-description "If the " { $link smtp-auth } " variable is set to this value, plain authentication will be performed, with the username and password stored in the " { $slot "username" } " and " { $slot "password" } " slots of the tuple sent to the server as plain-text." } ;
+
+HELP: <plain-auth> ( username password -- plain-auth )
+{ $values { "username" string } { "password" string } { "plain-auth" plain-auth } }
+{ $description "Creates a new " { $link plain-auth } " instance." } ;
 
 HELP: with-smtp-connection
 { $values { "quot" quotation } }
-{ $description "Connects to an SMTP server stored in " { $link smtp-server } " and calls the quotation." } ;
+{ $description "Connects to an SMTP server stored in " { $link smtp-server } " and calls the quotation." }
+{ $notes "This word is used to implement " { $link send-email } " and there is probably no reason to call it directly." } ;
+
+HELP: email
+{ $class-description "An e-mail. E-mails have the following slots:"
+    { $table
+        { { $slot "from" } "The sender of the e-mail. An e-mail address." }
+        { { $slot "to" } "The recipients of the e-mail. A sequence of e-mail addresses." }
+        { { $slot "cc" } "Carbon-copy. A sequence of e-mail addresses." }
+        { { $slot "bcc" } "Blind carbon-copy. A sequence of e-mail addresses." }
+        { { $slot "subject" } " The subject of the e-mail. A string." }
+        { { $slot "body" } " The body of the e-mail. A string." }
+    }
+"The " { $slot "from" } " and " { $slot "to" } " slots are required; the rest are optional."
+$nl
+"An e-mail address is a string in one of the following two formats:"
+{ $list
+    { $snippet "joe@groff.com" }
+    { $snippet "Joe Groff <joe@groff.com>" }
+} } ;
 
 HELP: <email>
 { $values { "email" email } }
@@ -26,9 +55,9 @@ HELP: <email>
 
 HELP: send-email
 { $values { "email" email } }
-{ $description "Sends an " { $link email } " object to an STMP server stored in the " { $link smtp-server } " variable.  The required slots are " { $slot "from" } " and " { $slot "to" } "." }
+{ $description "Sends an e-mail." }
 { $examples
-    { $unchecked-example "USING: accessors smtp ;"
+    { $code "USING: accessors smtp ;"
     "<email>"
     "    \"groucho@marx.bros\" >>from"
     "    { \"chico@marx.bros\" \"harpo@marx.bros\" } >>to"
@@ -42,10 +71,20 @@ HELP: send-email
 } ;
 
 ARTICLE: "smtp" "SMTP client library"
-"Configuring SMTP:"
+"The " { $vocab-link "smtp" } " vocabulary sends e-mail via an SMTP server."
+$nl
+"This library is configured by a set of dynamically-scoped variables:"
 { $subsection smtp-server }
 { $subsection smtp-read-timeout }
 { $subsection smtp-domain }
-{ $subsection esmtp? }
+{ $subsection smtp-auth }
+"The latter is set to an instance of one of the following:"
+{ $subsection no-auth }
+{ $subsection plain-auth }
+"Constructing an e-mail:"
+{ $subsection email }
+{ $subsection <email> }
 "Sending an email:"
 { $subsection send-email } ;
+
+ABOUT: "smtp"
index f8b321fdac465218ad4ae1d4f3d585fbfe83c984..c2c95168f86909bd617b92abdd39ca645da9fe2f 100644 (file)
@@ -1,8 +1,10 @@
 USING: smtp tools.test io.streams.string io.sockets threads
 smtp.server kernel sequences namespaces logging accessors
-assocs sorting smtp.private ;
+assocs sorting smtp.private concurrency.promises ;
 IN: smtp.tests
 
+\ send-email must-infer
+
 { 0 0 } [ [ ] with-smtp-connection ] must-infer-as
 
 [ "hello\nworld" validate-address ] must-fail
@@ -63,13 +65,13 @@ IN: smtp.tests
     [ from>> extract-email ] tri
 ] unit-test
 
-[ ] [ [ 4321 mock-smtp-server ] "SMTP server" spawn drop ] unit-test
+<promise> "p" set
 
-[ ] [ yield ] unit-test
+[ ] [ "p" get mock-smtp-server ] unit-test
 
 [ ] [
     [
-        "localhost" 4321 <inet> smtp-server set
+        "localhost" "p" get ?promise <inet> smtp-server set
 
         <email>
             "Hi guys\nBye guys" >>body
@@ -82,5 +84,3 @@ IN: smtp.tests
         send-email
     ] with-scope
 ] unit-test
-
-[ ] [ yield ] unit-test
index 9dc03dfac2a8ae7314a121a3612672f1e79873e8..c033ff2b2e973d840c21511797dd8f0513d00029 100644 (file)
@@ -1,16 +1,28 @@
 ! Copyright (C) 2007, 2008 Elie CHAFTARI, Dirk Vleugels,
 ! Slava Pestov, Doug Coleman.
 ! See http://factorcode.org/license.txt for BSD license.
-USING: arrays namespaces make io io.timeouts kernel logging
-io.sockets sequences combinators splitting assocs strings
-math.parser random system calendar io.encodings.ascii summary
-calendar.format accessors sets hashtables ;
+USING: arrays namespaces make io io.encodings.string
+io.encodings.utf8 io.timeouts kernel logging io.sockets
+sequences combinators splitting assocs strings math.parser
+random system calendar io.encodings.ascii summary
+calendar.format accessors sets hashtables base64 ;
 IN: smtp
 
 SYMBOL: smtp-domain
-SYMBOL: smtp-server     "localhost" 25 <inet> smtp-server set-global
-SYMBOL: smtp-read-timeout    1 minutes smtp-read-timeout set-global
-SYMBOL: esmtp?           t esmtp? set-global
+
+SYMBOL: smtp-server
+"localhost" 25 <inet> smtp-server set-global
+
+SYMBOL: smtp-read-timeout
+1 minutes smtp-read-timeout set-global
+
+SINGLETON: no-auth
+
+TUPLE: plain-auth username password ;
+C: <plain-auth> plain-auth
+
+SYMBOL: smtp-auth
+no-auth smtp-auth set-global
 
 LOG: log-smtp-connection NOTICE ( addrspec -- )
 
@@ -34,12 +46,12 @@ TUPLE: email
 : <email> ( -- email ) email new ;
 
 <PRIVATE
+
 : crlf ( -- ) "\r\n" write ;
 
 : command ( string -- ) write crlf flush ;
 
-: helo ( -- )
-    esmtp? get "EHLO " "HELO " ? host-name append command ;
+: helo ( -- ) "EHLO " host-name append command ;
 
 ERROR: bad-email-address email ;
 
@@ -60,8 +72,7 @@ ERROR: bad-email-address email ;
 ERROR: message-contains-dot message ;
 
 M: message-contains-dot summary ( obj -- string )
-    drop
-    "Message cannot contain . on a line by itself" ;
+    drop "Message cannot contain . on a line by itself" ;
 
 : validate-message ( msg -- msg' )
     "." over member?
@@ -115,7 +126,7 @@ ERROR: smtp-transaction-failed < smtp-error ;
     3 swap ?nth CHAR: - = ;
 
 : process-multiline ( multiline -- response )
-    >r readln r> 2dup " " append head? [
+    [ readln ] dip 2dup " " append head? [
         drop dup smtp-response
     ] [
         swap check-response process-multiline
@@ -127,6 +138,16 @@ ERROR: smtp-transaction-failed < smtp-error ;
 
 : get-ok ( -- ) receive-response check-response ;
 
+GENERIC: send-auth ( auth -- )
+
+M: no-auth send-auth drop ;
+
+M: plain-auth send-auth
+    [ username>> ] [ password>> ] bi "\0" swap 3append utf8 encode >base64
+    "AUTH PLAIN " prepend command ;
+
+: auth ( -- ) smtp-auth get send-auth get-ok ;
+
 ERROR: invalid-header-string string ;
 
 : validate-header ( string -- string' )
@@ -170,6 +191,7 @@ ERROR: invalid-header-string string ;
 : (send-email) ( headers email -- )
     [
         helo get-ok
+        auth
         dup from>> extract-email mail-from get-ok
         dup to>> [ extract-email rcpt-to get-ok ] each
         dup cc>> [ extract-email rcpt-to get-ok ] each
@@ -180,6 +202,7 @@ ERROR: invalid-header-string string ;
         body>> send-body get-ok
         quit get-ok
     ] with-smtp-connection ;
+
 PRIVATE>
 
 : send-email ( email -- )