imap4.tcl

Go to the documentation of this file.
00001 /*  IMAP4 protocol pure Tcl implementation.*/
00002 /* */
00003 /*  COPYRIGHT AND PERMISSION NOTICE*/
00004 /*  */
00005 /*  Copyright (C) 2004 Salvatore Sanfilippo <antirez@invece.org>.*/
00006 /*  */
00007 /*  All rights reserved.*/
00008 /*  */
00009 /*  Permission is hereby granted, free of charge, to any person obtaining a*/
00010 /*  copy of this software and associated documentation files (the*/
00011 /*  "Software"), to deal in the Software without restriction, including*/
00012 /*  without limitation the rights to use, copy, modify, merge, publish,*/
00013 /*  distribute, and/or sell copies of the Software, and to permit persons*/
00014 /*  to whom the Software is furnished to do so, provided that the above*/
00015 /*  copyright notice(s) and this permission notice appear in all copies of*/
00016 /*  the Software and that both the above copyright notice(s) and this*/
00017 /*  permission notice appear in supporting documentation.*/
00018 /*  */
00019 /*  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS*/
00020 /*  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF*/
00021 /*  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT*/
00022 /*  OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR*/
00023 /*  HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL*/
00024 /*  INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING*/
00025 /*  FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,*/
00026 /*  NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION*/
00027 /*  WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.*/
00028 /*  */
00029 /*  Except as contained in this notice, the name of a copyright holder*/
00030 /*  shall not be used in advertising or otherwise to promote the sale, use*/
00031 /*  or other dealings in this Software without prior written authorization*/
00032 /*  of the copyright holder.*/
00033 /* */
00034 /*  $Id: imap4.tcl,v 1.2 2004/06/22 17:47:46 mic42 Exp $*/
00035 
00036 /*  TODO*/
00037 /*  - option -inline for ::imap4::fetch, in order to return data as a Tcl list.*/
00038 /*  - Idle mode*/
00039 /*  - Async mode*/
00040 /*  - Authentications*/
00041 /*  - Literals on file mode*/
00042 /*  - isableto without arguments should return the capability list.*/
00043 /*  - fix OR in search, and implement time-related searches*/
00044 /*  All the rest... se the RFC*/
00045 
00046 package require Tcl 8.4
00047 package require struct::list 1.4
00048 
00049 namespace ::imap4 {
00050     
00051     /*  This is where we take state of all the IMAP connections.  */
00052     /*  The following arrays are indexed with the connection channel*/
00053     /*  to access the per-channel information.*/
00054 
00055     /*  general connection state info    */
00056     variable info
00057     array  info =  {}
00058 
00059     /*  selected mailbox info    */
00060     variable mboxinfo
00061     array  mboxinfo =  {}
00062 
00063     /*  messages info    */
00064     variable msginfo
00065     array  msginfo =  {}
00066 
00067     /*  inside debug mode? default is off*/
00068     variable debugmode 0
00069 
00070     /*  Debug mode? Don't use it for production! It will print debugging*/
00071     /*  information to standard output and run a special IMAP debug mode shell*/
00072     /*  on protocol error.*/
00073     
00074     variable debug 1
00075     
00076     /*  Version*/
00077     variable version "2004-03-07"
00078 
00079 }
00080 
00081 /*  imap4::open --*/
00082 /* */
00083 /*    Open a new IMAP connection and initialize the protocol handler.*/
00084 /* */
00085 /*    Arguments:*/
00086 /*        hostname    Hostname of the IMAP server to use*/
00087 /*        port        Port to use (defaults to 143)*/
00088 /* */
00089 /*    Results:*/
00090 /*        chan        Identifier for IMAP channel*/
00091 /* */
00092 ret  ::imap4::open (type hostname , optional port =143) {
00093     set chan [socket $hostname $port]
00094     fconfigure $chan -encoding binary -translation binary
00095     # Intialize the connection state array
00096     ::imap4::initinfo $chan
00097     # Get the banner
00098     ::imap4::processline $chan
00099     # Save the banner
00100     set ::imap4::info($chan,banner) [::imap4::lastline $chan]
00101     return $chan
00102 }
00103 
00104 /*  imap4::cleanup --*/
00105 /* */
00106 /*    Cleanup the internal state and close the IMAP channel. */
00107 /* */
00108 /*    Arguments:*/
00109 /*        chan    Identifier for IMAP channel*/
00110 /* */
00111 /*    Results:*/
00112 /*        chan    Identifier for close IMAP channel*/
00113 /* */
00114 ret  ::imap4::cleanup chan (
00115     type variable , type info
00116     , type variable , type mboxinfo
00117     , type variable , type msginfo
00118     
00119     , type close $, type chan
00120     
00121     , type array , type unset , type info $, type chan,*
00122     , type array , type unset , type mboxinfo $, type chan,*
00123     , type array , type unset , type msginfo $, type chan,*
00124     
00125     , type return $, type chan
00126 )
00127 
00128 # imap4::lastcode --
00129 #
00130 #   Return the last error code for the IMAP channel.
00131 #
00132 #   Arguments:
00133 #       chan    Identifier for IMAP channel
00134 #
00135 #   Results:
00136 #       code    Last error code for the given channel
00137 #
00138 proc ::imap4::lastcode chan {
00139     variable info
00140     return $info($chan,lastcode)
00141 }
00142 
00143 /*  imap4::lastline --*/
00144 /* */
00145 /*    Return the last line received from the server.*/
00146 /* */
00147 /*    Arguments:*/
00148 /*        chan    Identifier for IMAP channel*/
00149 /* */
00150 /*    Results:*/
00151 /*        line    Last line received from the server*/
00152 /* */
00153 ret  ::imap4::lastline chan (
00154     type variable , type info
00155     , type return $, type info($, type chan,, type lastline)
00156 )
00157 
00158 # imap4::state --
00159 #
00160 #   Get the current state
00161 #
00162 #   Arguments:
00163 #       chan    Identifier for IMAP channel
00164 #   
00165 #   Results:
00166 #       state   Current state of the channel
00167 #
00168 proc ::imap4::state chan {
00169     variable info
00170     return $info($chan,state)
00171 }
00172 
00173 /*  imap4::isableto --*/
00174 /* */
00175 /*    Test for capability. Use the capability command*/
00176 /*    to ask the server if not already done by the user.*/
00177 /* */
00178 /*    Arguments:*/
00179 /*        chan    Identifier for IMAP channel*/
00180 /*        capa    Capability to check*/
00181 /* */
00182 /*    Results:*/
00183 /*        1/0     1 if supported, 0 otherwise*/
00184 /* */
00185 ret  ::imap4::isableto (type chan , type capa) {
00186     variable info
00187     if {![llength $info($chan,capability)]} {
00188     if {[::imap4::capability $chan]} {
00189         # mic42 FIXME: This looks strange, 
00190         # should probably be an error, as this signals the
00191         # capabilities request failed
00192         #
00193         return 1
00194     }
00195     }
00196     set capa [string toupper $capa]
00197     expr {[lsearch -exact $info($chan,capability) $capa] != -1}
00198 }
00199 
00200 /*  imap4::msginfo --*/
00201 /* */
00202 /*    Get information (previously collected using fetch) from a given message.*/
00203 /*    If the 'info' argument is omitted or a null string, the full list*/
00204 /*    of information available for the given message is returned.*/
00205 /* */
00206 /*    If the required information name is suffixed with a ? character,*/
00207 /*    the command requires true if the information is available, or*/
00208 /*    false if it is not.*/
00209 /* */
00210 /*    Arguments:*/
00211 /*        chan    Identifier for IMAP channel*/
00212 /*        msgid   Message identifier*/
00213 /*        info    Type of info (optional)*/
00214 /*        default default value for info*/
00215 /* */
00216 /*    Results:*/
00217 /*        msginfo The message info requested or */
00218 /*                list of all valid info values if info and default*/
00219 /*                are missing.*/
00220 /* */
00221 ret  ::imap4::msginfo (type chan , type msgid , type args) {
00222     variable msginfo
00223     
00224     switch -- [llength $args] {
00225     0 {
00226         set info {}
00227     }
00228     1 {
00229         set info [lindex $args 0]
00230         set use_defval 0
00231     }
00232     2 {
00233         set info [lindex $args 0]
00234         set defval [lindex $args 1]
00235         set use_defval 1
00236     }
00237     default {
00238         error "::imap4::msginfo called with bad number of arguments! Try ::imap4::msginfo channel messageid ?info? ?defaultvalue?"
00239     }
00240     }
00241     set info [string tolower $info]
00242     # Handle the missing info case
00243     if {![string length $info]} {
00244     set list [array names msginfo $chan,$msgid,*]
00245     set availinfo {}
00246     foreach l $list {
00247         lappend availinfo [string range $l \
00248         [string length $chan,$msgid,] end]
00249     }
00250     return $availinfo
00251     }
00252     if {[string index $info end] eq {?}} {
00253     set info [string range $info 0 end-1]
00254     return [info exists msginfo($chan,$msgid,$info)]
00255     } else {
00256     if {![info exists msginfo($chan,$msgid,$info)]} {
00257         if {$use_defval} {
00258         return $defval
00259         } else {
00260         error "No such information '$info' available for message id '$msgid'"
00261         }
00262     }
00263     return $msginfo($chan,$msgid,$info)
00264     }
00265 }
00266 
00267 /*  imap4::mboxinfo --*/
00268 /* */
00269 /*    Get information on the currently selected mailbox.*/
00270 /*    If the 'info' argument is omitted or a null string, the full list*/
00271 /*    of information available for the mailbox is returned.*/
00272 /* */
00273 /*    If the required information name is suffixed with a ? character,*/
00274 /*    the command requires true if the information is available, or*/
00275 /*    false if it is not.*/
00276 /* */
00277 /*    Arguments:*/
00278 /*        chan    Identifier for IMAP channel*/
00279 /*        info    type of info requested, defaults to {}*/
00280 /* */
00281 /*    Results:*/
00282 /*        mboxinfo    Information about the selected mailbox    */
00283 /*    */
00284 ret  ::imap4::mboxinfo (type chan , optional info ={)} {
00285     set info [string tolower $info]
00286     # Handle the missing info case
00287     if {![string length $info]} {
00288      list =  [array names ::imap4::mboxinfo $chan,*]
00289      availinfo =  {}
00290     foreach l $list {
00291         lappend availinfo [string range $l \
00292         [string length $chan,] end]
00293     }
00294     return $availinfo
00295     }
00296     if {[string index $info end] eq {?}} {
00297      info =  [string range $info 0 end-1]
00298     return [info exists ::imap4::mboxinfo($chan,$info)]
00299     } else {
00300     if {![info exists ::imap4::mboxinfo($chan,$info)]} {
00301         error "No such information '$info' available for the current mailbox"
00302     }
00303     return $::imap4::mboxinfo($chan,$info)
00304     }
00305 }
00306 
00307 /* */
00308 /* */
00309 /*    Helper procs */
00310 /* */
00311 /* */
00312 
00313 /*  imap4::initinfo --*/
00314 /* */
00315 /*    Initialize the per connection info array for a new*/
00316 /*    IMAP connection.*/
00317 /* */
00318 /*    Arguments:*/
00319 /*        chan        Identifier for IMAP channel*/
00320 /* */
00321 /*    Results:*/
00322 /*        none  */
00323 ret  ::imap4::initinfo chan (
00324     type variable , type info
00325     , type set , type info($, type chan,, type curtag) 0
00326     , type set , type info($, type chan,, type state) , type NOAUTH
00327     , type set , type info($, type chan,, type capability) , optional 
00328     , type set , type info($, type chan,, type raise_, type on_, type NO) 1
00329     , type set , type info($, type chan,, type raise_, type on_, type BAD) 1
00330     , type set , type info($, type chan,, type idle) , optional 
00331     , type set , type info($, type chan,, type lastcode) , optional 
00332     , type set , type info($, type chan,, type lastline) , optional 
00333     , type set , type info($, type chan,, type lastrequest) , optional 
00334     , type return
00335 )
00336 
00337 ###############################################################################
00338 #
00339 # Implementations of IMAP protocol commands.
00340 #
00341 #
00342 ###############################################################################
00343 
00344 # imap4::capability --
00345 #
00346 #   Get capabilties, issues a
00347 #   CAPABILITY command to the server.
00348 #
00349 #   Arguments:
00350 #       chan    Identifer for IMAP channel
00351 #    
00352 #   Results:
00353 #       0/1     0 if successful, 1 otherwise
00354 #
00355 proc ::imap4::capability chan {
00356     ::imap4::request $chan "CAPABILITY"
00357     if {[::imap4::getresponse $chan]} {
00358     return 1
00359     }
00360     return 0
00361 }
00362 
00363 /*  imap4::check --*/
00364 /* */
00365 /*    CHECK. Flush to disk.*/
00366 /* */
00367 /*    Arguments:*/
00368 /*        chan    Identifier for IMAP channel*/
00369 /*    */
00370 /*    Results:*/
00371 /*        0/1*/
00372 /*    */
00373 ret  ::imap4::check chan (
00374     ::type imap4::, type simplecmd $, type chan , type CHECK , type SELECT , optional 
00375 )
00376 
00377 # imap4::close --
00378 #
00379 #   Close the mailbox. Permanently removes \Deleted messages and return to
00380 #   the AUTH state.
00381 #
00382 #   Arguments:
00383 #       chan    Identifier for IMAP channel
00384 #   
00385 #   Results:
00386 #       0/1     
00387 #   
00388 #   Side Effects:
00389 #       sets the state info
00390 #
00391 proc ::imap4::close chan {
00392     if {[::imap4::simplecmd $chan CLOSE SELECT {}]} {
00393     return 1
00394     }
00395     set ::imap4::info($chan,state) AUTH
00396     return 0
00397 }
00398 
00399 /*  imap4::create --*/
00400 /* */
00401 /*    Create a new mailbox.*/
00402 /* */
00403 /*    Arguments:*/
00404 /*        chan    Identifier for IMAP channel*/
00405 /*        mailbox Mailbox name*/
00406 /*    */
00407 /*    Results:*/
00408 /*        0/1*/
00409 /* */
00410 ret  ::imap4::create (type chan , type mailbox) {
00411     ::imap4::simplecmd $chan CREATE {AUTH SELECT} $mailbox
00412 }
00413 
00414 /*  imap4::delete --*/
00415 /*  */
00416 /*    Delete a mailbox*/
00417 /* */
00418 /*    Arguments:*/
00419 /*        chan    Identifier for IMAP channel*/
00420 /*        mailbox Mailbox name*/
00421 /* */
00422 /*    Results:*/
00423 /*        0/1*/
00424 /* */
00425 ret  ::imap4::delete (type chan , type mailbox) {
00426     ::imap4::simplecmd $chan DELETE {AUTH SELECT} $mailbox
00427 }
00428 
00429 /*  imap4::examine --*/
00430 /* */
00431 /*    Read-only equivalent of SELECT, uses the*/
00432 /*    EXAMINE command.*/
00433 /* */
00434 /*    Arguments:*/
00435 /*        chan    Identifier for IMAP channel*/
00436 /*        mailbox Name of the mailbox to examine,*/
00437 /*                defaults to INBOX.*/
00438 /* */
00439 /*    Results:*/
00440 /*        0/1     0 if successful, 1 otherwise*/
00441 /*       */
00442 ret  ::imap4::examine (type chan , optional mailbox =INBOX) {
00443     ::imap4::selectmbox $chan EXAMINE $mailbox
00444 }
00445 
00446 
00447 /*  imap4::fetch --*/
00448 /* */
00449 /*    Fetch a number of attributes from messages*/
00450 /* */
00451 /*    Arguments:*/
00452 /*        chan    Identifier for IMAP channel*/
00453 /*        range   IMAP range to fetch*/
00454 /*        args    list of attributes to fetch*/
00455 /*    */
00456 /*    Results:*/
00457 /*        0/1     0 if successful, 1 otherwise*/
00458 /* */
00459 /*    Side Effects:*/
00460 /*        */
00461 ret  ::imap4::fetch (type chan , type range , type args) {
00462     ::imap4::requirestate $chan SELECT
00463     ::imap4::parserange $chan $range start end
00464     set items {}
00465     set hdrfields {}
00466     foreach w $args {
00467     switch -glob -- [string toupper $w] {
00468         ALL {lappend items ALL}
00469         BODYSTRUCTURE {lappend items BODYSTRUCTURE}
00470         ENVELOPE {lappend items ENVELOPE}
00471         FLAGS {lappend items FLAGS}
00472         SIZE {lappend items RFC822.SIZE}
00473         TEXT {lappend items RFC822.TEXT}
00474         HEADER {lappend items RFC822.HEADER}
00475         UID {lappend items UID}
00476         *: {
00477         lappend hdrfields $w
00478         }
00479         default {
00480         # Fixme: better to raise an error here?
00481         lappend hdrfields $w:
00482         }
00483     }
00484     }
00485     if {[llength $hdrfields]} {
00486     set item {BODY[HEADER.FIELDS (}
00487     foreach field $hdrfields {
00488         append item [string toupper [string range $field 0 end-1]] { }
00489     }
00490     set item [string range $item 0 end-1]
00491     append item {)]}
00492     lappend items $item
00493     }
00494     # Send the request
00495     ::imap4::request $chan "FETCH $start:$end ([join $items])"
00496     if {[::imap4::getresponse $chan]} {
00497     return 1
00498     }
00499     return 0
00500 }
00501 
00502 /*  imap4::login --*/
00503 /* */
00504 /*    Login using the IMAP LOGIN command.*/
00505 /* */
00506 /*    Arguments:*/
00507 /*        chan    Identifier for IMAP channel*/
00508 /*        user    Username to use for LOGIN*/
00509 /*        pass    Password to use for LOGIN*/
00510 /* */
00511 /*    Results:*/
00512 /*        0/1     0 if successful, 1 otherwise*/
00513 /* */
00514 /*    Side Effects:*/
00515 /*        move channel to AUTH state if successful*/
00516 /* */
00517 ret  ::imap4::login (type chan , type user , type pass) {
00518     ::imap4::requirestate $chan NOAUTH
00519     ::imap4::request $chan "LOGIN $user $pass"
00520     if {[::imap4::getresponse $chan]} {
00521     return 1
00522     }
00523     set ::imap4::info($chan,state) AUTH
00524     return 0
00525 }
00526 
00527 /*  imap4::noop --*/
00528 /* */
00529 /*    NOOP command. May get information as untagged data.*/
00530 /*    Useful for keeping an IMAP connection alive.*/
00531 /* */
00532 /*    Arguments:*/
00533 /*        chan    Identifier for IMAP channel*/
00534 /* */
00535 /*    Results:*/
00536 /*        0/1*/
00537 /*    */
00538 /*    Side Effects:*/
00539 /*        may get information as untagged data*/
00540 /* */
00541 ret  ::imap4::noop chan (
00542     ::type imap4::, type simplecmd $, type chan , type NOOP , optional NOAUTH =AUTH SELECT , optional 
00543 )
00544 
00545 # imap4::rename --
00546 #
00547 #   Rename a mailbox
00548 #
00549 #   Arguments:
00550 #       chan    Identifier for IMAP channel
00551 #       oldname Name of mailbox to rename
00552 #       newname New name of mailbox
00553 #
00554 #   Results:
00555 #       0/1
00556 #
00557 proc ::imap4::rename {chan oldname newname} {
00558     ::imap4::simplecmd $chan RENAME {AUTH SELECT} $oldname $newname
00559 }
00560 
00561 /*  imap4::search --*/
00562 /* */
00563 /*    SEARCH command.*/
00564 /* */
00565 /*    Arguments:*/
00566 /*        chan    Identifier for IMAP channel*/
00567 /*        args    search arguments*/
00568 /* */
00569 /*    Results:*/
00570 /*        0/1*/
00571 /* */
00572 ret  ::imap4::search (type chan , type args) {
00573     if {![llength $args]} {
00574     error "missing arguments. Usage: ::imap4::search chan arg ?arg ...?"
00575     }
00576     ::imap4::requirestate $chan SELECT
00577     set imapexpr [::imap4::convert_search_expr $args]
00578     ::imap4::multiline_prefix_command imapexpr "SEARCH"
00579     ::imap4::multiline_request $chan $imapexpr
00580     if {[::imap4::getresponse $chan]} {
00581     return 1
00582     }
00583     return 0
00584 }
00585 
00586 /*  imap4::select --*/
00587 /* */
00588 /*    Mailbox selection. Performs a SELECT command.*/
00589 /*    */
00590 /*    Arguments:*/
00591 /*        chan    Identifier for IMAP channel*/
00592 /*        mailbox Name of the mailbox to select,*/
00593 /*                defaults to INBOX.*/
00594 /* */
00595 /*    Results:*/
00596 /*        0/1     0 if successful, 1 otherwise*/
00597 /* */
00598 ret  ::imap4::select (type chan , optional mailbox =INBOX) {
00599     ::imap4::selectmbox $chan SELECT $mailbox
00600 }
00601 
00602 /*  imap4::subscribe --*/
00603 /* */
00604 /*    Subscribe to a mailbox*/
00605 /* */
00606 /*    Arguments:*/
00607 /*        chan    Identifier for IMAP channel*/
00608 /*        mailbox Name of mailbox*/
00609 /*    */
00610 /*    Results:*/
00611 /*        0/1*/
00612 /* */
00613 ret  ::imap4::subscribe (type chan , type mailbox) {
00614     ::imap4::simplecmd $chan SUBSCRIBE {AUTH SELECT} $mailbox
00615 }
00616 
00617 /*  imap4::unsubscribe --*/
00618 /* */
00619 /*    Unsubscribe to a mailbox*/
00620 /* */
00621 /*    Arguments:*/
00622 /*        chan    Identifier for IMAP channel*/
00623 /*        mailbox Name of a mailbox*/
00624 /*    */
00625 /*    Results:*/
00626 /*        0/1*/
00627 /* */
00628 ret  ::imap4::unsubscribe (type chan , type mailbox) {
00629     ::imap4::simplecmd $chan UNSUBSCRIBE {AUTH SELECT} $mailbox
00630 }
00631 
00632 /* */
00633 /* */
00634 /*    Protocol support functions*/
00635 /* */
00636 /* */
00637 
00638 /*  imap4::literalcount --*/
00639 /* */
00640 /*    Creates an IMAP octect-count.*/
00641 /*    Used to send literals.*/
00642 /* */
00643 /*    Arguments:*/
00644 /*        string      string to check*/
00645 /*    */
00646 /*    Results:*/
00647 /*        litcount    IMAP literal octet count*/
00648 /* */
00649 ret  ::imap4::literalcount string (
00650     type return ", optional [string =length $string]"
00651 )
00652 
00653 # imap4::convert_search_expr --
00654 #
00655 #   Helper for the search command. Convert a programmer friendly expression
00656 #   (actually a tcl list) to the IMAP syntax. Returns a list composed of
00657 #   request, literal, request, literal, ... (to be sent with
00658 #   ::imap4::multiline_request).
00659 #
00660 #   Arguments:
00661 #       expr        Expression to use for search expression
00662 #       
00663 #   Results:
00664 #       imapexpr    IMAP search expression
00665 #
00666 proc ::imap4::convert_search_expr expr {
00667     set result {}
00668     while {[llength $expr]} {
00669     switch -glob -- [string toupper [set token [::struct::list shift expr]]] {
00670         *: {
00671         set wanted [::struct::list shift expr]
00672         ::imap4::multiline_append_command result "HEADER [string range $token 0 end-1]"
00673         ::imap4::multiline_append_literal result $wanted
00674         }
00675 
00676         ANSWERED - DELETED - DRAFT - FLAGGED - RECENT -
00677         SEEN - NEW - OLD - UNANSWERED - UNDELETED -
00678         UNDRAFT - UNFLAGGED - UNSEEN -
00679         ALL {::imap4::multiline_append_command result [string toupper $token]}
00680 
00681         BODY - CC - FROM - SUBJECT - TEXT - KEYWORD -
00682         BCC {
00683         set wanted [::struct::list shift expr]
00684         ::imap4::multiline_append_command result "$token"
00685         ::imap4::multiline_append_literal result $wanted
00686         }
00687 
00688         OR {
00689         set first [::imap4::convert_search_expr [::struct::list shift expr]]
00690         set second [::imap4::convert_search_expr [::struct::list shift expr]]
00691         ::imap4::multiline_append_command result "OR"
00692         ::imap4::multiline_concat_expr result $first
00693         ::imap4::multiline_concat_expr result $second
00694         }
00695 
00696         NOT {
00697         set e [::imap4::convert_search_expr [::struct::list shift expr]]
00698         ::imap4::multiline_append_command result "NOT"
00699         ::imap4::multiline_concat_expr result $e
00700         }
00701 
00702         SMALLER -
00703         LARGER {
00704         set len [::struct::list shift expr]
00705         if {![string is integer $len]} {
00706             error "Invalid integer follows '$token' in IMAP search"
00707         }
00708         ::imap4::multiline_append_command result "$token $len"
00709         }
00710 
00711         ON - SENTBEFORE - SENTON - SENTSINCE - SINCE -
00712         BEFORE {error "TODO"}
00713 
00714         UID {error "TODO"}
00715         default {
00716         error "Syntax error in search expression: '... $token $expr'"
00717         }
00718     }
00719     }
00720     return $result
00721 }
00722 
00723 /*  imap4::multiline_append_command --*/
00724 /* */
00725 /*    Append a command part to a multiline request*/
00726 /* */
00727 /*    Arguments:*/
00728 /*        reqvar  Variable storing the request*/
00729 /*        cmd     command to append*/
00730 /* */
00731 /*    Results:*/
00732 /*        none*/
00733 /* */
00734 ret  ::imap4::multiline_append_command (type reqvar , type cmd) {
00735     upvar 1 $reqvar req
00736     if {[llength $req] == 0} {
00737     lappend req {}
00738     }
00739     lset req end "[lindex $req end] $cmd"
00740 }
00741 
00742 /*  imap4::multiline_append_literal --*/
00743 /* */
00744 /*    Append a literal to a multiline request. Uses a quoted*/
00745 /*    string in simple cases.*/
00746 /* */
00747 /*    Arguments:*/
00748 /*        reqvar  Variable storing the request*/
00749 /*        lit     literal to append*/
00750 /*    */
00751 /*    Results:*/
00752 /*        none*/
00753 /* */
00754 ret  ::imap4::multiline_append_literal (type reqvar , type lit) {
00755     upvar 1 $reqvar req
00756     if {![string is alnum $lit]} {
00757     lset req end "[lindex $req end] [::imap4::literalcount $lit]"
00758     lappend req $lit {}
00759     } else {
00760     ::imap4::multiline_append_command req "\"$lit\""
00761     }
00762 }
00763 
00764 /*  imap4::multiline_prefix_command --*/
00765 /* */
00766 /*    Prefix a multiline request with a command.*/
00767 /* */
00768 /*    Arguments:*/
00769 /*        reqvar  Variable storing the request*/
00770 /*        cmd     Command to prepend*/
00771 /* */
00772 /*    Results:*/
00773 /*        none*/
00774 /* */
00775 ret  ::imap4::multiline_prefix_command (type reqvar , type cmd) {
00776     upvar 1 $reqvar req
00777     if {![llength $req]} {
00778     lappend req {}
00779     }
00780     lset req 0 " $cmd[lindex $req 0]"
00781 }
00782 
00783 /*  imap4::multiline_concat_expr --*/
00784 /*    */
00785 /*    Concat an already created search expression to a multiline request.*/
00786 /* */
00787 /*    Arguments:*/
00788 /*        reqvar  Variable storing the request*/
00789 /*        expr    expression to append*/
00790 /* */
00791 /*    Results:*/
00792 /*        ?*/
00793 /* */
00794 ret  ::imap4::multiline_concat_expr (type reqvar , type expr) {
00795     upvar 1 $reqvar req
00796     lset req end "[lindex $req end] ([string range [lindex $expr 0] 1 end]"
00797     set req [concat $req [lrange $expr 1 end]]
00798     lset req end "[lindex $req end])"
00799 }
00800 
00801 /*  imap4::simplecmd --*/
00802 /* */
00803 /*    This a general implementation for a simple implementation*/
00804 /*    of an IMAP command that just requires to call ::imap4::request*/
00805 /*    and ::imap4::getresponse.*/
00806 /* */
00807 /*    Arguments:*/
00808 /*        chan        Identifier for IMAP channel*/
00809 /*        command     IMAP command*/
00810 /*        validstates The valid states for this command*/
00811 /*        args        Arguments to the IMAP command*/
00812 /*        */
00813 ret  ::imap4::simplecmd (type chan , type command , type validstates , type args) {
00814     ::imap4::requirestate $chan $validstates
00815     set req "$command"
00816     foreach arg $args {
00817     append req " $arg"
00818     }
00819     ::imap4::request $chan $req
00820     if {[::imap4::getresponse $chan]} {
00821     return 1
00822     }
00823     return 0
00824 }
00825 
00826 /*  imap4::selectmbox --*/
00827 /* */
00828 /*    General function for mailbox selection.*/
00829 /* */
00830 /*    Arguments:*/
00831 /*        chan    Identifier for IMAP channel*/
00832 /*        cmd     IMAP command to use*/
00833 /*        mailbox Name of mailbox to use*/
00834 /* */
00835 /*    Results:*/
00836 /*        0/1     0 if successful, 1 otherwise*/
00837 /* */
00838 /*    Side Effects:*/
00839 /*        move to SELECT state if successful*/
00840 /* */
00841 ret  ::imap4::selectmbox (type chan , type cmd , type mailbox) {
00842     ::imap4::requirestate $chan AUTH
00843     
00844     # Clean info about the previous mailbox if any,
00845     # but save a copy to restore this info on error.
00846     set savedmboxinfo [array get ::imap4::mboxinfo $chan,*]
00847     array unset ::imap4::mboxinfo $chan,*
00848     ::imap4::request $chan "$cmd $mailbox"
00849     if {[::imap4::getresponse $chan]} {
00850     array set ::imap4::mboxinfo $savedmboxinfo
00851     return 1
00852     }
00853     set ::imap4::info($chan,state) SELECT
00854     # Set the new name as mbox->current.
00855     set ::imap4::mboxinfo($chan,current) $mailbox
00856     return 0
00857 }
00858 
00859 /*  imap4::tag --*/
00860 /* */
00861 /*    Return the next tag to use in IMAP requests.*/
00862 /* */
00863 /*    Arguments:*/
00864 /*        chan    Identifier for IMAP channel*/
00865 /* */
00866 /*    Results:*/
00867 /*        tag     A valid tag for an IMAP request*/
00868 /* */
00869 ret  ::imap4::tag chan (
00870     type incr ::, type imap4::, type info($, type chan,, type curtag)
00871 )
00872 
00873 # imap4::checkstate --
00874 #
00875 #   Check that the channel is in one of the specified states.
00876 #
00877 #   Arguments:
00878 #       chan    Identifier for IMAP channel
00879 #       states  List of states
00880 #
00881 #   Results:
00882 #       bool    Either 1 or 0.
00883 #   
00884 proc ::imap4::checkstate {chan states} {
00885     expr {[lsearch -exact $states $::imap4::info($chan,state)] == -1}
00886 }
00887 
00888 /*  imap4::requirestate --*/
00889 /* */
00890 /*    Asserts that the channel is in one of the specified states.*/
00891 /* */
00892 /*    Arguments:*/
00893 /*        chan    Identifier for IMAP channel*/
00894 /*        states  List of states*/
00895 /* */
00896 /*    Results:*/
00897 /*        none*/
00898 /*    */
00899 /*    Side Effects:*/
00900 /*        An error is raised if the channel is not in one*/
00901 /*        of the states in the states list.*/
00902 /* */
00903 ret  ::imap4::requirestate (type chan , type states) {
00904     if {[checkstate $chan $states]} {
00905     error "IMAP channel not in one of the following states: '$state' (current state is '$::imap4::info($chan,state)')"
00906     }
00907 }
00908 
00909 /* */
00910 /* */
00911 /*  procs for decoding IMAP responses and general processing*/
00912 /* */
00913 /* */
00914 
00915 /*  imap4::processline --*/
00916 /* */
00917 /*    Process an IMAP response line.*/
00918 /*    This function trades simplicity in IMAP commands*/
00919 /*    implementation with monolitic handling of responses.*/
00920 /*    However note that the IMAP server can reply to a command*/
00921 /*    with many different untagged info, so to have the reply*/
00922 /*    processing centralized makes this simple to handle.*/
00923 /* */
00924 /*    Arguments:*/
00925 /*        chan    Identifier for IMAP channel*/
00926 /* */
00927 /*    Results:*/
00928 /*        tag     Tag found on the line*/
00929 /* */
00930 ret  ::imap4::processline chan (
00931     type set , type literals , optional 
00932     , type while 1 , optional 
00933     # =Read a =line
00934     if ={[gets $chan =buf] == =-1 , optional 
00935         error ="IMAP unexpected =EOF from =server."
00936     
00937     , type append , type line $, type buf
00938     # , type Remove , type the , type trailing , type CR , type at , type the , type end , type of , type the , type line, , type if , type any.
00939     , type if , optional [string =index $line =end] eq ="\r" , optional 
00940         set =line [string =range $line =0 end-1]
00941     
00942     # , type Check , type if , type there , type is , type a , type literal , type to , type read, , type and , type read , type it , type if , type any.
00943     , type if , optional [regexp ={{([0-9]+)\, type s+$) $buf => length]} {
00944         # puts "Reading $length bytes of literal..."
00945         lappend literals [read $chan $length]
00946     } else {
00947         break
00948     }
00949     }
00950     set ::imap4::info($chan,lastline) $line
00951 
00952     if {$::imap4::debug} {
00953     puts "S: $line"
00954     }
00955 
00956     # Extract the tag.
00957     set idx [string first { } $line]
00958     if {$idx == -1 || $idx == 0} {
00959     ::imap4::protoerror $chan "IMAP: malformed response '$line'"
00960     }
00961     set tag [string range $line 0 [expr {$idx-1}]]
00962     set line [string range $line [expr {$idx+1}] end]
00963     # If it's just a command continuation response, return.
00964     if {$tag eq {+}} {return +}
00965     # Extract the error code, if it's a tagged line
00966     if {$tag ne {*}} {
00967     set idx [string first { } $line]
00968     if {$idx == -1 || $idx == 0} {
00969         ::imap4::protoerror $chan "IMAP: malformed response '$line'"
00970     }
00971     set code [string range $line 0 [expr {$idx-1}]]
00972     set line [string trim [string range $line [expr {$idx+1}] end]]
00973     set ::imap4::info($chan,lastcode) $code
00974     }
00975     # Extract information from the line
00976     set dirty 0
00977     switch -glob -- $line {
00978     {*\[READ-ONLY\]*} {set ::imap4::mboxinfo($chan,perm) READ-ONLY; incr dirty}
00979     {*\[READ-WRITE\]*} {set ::imap4::mboxinfo($chan,perm) READ-WRITE; incr dirty}
00980     {*\[TRYCREATE\]*} {set ::imap4::mboxinfo($chan,perm) TRYCREATE; incr dirty}
00981     {FLAGS *(*)*} {
00982         regexp {.*\((.*)\).*} $line => flags
00983          ::imap4 = ::mboxinfo($chan,flags) $flags
00984         incr dirty
00985     }
00986     {*\[PERMANENTFLAGS *(*)*\]*} {
00987         regexp {.*\[PERMANENTFLAGS \((.*)\).*\].*} $line => flags
00988          ::imap4 = ::mboxinfo($chan,permflags) $flags
00989         incr dirty
00990     }
00991     }
00992     if {!$dirty && $tag eq {*}} {
00993     /*  FIXME: match should be case insensitive.*/
00994     switch -regexp -- $line {
00995         {^[0-9]+\s+EXISTS} {
00996         regexp {^([0-9]+)\s+EXISTS} $line => ::imap4::mboxinfo($chan,exists)
00997         incr dirty
00998         }
00999         {^[0-9]+\s+RECENT} {
01000         regexp {^([0-9]+)\s+RECENT} $line => ::imap4::mboxinfo($chan,recent)
01001         incr dirty
01002         }
01003         {.*?\[UIDVALIDITY\s+[0-9]+?\]} {
01004         regexp {.*?\[UIDVALIDITY\s+([0-9]+?)\]} $line => \
01005             ::imap4::mboxinfo($chan,uidval)
01006         incr dirty
01007         }
01008         {.*?\[UNSEEN\s+[0-9]+?\]} {
01009         regexp {.*?\[UNSEEN\s+([0-9]+?)\]} $line => \
01010             ::imap4::mboxinfo($chan,unseen)
01011         incr dirty
01012         }
01013         {.*?\[UIDNEXT\s+[0-9]+?\]} {
01014         regexp {.*?\[UIDNEXT\s+([0-9]+?)\]} $line => \
01015             ::imap4::mboxinfo($chan,uidnext)
01016         incr dirty
01017         }
01018         {^[0-9]+\s+FETCH} {
01019         ret essfetchline $chan $line $literals
01020         incr dirty
01021         }
01022         (^type CAPABILITY\, type s+.*) {
01023         regexp {^CAPABILITY\s+(.*)\s*$} $line => capstring
01024         set ::imap4::info($chan,capability) [split [string toupper $capstring]]
01025         incr dirty
01026         }
01027         {^SEARCH\s*$} {
01028         /*  Search tag without list of messages. Nothing found*/
01029         /*  so we set an empty list.*/
01030          ::imap4 = ::mboxinfo($chan,found) {}
01031         }
01032         {^SEARCH\s+.*} {
01033         regexp {^SEARCH\s+(.*)\s*$} $line => foundlist
01034          ::imap4 = ::mboxinfo($chan,found) $foundlist
01035         incr dirty
01036         }
01037         default {
01038         if {$::imap4::debug} {
01039             puts "*** WARNING: unret essed server reply '$line'"
01040         }
01041         }
01042     }
01043     }
01044     if ([type string , type length [, type set ::, type imap4::, type info($, type chan,, type idle)]] && $, type dirty) {
01045     # ... Notify.
01046     }
01047     /*  if debug and no dirty and untagged line... warning: unprocessed IMAP line*/
01048     return $tag
01049 }
01050 
01051 /*  imap4::processfetchline --*/
01052 /* */
01053 /*    Process untagged FETCH lines.*/
01054 /* */
01055 /*    Arguments:*/
01056 /*        chan        Identifier for IMAP channel*/
01057 /*        line        Line to process*/
01058 /*        literals    IMAP literals (?)*/
01059 /* */
01060 /*    Results:*/
01061 /*        ?*/
01062 /* */
01063 /*    Side Effects:*/
01064 /*        Fills in the msginfo*/
01065 /* */
01066 ret  ::imap4::processfetchline (type chan , type line , type literals) {
01067     regexp -nocase {([0-9]+)\s+FETCH\s+(\(.*\))} $line => msgnum items
01068     foreach {name val} [imaptotcl items literals] {
01069     set attribname [switch -glob -- [string toupper $name] {
01070         INTERNALDATE {format internaldate}
01071         BODYSTRUCTURE {format bodystructure}
01072         {BODY\[HEADER.FIELDS*\]} {format fields}
01073         {BODY.PEEK\[HEADER.FIELDS*\]} {format fields}
01074         {BODY\[*\]} {format body}
01075         {BODY.PEEK\[*\]} {format body}
01076         HEADER {format header}
01077         RFC822.HEADER {format header}
01078         RFC822.SIZE {format size}
01079         RFC822.TEXT {format text}
01080         ENVELOPE {format envelope}
01081         FLAGS {format flags}
01082         UID {format uid}
01083         default {
01084         ::imap4::protoerror $chan "IMAP: Unknown FETCH item '$name'. Upgrade the software"
01085         }
01086     }]
01087     switch -- $attribname {
01088         fields {
01089         set last_fieldname __garbage__
01090         foreach f [split $val "\n\r"] {
01091             # Handle multi-line headers. Append to the last header
01092             # if this line starts with a tab character.
01093             if {[string is space [string index $f 0]]} {
01094             append ::imap4::msginfo($chan,$msgnum,$last_fieldname) " [string range $f 1 end]"
01095             continue
01096             }
01097             # Process the line searching for a new field.
01098             if {![string length $f]} continue
01099             if {[set fnameidx [string first ":" $f]] == -1} {
01100             ::imap4::protoerror $chan "IMAP: Not a valid RFC822 field '$f'"
01101             }
01102             set fieldname [string tolower [string range $f 0 $fnameidx]]
01103             set last_fieldname $fieldname
01104             set fieldval [string trim \
01105             [string range $f [expr {$fnameidx+1}] end]]
01106             set ::imap4::msginfo($chan,$msgnum,$fieldname) $fieldval
01107         }
01108         }
01109         default {
01110             set ::imap4::msginfo($chan,$msgnum,$attribname) $val
01111         }
01112     }
01113     #puts "$attribname -> [string range $val 0 20]"
01114     }
01115 }
01116 
01117 /*  imap4::parserange --*/
01118 /* */
01119 /*    Parse an IMAP range, store 'start' and 'end' in the*/
01120 /*    named vars. If the first number of the range is omitted,*/
01121 /*    1 is assumed. If the second number of the range is omitted,*/
01122 /*    the value of "exists" of the current mailbox is assumed.*/
01123 /* */
01124 /*    So : means all the messages.*/
01125 /* */
01126 /*    Arguments:*/
01127 /*        chan        Identifier for IMAP channel*/
01128 /*        range       range to parse*/
01129 /*        startvar    variable to store the start in*/
01130 /*        endvar      variable to store the end in*/
01131 /* */
01132 /*    Results:*/
01133 /*        none        */
01134 /* */
01135 /*    Side Effects:*/
01136 /*        results are stored in startvar and endvar*/
01137 /* */
01138 ret  ::imap4::parserange (type chan , type range , type startvar , type endvar) {
01139     upvar $startvar start $endvar end
01140     set rangelist [split $range :]
01141     switch -- [llength $rangelist] {
01142     1 {
01143         if {![string is integer $range]} {
01144         error "Invalid range"
01145         }
01146         set start $range
01147         set end $range
01148     }
01149     2 {
01150         foreach {start end} $rangelist break
01151         if {![string length $start]} {
01152         set start 1
01153         }
01154         if {![string length $end]} {
01155         set end [::imap4::mboxinfo $chan exists]
01156         }
01157         if {![string is integer $start] || ![string is integer $end]} {
01158         error "Invalid range"
01159         }
01160     }
01161     default {
01162         error "Invalid range"
01163     }
01164     }
01165 }
01166 
01167 /*  imap4::imaptotcl --*/
01168 /* */
01169 /*    Convert IMAP data into Tcl data. Consumes the part of the*/
01170 /*    string converted.*/
01171 /*    'literals' is a list with all the literals extracted*/
01172 /*    from the original line, in the same order they appeared.*/
01173 /* */
01174 /*    Arguments:*/
01175 /*        datavar     variable holding the data to parse*/
01176 /*        literalsvar variable holding the literals*/
01177 /* */
01178 /*    Results:*/
01179 /*        ?*/
01180 /*   */
01181 /*    Side Effects:*/
01182 /*        consumes data from datavar*/
01183 /*        */
01184 ret  ::imap4::imaptotcl (type datavar , type literalsvar) {
01185     upvar 1 $datavar data $literalsvar literals
01186     set data [string trim $data]
01187     switch -- [string index $data 0] {
01188     \{ {imaptotcl_literal data literals}
01189     "(" {imaptotcl_list data literals}
01190     "\"" {imaptotcl_quoted data}
01191     0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 {imaptotcl_number data}
01192     \) {imaptotcl_endlist data;# that's a trick to parse lists}
01193     default {imaptotcl_symbol data}
01194     }
01195 }
01196 
01197 # imap4::imaptotcl_literal --
01198 #
01199 #   Extract a literal
01200 #
01201 #   Arguments:
01202 #       datavar     variable holding the data to parse
01203 #       literalsvar variable holding the literals
01204 #
01205 #   Results:
01206 #       ?
01207 #
01208 #   Side Effects:
01209 #       consumes data from datavar
01210 #
01211 proc ::imap4::imaptotcl_literal {datavar literalsvar} {
01212     upvar 1 $datavar data $literalsvar literals
01213     if {![regexp {{.*?}} $data match]} {
01214     ::imap4::protoerror $chan "IMAP data format error: '$data'"
01215     }
01216     set data [string range $data [string length $match] end]
01217     set retval [lindex $literals 0]
01218     set literals [lrange $literals 1 end]
01219     return $retval
01220 }
01221 
01222 # imap4::imaptotcl_quoted --
01223 #
01224 #   Extract a quoted string
01225 #
01226 #   Arguments:
01227 #       datavar     variable holding the data to parse
01228 #   
01229 #   Results:
01230 #       string      The extracted string
01231 #
01232 #   Side Effects:
01233 #       consumes data from datavar
01234 #
01235 proc ::imap4::imaptotcl_quoted datavar {
01236     upvar 1 $datavar data
01237     if {![regexp "\\s*?(\".*?\[^\\\\\]\"|\"\")\\s*?" $data => match]} {
01238     ::imap4::protoerror $chan "IMAP data format error: '$data'"
01239     }
01240     set data [string range $data [string length $match] end]
01241     return [string range $match 1 end-1]
01242 }
01243 
01244 # imap4::imaptotcl_number --
01245 #
01246 #   Extract a number
01247 #
01248 #   Arguments:
01249 #       datavar     variable holding the data to parse
01250 #   
01251 #   Results:
01252 #       number      An integer number
01253 #
01254 #   Side Effects:
01255 #       consumes data from datavar
01256 #
01257 proc imaptotcl_number datavar {
01258     upvar 1 $datavar data
01259     if {![regexp {^[0-9]+} $data match]} {
01260     ::imap4::protoerror $chan "IMAP data format error: '$data'"
01261     }
01262     set data [string range $data [string length $match] end]
01263     return $match
01264 }
01265 
01266 # imap4::imaptotcl_symbol --
01267 #
01268 #   Extract a "symbol". Not really exists in IMAP, but there
01269 #   are named items, and this names have a strange unquoted
01270 #   syntax like BODY[HEAEDER.FIELD (From To)] and other stuff
01271 #   like that.
01272 #
01273 #   Arguments:
01274 #       datavar     variable holding the data to parse
01275 #   
01276 #   Results:
01277 #       match       The symbol found
01278 #
01279 #   Side Effects:
01280 #       consume data from datavar
01281 #
01282 proc ::imap4::imaptotcl_symbol datavar {
01283     upvar 1 $datavar data
01284     if {![regexp {([\w\.]+\[[^\[]+\]|[\w\.]+)} $data => match]} {
01285     ::imap4::protoerror $chan "IMAP data format error: '$data'"
01286     }
01287     set data [string range $data [string length $match] end]
01288     return $match
01289 }
01290 
01291 # imap4::imaptotcl_list --
01292 #
01293 #   Extract an IMAP list.
01294 #
01295 #   Arguments:
01296 #       datavar     variable holding the data to parse
01297 #       literalsvar variable holding the literals
01298 #
01299 #   Results:
01300 #       ?
01301 #
01302 #   Side Effects:
01303 #       consumes data from datavar
01304 #
01305 proc ::imap4::imaptotcl_list {datavar literalsvar} {
01306     upvar 1 $datavar data $literalsvar literals
01307     set list {}
01308     # Remove the first '(' char
01309     set data [string range $data 1 end]
01310     # Get all the elements of the list. May indirectly recurse called
01311     # by [imaptotcl].
01312     while {[string length $data]} {
01313     set ele [imaptotcl data literals]
01314     if {$ele eq {)}} {
01315         break
01316     }
01317     lappend list $ele
01318     }
01319     return $list
01320 }
01321 
01322 # imap4::imaptotcl_endlist --
01323 #
01324 #   Just extracts the ")" character alone.
01325 #   This is actually part of the list extraction work.
01326 #
01327 #   Arguments:
01328 #       datavar     variable holding the data to parse
01329 #       
01330 #   Results:
01331 #       char        The character "("
01332 #
01333 #   Side Effects:
01334 #       consumes data from datavar
01335 #
01336 proc ::imap4::imaptotcl_endlist datavar {
01337     upvar 1 $datavar data
01338     set data [string range $data 1 end]
01339     return ")"
01340 }
01341 
01342 ######################################################################
01343 #
01344 #   procs for communication with server
01345 #
01346 ######################################################################
01347 
01348 # imap4::request --
01349 #
01350 #   Write a request to the IMAP channel.
01351 #
01352 #   Arguments:
01353 #       chan    Identifier for IMAP channel
01354 #       request Request to send
01355 #
01356 #   Results:
01357 #       none
01358 #
01359 proc ::imap4::request {chan request} {
01360     set t "[::imap4::tag $chan] $request"
01361     if {$::imap4::debug} {
01362     puts "C: $t"
01363     }
01364     set ::imap4::info($chan,lastrequest) $t
01365     puts -nonewline $chan "$t\r\n"
01366     flush $chan
01367 }
01368 
01369 # imap4::multiline_request --
01370 #
01371 #   Write a multiline request. The 'request' list must contain
01372 #   parts of command and literals interleaved. Literals are at odd
01373 #   list positions (1, 3, ...).
01374 #
01375 #   Arguments:
01376 #       chan    Identifier for IMAP channel
01377 #       request request list
01378 #   
01379 #   Results:
01380 #       none
01381 #
01382 proc ::imap4::multiline_request {chan request} {
01383     lset request 0 "[::imap4::tag $chan][lindex $request 0]"
01384     set items [llength $request]
01385     foreach {line literal} $request {
01386     # Send the line
01387     if {$::imap4::debug} {
01388         puts "C: $line"
01389     }
01390     puts -nonewline $chan "$line\r\n"
01391     flush $chan
01392     incr items -1
01393     if {!$items} break
01394     # Wait for the command continuation response
01395     if {[::imap4::processline $chan] ne {+}} {
01396         ::imap4::protoerror $chan "Expected a command continuation response but got '[::imap4::lastline $chan]'"
01397     }
01398     # Send the literal
01399     if {$::imap4::debug} {
01400         puts "C> $literal"
01401     }
01402     puts -nonewline $chan $literal
01403     flush $chan
01404     incr items -1
01405     }
01406     set ::imap4::info($chan,lastrequest) $request
01407 }
01408 
01409 # imap4::getresponse --
01410 #
01411 #   Process IMAP responses. If the IMAP channel is not
01412 #   configured to raise errors on IMAP errors, returns 0
01413 #   on OK response, otherwise 1 is returned.
01414 #
01415 #   Arguments:
01416 #       chan    Identifier for IMAP channel
01417 #   
01418 #   Results:
01419 #       0/1     0 for BAD/NO , 1 for OK
01420 #
01421 #   Side Effects:
01422 #       may raise errors
01423 #
01424 proc ::imap4::getresponse chan {
01425     # Process lines until the tagged one.
01426     while {[set tag [::imap4::processline $chan]] eq {*} || $tag eq {+}} {}
01427     switch -- [::imap4::lastcode $chan] {
01428     OK {return 0}
01429     NO {
01430         if {$::imap4::info($chan,raise_on_NO)} {
01431         error "IMAP error: [::imap4::lastline $chan]"
01432         }
01433         return 1
01434     }
01435     BAD {
01436         if {$::imap4::info($chan,raise_on_BAD)} {
01437         ::imap4::protoerror $chan "IMAP error: [::imap4::lastline $chan]"
01438         }
01439         return 1
01440     }
01441     default {
01442         ::imap4::protoerror $chan "IMAP protocol error. Unknown response code '[::imap4::lastcode $chan]'"
01443     }
01444     }
01445 }
01446 
01447 
01448 ########################################################################################
01449 #
01450 #   Debug and example code
01451 #
01452 ########################################################################################
01453 
01454 
01455 
01456 # Debug mode.
01457 # This is a developers mode only that pass the control to the
01458 # programmer. Every line entered is sent verbatim to the
01459 # server (after the addition of the request identifier).
01460 # The ::imap4::debug variable is automatically set to '1' on enter.
01461 #
01462 # It's possible to execute Tcl commands starting the line
01463 # with a slash.
01464 
01465 proc ::imap4::debugmode {chan {errormsg {None}}} {
01466     set ::imap4::debugmode 1
01467     set ::imap4::debugchan $chan
01468     set welcometext [list \
01469     "------------------------ IMAP DEBUG MODE --------------------" \
01470     "IMAP Debug mode usage: Every line typed will be sent" \
01471     "verbatim to the IMAP server prefixed with a unique IMAP tag." \
01472     "To execute Tcl commands prefix the line with a / character." \
01473     "The current debugged channel is returned by the \[me\] command." \
01474     "Type ! to exit" \
01475     "Type help for more information" \
01476     "Type info to see information about the connection" \
01477     "" \
01478     "Last error: '$errormsg'" \
01479     "IMAP library version: '$imap4::version'" \
01480     "" \
01481     ]
01482     foreach l $welcometext {
01483     puts $l
01484     }
01485     ::imap4::debugmode_info $chan
01486     while 1 {
01487     puts -nonewline "imap debug> "
01488     flush stdout
01489     gets stdin line
01490     if {![string length $line]} continue
01491     if {$line eq {!}} exit
01492     if {$line eq {info}} {
01493         ::imap4::debugmode_info $chan
01494         continue
01495     }
01496     if {[string index $line 0] eq {/}} {
01497         catch {eval [string range $line 1 end]} result
01498         puts $result
01499     } else {
01500         ::imap4::request $chan $line
01501         if {[catch {::imap4::getresponse $chan} error]} {
01502         puts "--- ERROR ---\n$error\n-------------\n"
01503         }
01504     }
01505     }
01506 }
01507 
01508 # Little helper for debugmode command.
01509 proc ::imap4::debugmode_info chan {
01510     puts "Last sent request: '$imap4::info($chan,lastrequest)'"
01511     puts "Last received line: '$imap4::info($chan,lastline)'"
01512     puts ""
01513 }
01514 
01515 # Protocol error! Enter the debug mode if ::imap4::debug is true.
01516 # Otherwise just raise the error.
01517 proc ::imap4::protoerror {chan msg} {
01518     if {$::imap4::debug && !$::imap4::debugmode} {
01519     ::imap4::debugmode $chan $msg
01520     } else {
01521     error $msg
01522     }
01523 }
01524 
01525 proc ::imap4::me {} {
01526     set ::imap4::debugchan
01527 }
01528 
01529 # Other stuff to do in random order...
01530 #
01531 # proc ::imap4::idle notify-command
01532 # proc ::imap4::auth plain ...
01533 # proc ::imap4::securestauth user pass
01534 # proc ::imap4::store
01535 # proc ::imap4::logout (need to clean both msg and mailbox info arrays)
01536 # proc ::imap4::create
01537 # proc ::imap4::delete
01538 # proc ::imap4::list
01539 # ::imap4::search $chan or {flags {seen flagged}} {larger 1000}
01540 # ::imap4::search $chan from: antirez to: ...
01541 
01542 ################################################################################
01543 # Example
01544 ################################################################################
01545 
01546 set ::imap4::debug 0
01547 if {[llength $argv] < 3} {
01548     puts "Usage: imap4.tcl <servername> <username> <password> ?-debugmode?"
01549     exit
01550 }
01551 if {[llength $argv] > 3} {
01552     est ::imap4::debug 1
01553 }
01554 foreach {servername username password} $argv break
01555 
01556 # Star the connection and select the INBOX mailbox
01557 set imap [::imap4::open $servername]
01558 ::imap4::login $imap $username $password
01559 ::imap4::select $imap INBOX
01560 
01561 # Output all the information about that mailbox
01562 foreach info [::imap4::mboxinfo $imap] {
01563     puts "$info -> [::imap4::mboxinfo $imap $info]"
01564 }
01565 
01566 # Fetch from: to: and size for all the messages
01567 ::imap4::fetch $imap : from: to: size header bodystructure
01568 
01569 # Show they
01570 for {set i 1} {$i <= [::imap4::mboxinfo $imap exists]} {incr i} {
01571     puts "$i) To: [::imap4::msginfo $imap $i to: {No To: field}]"
01572     set bstruct [::imap4::msginfo $imap $i bodystructure]
01573     if {[string toupper [lindex $bstruct 0]] eq {TEXT}} {
01574     set bstruct [list $bstruct]
01575     }
01576     foreach entry $bstruct {
01577     puts "\t$entry"
01578     }
01579 }
01580 
01581 # Show all the information available about the message ID 1
01582 puts "Available info about message 1: [::imap4::msginfo $imap 1]"
01583 
01584 # Use the capability stuff
01585 ::imap4::capability $imap
01586 puts "Is able to idle? [::imap4::isableto $imap idle]"
01587 puts "Is able to jump? [::imap4::isableto $imap jump]"
01588 puts "Is able to imap4rev1? [::imap4::isableto $imap imap4rev1]"
01589 
01590 # Show the search feature.
01591 ::imap4::search $imap larger 4000 seen
01592 puts "Found messages: [::imap4::mboxinfo $imap found]"
01593 
01594 # Enter the debug mode for fun or development time
01595 ::imap4::debugmode $imap
01596 
01597 # Cleanup
01598 ::imap4::cleanup $imap
01599 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1