00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046 package require Tcl 8.4
00047 package require struct::list 1.4
00048
00049 namespace ::imap4 {
00050
00051
00052
00053
00054
00055
00056 variable info
00057 array info = {}
00058
00059
00060 variable mboxinfo
00061 array mboxinfo = {}
00062
00063
00064 variable msginfo
00065 array msginfo = {}
00066
00067
00068 variable debugmode 0
00069
00070
00071
00072
00073
00074 variable debug 1
00075
00076
00077 variable version "2004-03-07"
00078
00079 }
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090
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
00105
00106
00107
00108
00109
00110
00111
00112
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
00144
00145
00146
00147
00148
00149
00150
00151
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
00174
00175
00176
00177
00178
00179
00180
00181
00182
00183
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
00201
00202
00203
00204
00205
00206
00207
00208
00209
00210
00211
00212
00213
00214
00215
00216
00217
00218
00219
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
00268
00269
00270
00271
00272
00273
00274
00275
00276
00277
00278
00279
00280
00281
00282
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
00310
00311
00312
00313
00314
00315
00316
00317
00318
00319
00320
00321
00322
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
00364
00365
00366
00367
00368
00369
00370
00371
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
00400
00401
00402
00403
00404
00405
00406
00407
00408
00409
00410 ret ::imap4::create (type chan , type mailbox) {
00411 ::imap4::simplecmd $chan CREATE {AUTH SELECT} $mailbox
00412 }
00413
00414
00415
00416
00417
00418
00419
00420
00421
00422
00423
00424
00425 ret ::imap4::delete (type chan , type mailbox) {
00426 ::imap4::simplecmd $chan DELETE {AUTH SELECT} $mailbox
00427 }
00428
00429
00430
00431
00432
00433
00434
00435
00436
00437
00438
00439
00440
00441
00442 ret ::imap4::examine (type chan , optional mailbox =INBOX) {
00443 ::imap4::selectmbox $chan EXAMINE $mailbox
00444 }
00445
00446
00447
00448
00449
00450
00451
00452
00453
00454
00455
00456
00457
00458
00459
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
00503
00504
00505
00506
00507
00508
00509
00510
00511
00512
00513
00514
00515
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
00528
00529
00530
00531
00532
00533
00534
00535
00536
00537
00538
00539
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
00562
00563
00564
00565
00566
00567
00568
00569
00570
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
00587
00588
00589
00590
00591
00592
00593
00594
00595
00596
00597
00598 ret ::imap4::select (type chan , optional mailbox =INBOX) {
00599 ::imap4::selectmbox $chan SELECT $mailbox
00600 }
00601
00602
00603
00604
00605
00606
00607
00608
00609
00610
00611
00612
00613 ret ::imap4::subscribe (type chan , type mailbox) {
00614 ::imap4::simplecmd $chan SUBSCRIBE {AUTH SELECT} $mailbox
00615 }
00616
00617
00618
00619
00620
00621
00622
00623
00624
00625
00626
00627
00628 ret ::imap4::unsubscribe (type chan , type mailbox) {
00629 ::imap4::simplecmd $chan UNSUBSCRIBE {AUTH SELECT} $mailbox
00630 }
00631
00632
00633
00634
00635
00636
00637
00638
00639
00640
00641
00642
00643
00644
00645
00646
00647
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
00724
00725
00726
00727
00728
00729
00730
00731
00732
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
00743
00744
00745
00746
00747
00748
00749
00750
00751
00752
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
00765
00766
00767
00768
00769
00770
00771
00772
00773
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
00784
00785
00786
00787
00788
00789
00790
00791
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
00802
00803
00804
00805
00806
00807
00808
00809
00810
00811
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
00827
00828
00829
00830
00831
00832
00833
00834
00835
00836
00837
00838
00839
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
00860
00861
00862
00863
00864
00865
00866
00867
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
00889
00890
00891
00892
00893
00894
00895
00896
00897
00898
00899
00900
00901
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
00912
00913
00914
00915
00916
00917
00918
00919
00920
00921
00922
00923
00924
00925
00926
00927
00928
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
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
01029
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
01048 return $tag
01049 }
01050
01051
01052
01053
01054
01055
01056
01057
01058
01059
01060
01061
01062
01063
01064
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
01118
01119
01120
01121
01122
01123
01124
01125
01126
01127
01128
01129
01130
01131
01132
01133
01134
01135
01136
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
01168
01169
01170
01171
01172
01173
01174
01175
01176
01177
01178
01179
01180
01181
01182
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