chicken-users
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Chicken-users] Wierd diagnostic, and gripe!


From: Julian Morrison
Subject: Re: [Chicken-users] Wierd diagnostic, and gripe!
Date: Mon, 14 Feb 2005 10:54:22 +0000
User-agent: Debian Thunderbird 1.0 (X11/20050116)

felix winkelmann wrote:

This looks more like a bug in the compiler. Can you send me the
code, or a snippet containing code that triggers this behaviour?
I'll attach it. I hope the mailing list doesn't drop attachments!

I'm new to Scheme, and cook-booking a bit, so if I made any dumb mistakes please say.

What Chicken version are you using?
The chicken -version command says "Version 1, Build 63 - linux-unix-gnu-x86". It's the debian package version 1.63-2.
;; This is the advanced-pop3 library (in Chicken scheme)
;;
;; All functions of RFC 1939 Post Office Protocol version 3 are supported.
;; All errors are recovered if recoverable, signalled with (exn advanced-pop3) 
if not.
;;
;; Requires: library extras tcp regex
;;
;; Requires-eggs: md5
;;
;; Author: Julian Morrison <address@hidden>
;;
;; Copying conditions: this file has been placed in the public domain by the 
author.

(declare
 (uses library extras tcp regex md5)
 (export advanced-pop3:connect
         advanced-pop3:connected?
         advanced-pop3:user
         advanced-pop3:pass
         advanced-pop3:apop
         advanced-pop3:connect-and-authenticate
         advanced-pop3:stat
         advanced-pop3:list
         advanced-pop3:retr
         advanced-pop3:dele
         advanced-pop3:noop
         advanced-pop3:rset
         advanced-pop3:top
         advanced-pop3:uidl
         advanced-pop3:quit
         advanced-pop3:fetch-each
         advanced-pop3:fetch-everything
         advanced-pop3:pop3-connection?))

