PageRenderTime 48ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/racket-5-0-2-bin-i386-osx-mac-dmg/collects/net/pop3-unit.rkt

http://github.com/smorin/f4f.arc
Racket | 390 lines | 244 code | 67 blank | 79 comment | 27 complexity | bab8c9af21b84621910815ba274d91f4 MD5 | raw file
Possible License(s): LGPL-2.0
  1. #lang scheme/unit
  2. (require scheme/tcp "pop3-sig.ss")
  3. (import)
  4. (export pop3^)
  5. ;; Implements RFC 1939, Post Office Protocol - Version 3, Myers & Rose
  6. ;; sender : oport
  7. ;; receiver : iport
  8. ;; server : string
  9. ;; port : number
  10. ;; state : symbol = (disconnected, authorization, transaction)
  11. (define-struct communicator (sender receiver server port [state #:mutable]))
  12. (define-struct (pop3 exn) ())
  13. (define-struct (cannot-connect pop3) ())
  14. (define-struct (username-rejected pop3) ())
  15. (define-struct (password-rejected pop3) ())
  16. (define-struct (not-ready-for-transaction pop3) (communicator))
  17. (define-struct (not-given-headers pop3) (communicator message))
  18. (define-struct (illegal-message-number pop3) (communicator message))
  19. (define-struct (cannot-delete-message exn) (communicator message))
  20. (define-struct (disconnect-not-quiet pop3) (communicator))
  21. (define-struct (malformed-server-response pop3) (communicator))
  22. ;; signal-error :
  23. ;; (exn-args ... -> exn) x format-string x values ... ->
  24. ;; exn-args -> ()
  25. (define (signal-error constructor format-string . args)
  26. (lambda exn-args
  27. (raise (apply constructor
  28. (apply format format-string args)
  29. (current-continuation-marks)
  30. exn-args))))
  31. ;; signal-malformed-response-error :
  32. ;; exn-args -> ()
  33. ;; -- in practice, it takes only one argument: a communicator.
  34. (define signal-malformed-response-error
  35. (signal-error make-malformed-server-response
  36. "malformed response from server"))
  37. ;; confirm-transaction-mode :
  38. ;; communicator x string -> ()
  39. ;; -- signals an error otherwise.
  40. (define (confirm-transaction-mode communicator error-message)
  41. (unless (eq? (communicator-state communicator) 'transaction)
  42. ((signal-error make-not-ready-for-transaction error-message)
  43. communicator)))
  44. ;; default-pop-port-number :
  45. ;; number
  46. (define default-pop-port-number 110)
  47. (define-struct server-responses ())
  48. (define-struct (+ok server-responses) ())
  49. (define-struct (-err server-responses) ())
  50. ;; connect-to-server*:
  51. ;; input-port output-port -> communicator
  52. (define connect-to-server*
  53. (case-lambda
  54. [(receiver sender) (connect-to-server* receiver sender "unspecified" "unspecified")]
  55. [(receiver sender server-name port-number)
  56. (let ([communicator (make-communicator sender receiver server-name port-number
  57. 'authorization)])
  58. (let ([response (get-status-response/basic communicator)])
  59. (cond
  60. [(+ok? response) communicator]
  61. [(-err? response)
  62. ((signal-error make-cannot-connect
  63. "cannot connect to ~a on port ~a"
  64. server-name port-number))])))]))
  65. ;; connect-to-server :
  66. ;; string [x number] -> communicator
  67. (define connect-to-server
  68. (lambda (server-name (port-number default-pop-port-number))
  69. (let-values ([(receiver sender) (tcp-connect server-name port-number)])
  70. (connect-to-server* receiver sender server-name port-number))))
  71. ;; authenticate/plain-text :
  72. ;; string x string x communicator -> ()
  73. ;; -- if authentication succeeds, sets the communicator's state to
  74. ;; transaction.
  75. (define (authenticate/plain-text username password communicator)
  76. (let ([sender (communicator-sender communicator)])
  77. (send-to-server communicator "USER ~a" username)
  78. (let ([status (get-status-response/basic communicator)])
  79. (cond
  80. [(+ok? status)
  81. (send-to-server communicator "PASS ~a" password)
  82. (let ([status (get-status-response/basic communicator)])
  83. (cond
  84. [(+ok? status)
  85. (set-communicator-state! communicator 'transaction)]
  86. [(-err? status)
  87. ((signal-error make-password-rejected
  88. "password was rejected"))]))]
  89. [(-err? status)
  90. ((signal-error make-username-rejected
  91. "username was rejected"))]))))
  92. ;; get-mailbox-status :
  93. ;; communicator -> number x number
  94. ;; -- returns number of messages and number of octets.
  95. (define (get-mailbox-status communicator)
  96. (confirm-transaction-mode
  97. communicator
  98. "cannot get mailbox status unless in transaction mode")
  99. (send-to-server communicator "STAT")
  100. (apply values
  101. (map string->number
  102. (let-values ([(status result)
  103. (get-status-response/match
  104. communicator
  105. #rx"([0-9]+) ([0-9]+)"
  106. #f)])
  107. result))))
  108. ;; get-message/complete :
  109. ;; communicator x number -> list (string) x list (string)
  110. (define (get-message/complete communicator message)
  111. (confirm-transaction-mode
  112. communicator
  113. "cannot get message headers unless in transaction state")
  114. (send-to-server communicator "RETR ~a" message)
  115. (let ([status (get-status-response/basic communicator)])
  116. (cond
  117. [(+ok? status)
  118. (split-header/body (get-multi-line-response communicator))]
  119. [(-err? status)
  120. ((signal-error make-illegal-message-number
  121. "not given message ~a" message)
  122. communicator message)])))
  123. ;; get-message/headers :
  124. ;; communicator x number -> list (string)
  125. (define (get-message/headers communicator message)
  126. (confirm-transaction-mode
  127. communicator
  128. "cannot get message headers unless in transaction state")
  129. (send-to-server communicator "TOP ~a 0" message)
  130. (let ([status (get-status-response/basic communicator)])
  131. (cond
  132. [(+ok? status)
  133. (let-values ([(headers body)
  134. (split-header/body
  135. (get-multi-line-response communicator))])
  136. headers)]
  137. [(-err? status)
  138. ((signal-error make-not-given-headers
  139. "not given headers to message ~a" message)
  140. communicator message)])))
  141. ;; get-message/body :
  142. ;; communicator x number -> list (string)
  143. (define (get-message/body communicator message)
  144. (let-values ([(headers body) (get-message/complete communicator message)])
  145. body))
  146. ;; split-header/body :
  147. ;; list (string) -> list (string) x list (string)
  148. ;; -- returns list of headers and list of body lines.
  149. (define (split-header/body lines)
  150. (let loop ([lines lines] [header null])
  151. (if (null? lines)
  152. (values (reverse header) null)
  153. (let ([first (car lines)]
  154. [rest (cdr lines)])
  155. (if (string=? first "")
  156. (values (reverse header) rest)
  157. (loop rest (cons first header)))))))
  158. ;; delete-message :
  159. ;; communicator x number -> ()
  160. (define (delete-message communicator message)
  161. (confirm-transaction-mode
  162. communicator
  163. "cannot delete message unless in transaction state")
  164. (send-to-server communicator "DELE ~a" message)
  165. (let ([status (get-status-response/basic communicator)])
  166. (cond
  167. [(-err? status)
  168. ((signal-error make-cannot-delete-message
  169. "no message numbered ~a available to be deleted" message)
  170. communicator message)]
  171. [(+ok? status)
  172. 'deleted])))
  173. ;; regexp for UIDL responses
  174. (define uidl-regexp #rx"([0-9]+) (.*)")
  175. ;; get-unique-id/single :
  176. ;; communicator x number -> string
  177. (define (get-unique-id/single communicator message)
  178. (confirm-transaction-mode
  179. communicator
  180. "cannot get unique message id unless in transaction state")
  181. (send-to-server communicator "UIDL ~a" message)
  182. (let-values ([(status result)
  183. (get-status-response/match communicator uidl-regexp ".*")])
  184. ;; The server response is of the form
  185. ;; +OK 2 QhdPYR:00WBw1Ph7x7
  186. (cond
  187. [(-err? status)
  188. ((signal-error make-illegal-message-number
  189. "no message numbered ~a available for unique id" message)
  190. communicator message)]
  191. [(+ok? status)
  192. (cadr result)])))
  193. ;; get-unique-id/all :
  194. ;; communicator -> list(number x string)
  195. (define (get-unique-id/all communicator)
  196. (confirm-transaction-mode communicator
  197. "cannot get unique message ids unless in transaction state")
  198. (send-to-server communicator "UIDL")
  199. (let ([status (get-status-response/basic communicator)])
  200. ;; The server response is of the form
  201. ;; +OK
  202. ;; 1 whqtswO00WBw418f9t5JxYwZ
  203. ;; 2 QhdPYR:00WBw1Ph7x7
  204. ;; .
  205. (map (lambda (l)
  206. (let ([m (regexp-match uidl-regexp l)])
  207. (cons (string->number (cadr m)) (caddr m))))
  208. (get-multi-line-response communicator))))
  209. ;; close-communicator :
  210. ;; communicator -> ()
  211. (define (close-communicator communicator)
  212. (close-input-port (communicator-receiver communicator))
  213. (close-output-port (communicator-sender communicator)))
  214. ;; disconnect-from-server :
  215. ;; communicator -> ()
  216. (define (disconnect-from-server communicator)
  217. (send-to-server communicator "QUIT")
  218. (set-communicator-state! communicator 'disconnected)
  219. (let ([response (get-status-response/basic communicator)])
  220. (close-communicator communicator)
  221. (cond
  222. [(+ok? response) (void)]
  223. [(-err? response)
  224. ((signal-error make-disconnect-not-quiet
  225. "got error status upon disconnect")
  226. communicator)])))
  227. ;; send-to-server :
  228. ;; communicator x format-string x list (values) -> ()
  229. (define (send-to-server communicator message-template . rest)
  230. (apply fprintf (communicator-sender communicator)
  231. (string-append message-template "\r\n")
  232. rest)
  233. (flush-output (communicator-sender communicator)))
  234. ;; get-one-line-from-server :
  235. ;; iport -> string
  236. (define (get-one-line-from-server server->client-port)
  237. (read-line server->client-port 'return-linefeed))
  238. ;; get-server-status-response :
  239. ;; communicator -> server-responses x string
  240. ;; -- provides the low-level functionality of checking for +OK
  241. ;; and -ERR, returning an appropriate structure, and returning the
  242. ;; rest of the status response as a string to be used for further
  243. ;; parsing, if necessary.
  244. (define (get-server-status-response communicator)
  245. (let* ([receiver (communicator-receiver communicator)]
  246. [status-line (get-one-line-from-server receiver)]
  247. [r (regexp-match #rx"^\\+OK(.*)" status-line)])
  248. (if r
  249. (values (make-+ok) (cadr r))
  250. (let ([r (regexp-match #rx"^\\-ERR(.*)" status-line)])
  251. (if r
  252. (values (make--err) (cadr r))
  253. (signal-malformed-response-error communicator))))))
  254. ;; get-status-response/basic :
  255. ;; communicator -> server-responses
  256. ;; -- when the only thing to determine is whether the response
  257. ;; was +OK or -ERR.
  258. (define (get-status-response/basic communicator)
  259. (let-values ([(response rest)
  260. (get-server-status-response communicator)])
  261. response))
  262. ;; get-status-response/match :
  263. ;; communicator x regexp x regexp -> (status x list (string))
  264. ;; -- when further parsing of the status response is necessary.
  265. ;; Strips off the car of response from regexp-match.
  266. (define (get-status-response/match communicator +regexp -regexp)
  267. (let-values ([(response rest)
  268. (get-server-status-response communicator)])
  269. (if (and +regexp (+ok? response))
  270. (let ([r (regexp-match +regexp rest)])
  271. (if r (values response (cdr r))
  272. (signal-malformed-response-error communicator)))
  273. (if (and -regexp (-err? response))
  274. (let ([r (regexp-match -regexp rest)])
  275. (if r (values response (cdr r))
  276. (signal-malformed-response-error communicator)))
  277. (signal-malformed-response-error communicator)))))
  278. ;; get-multi-line-response :
  279. ;; communicator -> list (string)
  280. (define (get-multi-line-response communicator)
  281. (let ([receiver (communicator-receiver communicator)])
  282. (let loop ()
  283. (let ([l (get-one-line-from-server receiver)])
  284. (cond
  285. [(eof-object? l)
  286. (signal-malformed-response-error communicator)]
  287. [(string=? l ".")
  288. '()]
  289. [(and (> (string-length l) 1)
  290. (char=? (string-ref l 0) #\.))
  291. (cons (substring l 1 (string-length l)) (loop))]
  292. [else
  293. (cons l (loop))])))))
  294. ;; make-desired-header :
  295. ;; string -> desired
  296. (define (make-desired-header raw-header)
  297. (regexp
  298. (string-append
  299. "^"
  300. (list->string
  301. (apply append
  302. (map (lambda (c)
  303. (cond
  304. [(char-lower-case? c)
  305. (list #\[ (char-upcase c) c #\])]
  306. [(char-upper-case? c)
  307. (list #\[ c (char-downcase c) #\])]
  308. [else
  309. (list c)]))
  310. (string->list raw-header))))
  311. ":")))
  312. ;; extract-desired-headers :
  313. ;; list (string) x list (desired) -> list (string)
  314. (define (extract-desired-headers headers desireds)
  315. (let loop ([headers headers])
  316. (if (null? headers) null
  317. (let ([first (car headers)]
  318. [rest (cdr headers)])
  319. (if (ormap (lambda (matcher)
  320. (regexp-match matcher first))
  321. desireds)
  322. (cons first (loop rest))
  323. (loop rest))))))