]> gitweb.factorcode.org Git - factor.git/blob - extra/imap/imap.factor
change ERROR: words from throw-foo back to foo.
[factor.git] / extra / imap / imap.factor
1 USING: accessors arrays assocs calendar calendar.format calendar.format.macros
2 formatting fry grouping io io.crlf io.encodings.ascii io.encodings.binary
3 io.encodings.string io.encodings.utf7 io.encodings.utf8 io.sockets
4 io.sockets.secure io.streams.duplex io.streams.string kernel math math.parser
5 sequences splitting strings ;
6 QUALIFIED: pcre
7 IN: imap
8
9 ERROR: imap4-error ind data ;
10
11 CONSTANT: IMAP4_PORT     143
12 CONSTANT: IMAP4_SSL_PORT 993
13
14 ! Converts a timestamp to the format imap4 expects.
15 : timestamp>internal-date ( timestamp -- str )
16     [
17
18         {
19             DD "-" MONTH "-" YYYY " "
20             hh ":" mm ":" ss " "
21             [ gmt-offset>> write-gmt-offset ]
22         } formatted
23     ] with-string-writer ;
24
25 : internal-date>timestamp ( str -- timestamp )
26     [
27         ! Date, month, year.
28         "-" read-token checked-number
29         "-" read-token month-abbreviations index 1 +
30         read-sp checked-number -rot swap
31         ! Hour, minute second and gmt offset.
32         read-hms " " expect readln parse-rfc822-gmt-offset <timestamp>
33     ] with-string-reader  ;
34
35 <PRIVATE
36
37 : >utf7imap4 ( str -- str' )
38     utf7imap4 encode >string ;
39
40 : comma-list ( numbers -- str )
41     [ number>string ] map "," join ;
42
43 : check-status ( ind data -- )
44     over "OK" = not [ imap4-error ] [ 2drop ] if ;
45
46 : read-response-chunk ( stop-expr -- item ? )
47     read-?crlf ascii decode swap dupd pcre:findall
48     [
49         dup "^.*{(\\d+)}$" pcre:findall
50         [
51             dup "^\\* (\\d+) [A-Z-]+ (.*)$" pcre:findall
52             [ ] [ nip first third second ] if-empty
53         ]
54         [
55             ! Literal item to read, such as message body.
56             nip first second second string>number read ascii decode
57             read-?crlf drop
58         ] if-empty t
59     ]
60     [ nip first rest values f ] if-empty ;
61
62 : read-response ( tag -- lines )
63     "^%s (BAD|NO|OK) (.*)$" sprintf
64     '[ _ read-response-chunk [ suffix ] dip ] { } swap loop
65     unclip-last first2 [ check-status ] keep suffix ;
66
67 : write-command ( command literal tag -- )
68     -rot [
69         [ "%s %s\r\n" sprintf ] [ length "%s %s {%d}\r\n" sprintf ] if-empty
70         ascii encode write flush
71     ] keep [
72         read-?crlf drop "\r\n" append write flush
73     ] unless-empty ;
74
75 : command-response ( command literal -- obj )
76     "ABCD" [ write-command ] [ read-response ] bi ;
77
78 ! Special parsing
79 : parse-items ( seq -- items )
80     first " " split 2 tail ;
81
82 : parse-list-folders ( str -- folder )
83     "\\* LIST \\(([^\\)]+)\\) \"([^\"]+)\" \"([^\"]+)\"" pcre:findall
84     first rest values [ utf7imap4 decode ] map ;
85
86 : parse-select-folder ( seq -- count )
87     [ "\\* (\\d+) EXISTS" pcre:findall ] map harvest
88     [ f ] [ first first last last string>number ] if-empty ;
89
90 ! Returns uid if the server supports the UIDPLUS extension.
91 : parse-append-mail ( seq -- uid/f )
92     [ "\\[APPENDUID (\\d+) \\d+\\]" pcre:findall ] map harvest
93     [ f ] [ first first last last string>number ] if-empty ;
94
95 : parse-status ( seq -- assoc )
96     first "\\* STATUS \"[^\"]+\" \\(([^\\)]+)\\)" pcre:findall first last last
97     " " split 2 group [ string>number ] assoc-map ;
98
99 : parse-store-mail-line ( str -- pair/f )
100     "\\(FLAGS \\(([^\\)]+)\\) UID (\\d+)\\)" pcre:findall [ f ] [
101         first rest values first2 [ " " split ] dip string>number swap 2array
102     ] if-empty ;
103
104 : parse-store-mail ( seq -- assoc )
105     but-last [ parse-store-mail-line ] map sift ;
106
107 PRIVATE>
108
109 ! Constructor
110 : <imap4ssl> ( host -- imap4 )
111     IMAP4_SSL_PORT <inet> <secure> binary <client> drop
112     ! Read the useless welcome message.
113     dup [ "\\*" read-response drop ] with-stream* ;
114
115 ! IMAP commands
116 : capabilities ( -- caps )
117     "CAPABILITY" "" command-response parse-items ;
118
119 : login ( username password -- caps )
120     "LOGIN %s \"%s\"" sprintf "" command-response parse-items ;
121
122 ! Folder management
123 : list-folders ( directory -- folders )
124     "LIST \"%s\" *" sprintf "" command-response
125     but-last [ parse-list-folders ] map ;
126
127 : select-folder ( mailbox -- count )
128     >utf7imap4 "SELECT \"%s\"" sprintf "" command-response
129     parse-select-folder ;
130
131 : create-folder ( mailbox -- )
132     >utf7imap4 "CREATE \"%s\"" sprintf "" command-response
133     drop ;
134
135 : delete-folder ( mailbox -- )
136     >utf7imap4 "DELETE \"%s\"" sprintf "" command-response
137     drop ;
138
139 : rename-folder ( old-name new-name -- )
140     [ >utf7imap4 ] bi@ "RENAME \"%s\" \"%s\"" sprintf "" command-response
141     drop ;
142
143 : status-folder ( mailbox keys -- assoc )
144     [ >utf7imap4 ] dip " " join "STATUS \"%s\" (%s)" sprintf
145     "" command-response parse-status ;
146
147 : close-folder ( -- )
148     "CLOSE" "" command-response drop ;
149
150 ! Mail management
151 : search-mails ( data-spec str -- uids )
152     [ "UID SEARCH CHARSET UTF-8 %s" sprintf ] dip utf8 encode
153     command-response parse-items [ string>number ] map ;
154
155 : fetch-mails ( uids data-spec -- texts )
156     [ comma-list ] dip "UID FETCH %s %s" sprintf "" command-response but-last ;
157
158 : copy-mails ( uids mailbox -- )
159     [ comma-list ] dip >utf7imap4 "UID COPY %s \"%s\"" sprintf ""
160     command-response drop ;
161
162 : append-mail ( mailbox flags date-time mail -- uid/f )
163     [
164         [ >utf7imap4 ]
165         [ [ "" ] [ " " append ] if-empty ]
166         [ timestamp>internal-date ] tri*
167         "APPEND \"%s\" %s\"%s\"" sprintf
168     ] dip utf8 encode command-response parse-append-mail ;
169
170 : store-mail ( uids command flags -- mail-flags )
171     [ comma-list ] 2dip "UID STORE %s %s %s" sprintf "" command-response
172     parse-store-mail ;
173
174 ! High level API
175
176 : with-imap ( host email password quot -- )
177     [ <imap4ssl> ] 3dip '[ _ _ login drop @ ] with-stream ; inline
178
179 TUPLE: imap-settings host email password ;
180
181 : <imap-settings> ( host email password -- obj )
182     imap-settings new
183         swap >>password
184         swap >>email
185         swap >>host ; inline
186
187 : with-imap-settings ( imap-settings quot -- )
188     [ [ host>> ] [ email>> ] [ password>> ] tri ] dip with-imap ; inline