(require 'tcp)
(require 'regex)
(require 'md5)

(define default-port 110)

(define-record pop3-connection
   state ; 'authorization 'transaction or 'closed
   logger-proc ; or #f
   apop-timestamp ; or #f
   in
   out)

;; just forward the predicate for export
(define advanced-pop3:pop3-connection? pop3-connection?)

;; test the state machine for sanity
(define (require-state pop3 state)
   (ensure symbol? state)
   (ensure pop3-connection? pop3)
   (ensure (lambda (x) (eq? x state)) (pop3-connection-state pop3)))

(define (advanced-pop3:connected? pop3)
   (ensure pop3-connection? pop3)
   (not (eq? (pop3-connection-state pop3) 'closed)))

(define (has-apop-capability? pop3)
   (ensure pop3-connection? pop3)
   (if (pop3-connection-apop-timestamp pop3) #t #f))

;; error handling
;; errors are either recoverable or not
(define recoverable (make-property-condition 'recoverable))
(define non-recoverable (make-property-condition 'non-recoverable))
(define (pop3-exception is-recoverable msg location . etc)
   (ensure condition? is-recoverable)
   (ensure string? msg)
   (abort (make-composite-condition
           (make-property-condition 'exn 'message msg 'location location 
'arguments etc)
           (make-property-condition 'advanced-pop3)
           is-recoverable)))
   

;; the logger takes a message generator or a message
;; this avoids constructing log output unless necessary
(define (log pop3 message-generator)
   (ensure pop3-connection? pop3)
   (let ((logger (pop3-connection-logger-proc pop3)))
      (when logger
         (let ((message (if (procedure? message-generator)
                            (apply message-generator '())  ; run it
                            message-generator))) ; otherwise just use it 
directly
            (ensure string? message)
            (logger message)))))

;; all subs should handle unidentified exceptions
(define-macro (with-fallback-error-handling . code)
   ;`(begin ,@code)) ;; does nothing
   `(condition-case
     (begin ,@code)
     (rethrow-me (exn advanced-pop3) (abort rethrow-me))
     (exn (exn)
        (let ((msg ((condition-property-accessor 'exn 'message) exn))
              (location ((condition-property-accessor 'exn 'location) exn)))
           (pop3-exception non-recoverable (sprintf "Caught exception ~S msg:~S 
location:~S" exn msg location ) location exn)))
     (other () (pop3-exception non-recoverable (sprintf "Caught unrecognized 
condition ~S" other) "unknown" other))))

;; if pop3 signals an error, turn it into a recoverable exception
;; if the line is garbled, throw a nonrecoverable exception
;; otherwise return the whole line
(define (input-line pop3)
   (ensure pop3-connection? pop3)
   (with-fallback-error-handling
    (let* ((line (read-line (pop3-connection-in pop3))))
       (log pop3 (lambda () (sprintf ">~A" line)))
       (cond
          ((zero? (string-length line)) (pop3-exception non-recoverable 
"unexpected blank line from server" 'input-line line))
          ((substring=? line "+OK") line)
          ((substring=? line "-ERR") (pop3-exception recoverable line))
          (else (pop3-exception non-recoverable "garbled line from server" 
'input-line line))))))

;; emit the line and return the response
;; implicitly tests the response and will exception if it is an error
(define (output-line pop3 format-string . format-args)
   (ensure pop3-connection? pop3)
   (ensure string? format-string)
   (with-fallback-error-handling
    (log pop3 (lambda () (sprintf "<~?" format-string format-args)))
    (fprintf (pop3-connection-out pop3) "~?\r\n" format-string format-args)
    (flush-output (pop3-connection-out pop3))
    (input-line pop3)))

;; this just establishes a connection and parses the apop timestamp (if any 
exists)
;; Args: server - must be a string, the remote server to connect to
;;       port   - must be an integer and a valid port number, optional
;;       logger - a procedure with one argument to callback with log strings
(define (advanced-pop3:connect server . etc)
   (let-optionals etc ((port default-port) (logger #f))
      (ensure string? server)
      (ensure (lambda (x) (and (integer? x) (> x 0) (< x 65535))) port)
      (ensure (lambda (x) (or (not x) (procedure? x))) logger)
      (with-fallback-error-handling
       (let-values (((in out) (tcp-connect server port)))
          (let* ((pop3 (make-pop3-connection
                        'authorization ; state
                        logger
                        #f ; apop-timestamp
                        in
                        out))
                 (hello-from-server (input-line pop3))
                 (apop-timestamp-matched (string-match ".*?(<.*?>).*" 
hello-from-server)))
             (when apop-timestamp-matched
                (let ((apop-timestamp (list-ref apop-timestamp-matched 1)))
                   (pop3-connection-apop-timestamp-set! pop3 apop-timestamp)
                   (log pop3 (lambda () (sprintf "!An APOP timestamp was 
detected, it was ~S" apop-timestamp)))))
             ;; and return
             pop3)))))

(define (advanced-pop3:user pop3 username)
   (ensure pop3-connection? pop3)
   (ensure string? username)
   (require-state pop3 'authorization)
   (output-line pop3 "USER ~A" username))

;; warning, insecure, password is sent in plain-text
;; must be called after advanced-pop3:user
(define (advanced-pop3:pass pop3 password)
   (ensure pop3-connection? pop3)
   (ensure string? password)
   (require-state pop3 'authorization)
   (output-line pop3 "PASS ~A" password)
   ;;implicit success
   (pop3-connection-state-set! pop3 'transaction)
   (log pop3 "!Authentication success"))

;; authenticate securely with md5
;; this POP3 command is optional and may exception if unsupported
(define (advanced-pop3:apop pop3 username password)
   (ensure pop3-connection? pop3)
   (ensure string? username)
   (ensure string? password)
   (require-state pop3 'authorization)
   (let ((timestamp (pop3-connection-apop-timestamp pop3)))
      (unless timestamp
         (pop3-exception recoverable "APOP is not supported" 
'advanced-pop3:apop))
      (let ((digest (md5:digest (string-append timestamp password))))
         (output-line pop3 "APOP ~A ~S" username digest)
         ;;implicit success
         (pop3-connection-state-set! pop3 'transaction)
         (log pop3 "!Authentication success"))))

;; connect-and-authenticate chooses between PASS and APOP automatically
;; if it chooses APOP, and fails, it falls back to PASS
(define (advanced-pop3:connect-and-authenticate server username password . etc)
  (define (do-pass pop3)
     (advanced-pop3:user pop3 username)
     (advanced-pop3:pass pop3 password))
  (let ((pop3 (apply advanced-pop3:connect server etc))) ;; using apply to 
splice the etc list
     (cond ((has-apop-capability? pop3)
            (condition-case ;; begin exception trapping
             (begin
                (advanced-pop3:apop pop3 username password)
                pop3)
             ((exn advanced-pop3 recoverable)
              (do-pass pop3)
              pop3)
             (other () (abort other)))) ;; rethrow
           ;; no apop, so only try pass
           (else
            (do-pass pop3)
            pop3))))

;; returns a list of how many mails, and the total bytesize
(define (advanced-pop3:stat pop3)
   (ensure pop3-connection? pop3)
   (require-state pop3 'transaction)
   (let* ((status (output-line pop3 "STAT"))
          (fields (string-split status))
          (count (string->number (list-ref fields 0)))
          (bytes (string->number (list-ref fields 1))))
      (list count bytes)))

(define (lines-until-period pop3 . accumulator) ;; cuteness
   (define (unstuff line) (cond
                             ((zero? (string-length line)) line)
                             ((substring=? line ".")
                              (substring line 1 (string-length line))) ;; chop 
first char
                             (else line)))
   (define line (read-line (pop3-connection-in pop3)))
   (if (string=? line ".")
       ;; return the list of values
       (reverse accumulator)
       ;; otherwise tail-recurse prepending the line to the accumulator
       (let ((line (unstuff line)))
          (log pop3 (lambda () (sprintf ">~A" line)))
          (apply lines-until-period pop3 (cons line accumulator)))))

;; return message IDs and byte sizes
;; with the optional message-id, one byte-size is returned as an integer
;; without the optional message-id, a list of lists of (msgid byte-size) is 
returned
(define (advanced-pop3:list pop3 . etc)
   (let-optionals etc ((message-id #f))
      (ensure pop3-connection? pop3)
      (require-state pop3 'transaction)
      (cond
         (message-id
          (ensure integer? message-id)
          (let* ((response (output-line pop3 "LIST ~A" message-id))
                 (fields (string-split response))
                 (message-bytes (string->number (list-ref fields 2))))
             message-bytes))
         (else
          (output-line pop3 "LIST")
          (let ((lines (lines-until-period pop3)))
             (map
              (lambda (line)
                 (let* ((fields (string-split line))
                        (message-id (string->number (list-ref fields 0)))
                        (message-bytes (string->number (list-ref fields 1))))
                    (list message-id message-bytes)))
              lines))))))
             

;; returns the raw unparsed email
(define (advanced-pop3:retr pop3 message-id)
   (ensure pop3-connection? pop3)
   (require-state pop3 'transaction)
   (ensure integer? message-id)
   (output-line pop3 "RETR ~A" message-id)
   (string-append (string-intersperse (lines-until-period pop3) "\n") "\n"))

;; mark the message for deletion at quit
(define (advanced-pop3:dele pop3 message-id)
   (ensure pop3-connection? pop3)
   (require-state pop3 'transaction)
   (ensure integer? message-id)
   (output-line pop3 "DELE ~A" message-id))

;; not very useful, but completeness is good
(define (advanced-pop3:noop pop3)
   (ensure pop3-connection? pop3)
   (require-state pop3 'transaction)
   (output-line pop3 "NOOP"))

;; cancel all deletions
(define (advanced-pop3:rset pop3) 
   (ensure pop3-connection? pop3)
   (require-state pop3 'transaction)
   (output-line pop3 "RSET"))

;; returns a string consisting of the headers, a blank line, and the indicated 
number of lines of message body
;; this POP3 command is optional and may exception if unsupported
(define (advanced-pop3:top pop3 message-id count-of-lines)
   (ensure pop3-connection? pop3)
   (require-state pop3 'transaction)
   (ensure integer? message-id)
   (ensure integer? count-of-lines)
   (output-line pop3 "TOP ~A ~A" message-id count-of-lines)
   (string-append (string-intersperse (lines-until-period pop3) "\n") "\n"))

;; return non-repeating unique IDs
;; with the optional message-id, one uid is returned as a string
;; without the optional message-id, a list of lists of (msgid uid) is returned
;; this POP3 command is optional and may exception if unsupported
(define (advanced-pop3:uidl pop3 . etc)
   (let-optionals etc ((message-id #f))
      (require-state pop3 'transaction)
      (ensure pop3-connection? pop3)
      (cond
         (message-id
          (ensure integer? message-id)
          (let* ((response (output-line pop3 "UIDL ~A" message-id))
                 (fields (string-split response))
                 (message-uidl (list-ref fields 2)))
             message-uidl))
         (else
          (output-line pop3 "UIDL")
          (let ((lines (lines-until-period pop3)))
             (map
              (lambda (line)
                 (let* ((fields (string-split line))
                        (message-id (string->number (list-ref fields 0)))
                        (message-uidl (list-ref fields 1)))
                    (list message-id message-uidl)))
              lines))))))

;; perform all pending deletions and close the connection
(define (advanced-pop3:quit pop3)
   (output-line pop3 "QUIT")
   (close-input-port (pop3-connection-in pop3))
   (close-output-port (pop3-connection-out pop3))
   (pop3-connection-state-set! pop3 'closed))

;; connects, calls handler-proc once for each email, deletes each email after 
handler-proc returns, and quits
;; deletion is guaranteed not to occur until after handler-proc has returned
(define (advanced-pop3:fetch-each handler-proc server username password . etc)
   (ensure procedure? handler-proc)
   (define pop3 (apply advanced-pop3:connect-and-authenticate server username 
password etc))
   (define (fetch x)
      (let ((message-id (list-ref fields 0)))
         (handler-proc (advanced-pop3:retr pop3 message-id)))
         (advanced-pop3:dele pop3 message-id))
   (condition-case ;; begin exception trapping
    (begin
       (for-each fetch (advanced-pop3:list pop3))
       (advanced-pop3:quit pop3))
    ;; handle clean quit if there was a problem
    (rethrow-me ()
       (when (advanced-pop3:connected? pop3)
          (advanced-pop3:rset pop3)
          (advanced-pop3:quit pop3))
       (abort rethrow-me))))

;; connects, performs LIST, RETR, DELE to obtain all emails in a list of 
strings, and QUITs
(define (advanced-pop3:fetch-everything server username password . etc)
   (define messages '())
   (define (handler-proc message)
      (set! messages (cons message messages)))
   (apply advanced-pop3:fetch-each handler-proc server username password etc)
   ;; and return
   messages)

;; this is the test harness
; (begin
;    (define (log-to-screen log)
;       (with-output-to-port (current-error-port)
;        (lambda ()
;           (display log)
;           (newline))))
;    (condition-case
;     (begin
;        (define pop3 (advanced-pop3:connect-and-authenticate "servername" 
"username" "password" default-port log-to-screen))
;        (advanced-pop3:list pop3)
;        (advanced-pop3:uidl pop3)
;        (advanced-pop3:retr pop3 1)
;        (advanced-pop3:quit pop3))
;     (exn (exn)
;        (printf "There was an exception: ~S~%" exn)
;        (printf "- ~A~%" ((condition-property-accessor 'exn 'message) exn))
;        (printf "- location: ~A~%" ((condition-property-accessor 'exn 
'location) exn)))
;     (problem ()
;        (printf "There was a problem: ~S~%" problem))))

reply via email to

[Prev in Thread] Current Thread [Next in Thread]