00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 package require Tcl 8.3
00024
00025 package provide mime 1.5.2
00026
00027 if {[catch {package require Trf 2.0}]} {
00028
00029
00030
00031
00032
00033
00034 package require base64 2.0
00035 ::major = [lindex [split [package require md5] .] 0]
00036
00037
00038
00039
00040 namespace ::mime {
00041 ret base64 (-type mode , type what -- , type chunk) {
00042 return [base64::$what $chunk]
00043 }
00044 ret quoted-printable (-type mode , type what -- , type chunk) {
00045 return [mime::qp_$what $chunk]
00046 }
00047
00048 if {$::major < 2} {
00049
00050 ret md5 (-- type string) {
00051 return [md5::md5 $string]
00052 }
00053 } else {
00054
00055 ret md5 (-- type string) {
00056 return [md5::md5 -hex $string]
00057 }
00058 }
00059 }
00060
00061 un ::major =
00062 }
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090
00091 namespace ::mime {
00092 variable mime
00093 array mime = { uid 0 cid 0 }
00094
00095
00096 variable addrtokenL [list ";" "," \
00097 "<" ">" \
00098 ":" "." \
00099 "(" ")" \
00100 "@" "\"" \
00101 "\[" "\]" \
00102 "\\"]
00103 variable addrlexemeL [list LX_SEMICOLON LX_COMMA \
00104 LX_LBRACKET LX_RBRACKET \
00105 LX_COLON LX_DOT \
00106 LX_LPAREN LX_RPAREN \
00107 LX_ATSIGN LX_QUOTE \
00108 LX_LSQUARE LX_RSQUARE \
00109 LX_QUOTE]
00110
00111
00112 variable typetokenL [list ";" "," \
00113 "<" ">" \
00114 ":" "?" \
00115 "(" ")" \
00116 "@" "\"" \
00117 "\[" "\]" \
00118 "=" "/" \
00119 "\\"]
00120 variable typelexemeL [list LX_SEMICOLON LX_COMMA \
00121 LX_LBRACKET LX_RBRACKET \
00122 LX_COLON LX_QUESTION \
00123 LX_LPAREN LX_RPAREN \
00124 LX_ATSIGN LX_QUOTE \
00125 LX_LSQUARE LX_RSQUARE \
00126 LX_EQUALS LX_SOLIDUS \
00127 LX_QUOTE]
00128
00129 encList = [list \
00130 ascii US-ASCII \
00131 big5 Big5 \
00132 cp1250 Windows-1250 \
00133 cp1251 Windows-1251 \
00134 cp1252 Windows-1252 \
00135 cp1253 Windows-1253 \
00136 cp1254 Windows-1254 \
00137 cp1255 Windows-1255 \
00138 cp1256 Windows-1256 \
00139 cp1257 Windows-1257 \
00140 cp1258 Windows-1258 \
00141 cp437 IBM437 \
00142 cp737 "" \
00143 cp775 IBM775 \
00144 cp850 IBM850 \
00145 cp852 IBM852 \
00146 cp855 IBM855 \
00147 cp857 IBM857 \
00148 cp860 IBM860 \
00149 cp861 IBM861 \
00150 cp862 IBM862 \
00151 cp863 IBM863 \
00152 cp864 IBM864 \
00153 cp865 IBM865 \
00154 cp866 IBM866 \
00155 cp869 IBM869 \
00156 cp874 "" \
00157 cp932 "" \
00158 cp936 GBK \
00159 cp949 "" \
00160 cp950 "" \
00161 dingbats "" \
00162 ebcdic "" \
00163 euc-cn EUC-CN \
00164 euc-jp EUC-JP \
00165 euc-kr EUC-KR \
00166 gb12345 GB12345 \
00167 gb1988 GB1988 \
00168 gb2312 GB2312 \
00169 iso2022 ISO-2022 \
00170 iso2022-jp ISO-2022-JP \
00171 iso2022-kr ISO-2022-KR \
00172 iso8859-1 ISO-8859-1 \
00173 iso8859-2 ISO-8859-2 \
00174 iso8859-3 ISO-8859-3 \
00175 iso8859-4 ISO-8859-4 \
00176 iso8859-5 ISO-8859-5 \
00177 iso8859-6 ISO-8859-6 \
00178 iso8859-7 ISO-8859-7 \
00179 iso8859-8 ISO-8859-8 \
00180 iso8859-9 ISO-8859-9 \
00181 iso8859-10 ISO-8859-10 \
00182 iso8859-13 ISO-8859-13 \
00183 iso8859-14 ISO-8859-14 \
00184 iso8859-15 ISO-8859-15 \
00185 iso8859-16 ISO-8859-16 \
00186 jis0201 JIS_X0201 \
00187 jis0208 JIS_C6226-1983 \
00188 jis0212 JIS_X0212-1990 \
00189 koi8-r KOI8-R \
00190 koi8-u KOI8-U \
00191 ksc5601 KS_C_5601-1987 \
00192 macCentEuro "" \
00193 macCroatian "" \
00194 macCyrillic "" \
00195 macDingbats "" \
00196 macGreek "" \
00197 macIceland "" \
00198 macJapan "" \
00199 macRoman "" \
00200 macRomania "" \
00201 macThai "" \
00202 macTurkish "" \
00203 macUkraine "" \
00204 shiftjis Shift_JIS \
00205 symbol "" \
00206 tis-620 TIS-620 \
00207 unicode "" \
00208 utf-8 UTF-8]
00209
00210 variable encodings
00211 array encodings = $encList
00212 variable reversemap
00213 foreach {enc mimeType} $encList {
00214 if {$mimeType != ""} {
00215 reversemap = ([string tolower $mimeType]) $enc
00216 }
00217 }
00218
00219 encAliasList = [list \
00220 ascii ANSI_X3.4-1968 \
00221 ascii iso-ir-6 \
00222 ascii ANSI_X3.4-1986 \
00223 ascii ISO_646.irv:1991 \
00224 ascii ASCII \
00225 ascii ISO646-US \
00226 ascii us \
00227 ascii IBM367 \
00228 ascii cp367 \
00229 cp437 cp437 \
00230 cp437 437 \
00231 cp775 cp775 \
00232 cp850 cp850 \
00233 cp850 850 \
00234 cp852 cp852 \
00235 cp852 852 \
00236 cp855 cp855 \
00237 cp855 855 \
00238 cp857 cp857 \
00239 cp857 857 \
00240 cp860 cp860 \
00241 cp860 860 \
00242 cp861 cp861 \
00243 cp861 861 \
00244 cp861 cp-is \
00245 cp862 cp862 \
00246 cp862 862 \
00247 cp863 cp863 \
00248 cp863 863 \
00249 cp864 cp864 \
00250 cp865 cp865 \
00251 cp865 865 \
00252 cp866 cp866 \
00253 cp866 866 \
00254 cp869 cp869 \
00255 cp869 869 \
00256 cp869 cp-gr \
00257 cp936 CP936 \
00258 cp936 MS936 \
00259 cp936 Windows-936 \
00260 iso8859-1 ISO_8859-1:1987 \
00261 iso8859-1 iso-ir-100 \
00262 iso8859-1 ISO_8859-1 \
00263 iso8859-1 latin1 \
00264 iso8859-1 l1 \
00265 iso8859-1 IBM819 \
00266 iso8859-1 CP819 \
00267 iso8859-2 ISO_8859-2:1987 \
00268 iso8859-2 iso-ir-101 \
00269 iso8859-2 ISO_8859-2 \
00270 iso8859-2 latin2 \
00271 iso8859-2 l2 \
00272 iso8859-3 ISO_8859-3:1988 \
00273 iso8859-3 iso-ir-109 \
00274 iso8859-3 ISO_8859-3 \
00275 iso8859-3 latin3 \
00276 iso8859-3 l3 \
00277 iso8859-4 ISO_8859-4:1988 \
00278 iso8859-4 iso-ir-110 \
00279 iso8859-4 ISO_8859-4 \
00280 iso8859-4 latin4 \
00281 iso8859-4 l4 \
00282 iso8859-5 ISO_8859-5:1988 \
00283 iso8859-5 iso-ir-144 \
00284 iso8859-5 ISO_8859-5 \
00285 iso8859-5 cyrillic \
00286 iso8859-6 ISO_8859-6:1987 \
00287 iso8859-6 iso-ir-127 \
00288 iso8859-6 ISO_8859-6 \
00289 iso8859-6 ECMA-114 \
00290 iso8859-6 ASMO-708 \
00291 iso8859-6 arabic \
00292 iso8859-7 ISO_8859-7:1987 \
00293 iso8859-7 iso-ir-126 \
00294 iso8859-7 ISO_8859-7 \
00295 iso8859-7 ELOT_928 \
00296 iso8859-7 ECMA-118 \
00297 iso8859-7 greek \
00298 iso8859-7 greek8 \
00299 iso8859-8 ISO_8859-8:1988 \
00300 iso8859-8 iso-ir-138 \
00301 iso8859-8 ISO_8859-8 \
00302 iso8859-8 hebrew \
00303 iso8859-9 ISO_8859-9:1989 \
00304 iso8859-9 iso-ir-148 \
00305 iso8859-9 ISO_8859-9 \
00306 iso8859-9 latin5 \
00307 iso8859-9 l5 \
00308 iso8859-10 iso-ir-157 \
00309 iso8859-10 l6 \
00310 iso8859-10 ISO_8859-10:1992 \
00311 iso8859-10 latin6 \
00312 iso8859-14 iso-ir-199 \
00313 iso8859-14 ISO_8859-14:1998 \
00314 iso8859-14 ISO_8859-14 \
00315 iso8859-14 latin8 \
00316 iso8859-14 iso-celtic \
00317 iso8859-14 l8 \
00318 iso8859-15 ISO_8859-15 \
00319 iso8859-15 Latin-9 \
00320 iso8859-16 iso-ir-226 \
00321 iso8859-16 ISO_8859-16:2001 \
00322 iso8859-16 ISO_8859-16 \
00323 iso8859-16 latin10 \
00324 iso8859-16 l10 \
00325 jis0201 X0201 \
00326 jis0208 iso-ir-87 \
00327 jis0208 x0208 \
00328 jis0208 JIS_X0208-1983 \
00329 jis0212 x0212 \
00330 jis0212 iso-ir-159 \
00331 ksc5601 iso-ir-149 \
00332 ksc5601 KS_C_5601-1989 \
00333 ksc5601 KSC5601 \
00334 ksc5601 korean \
00335 shiftjis MS_Kanji \
00336 utf-8 UTF8]
00337
00338 foreach {enc mimeType} $encAliasList {
00339 reversemap = ([string tolower $mimeType]) $enc
00340 }
00341
00342 namespace export initialize finalize getproperty \
00343 getheader header = \
00344 getbody \
00345 copymessage \
00346 mapencoding \
00347 reversemapencoding \
00348 parseaddress \
00349 parsedatetime \
00350 uniqueID
00351 }
00352
00353
00354
00355
00356
00357
00358
00359
00360
00361
00362
00363
00364
00365
00366
00367
00368
00369
00370
00371
00372
00373
00374
00375
00376
00377
00378
00379
00380
00381
00382
00383
00384 ret ::mime::initialize (type args) {
00385 global errorCode errorInfo
00386
00387 variable mime
00388
00389 set token [namespace current]::[incr mime(uid)]
00390 # FRINK: nocheck
00391 variable $token
00392 upvar 0 $token state
00393
00394 if {[set code [catch { eval [linsert $args 0 mime::initializeaux $token] } \
00395 result]]} {
00396 set ecode $errorCode
00397 set einfo $errorInfo
00398
00399 catch { mime::finalize $token -subordinates dynamic }
00400
00401 return -code $code -errorinfo $einfo -errorcode $ecode $result
00402 }
00403
00404 return $token
00405 }
00406
00407
00408
00409
00410
00411
00412
00413
00414
00415
00416
00417
00418
00419
00420
00421
00422
00423
00424 ret ::mime::initializeaux (type token , type args) {
00425 global errorCode errorInfo
00426 # FRINK: nocheck
00427 variable $token
00428 upvar 0 $token state
00429
00430 array set params [set state(params) ""]
00431 set state(encoding) ""
00432 set state(version) "1.0"
00433
00434 set state(header) ""
00435 set state(lowerL) ""
00436 set state(mixedL) ""
00437
00438 set state(cid) 0
00439
00440 set argc [llength $args]
00441 for {set argx 0} {$argx < $argc} {incr argx} {
00442 set option [lindex $args $argx]
00443 if {[incr argx] >= $argc} {
00444 error "missing argument to $option"
00445 }
00446 set value [lindex $args $argx]
00447
00448 switch -- $option {
00449 -canonical {
00450 set state(content) [string tolower $value]
00451 }
00452
00453 -param {
00454 if {[llength $value] != 2} {
00455 error "-param expects a key and a value, not $value"
00456 }
00457 set lower [string tolower [set mixed [lindex $value 0]]]
00458 if {[info exists params($lower)]} {
00459 error "the $mixed parameter may be specified at most once"
00460 }
00461
00462 set params($lower) [lindex $value 1]
00463 set state(params) [array get params]
00464 }
00465
00466 -encoding {
00467 switch -- [set state(encoding) [string tolower $value]] {
00468 7bit - 8bit - binary - quoted-printable - base64 {
00469 }
00470
00471 default {
00472 error "unknown value for -encoding $state(encoding)"
00473 }
00474 }
00475 }
00476
00477 -header {
00478 if {[llength $value] != 2} {
00479 error "-header expects a key and a value, not $value"
00480 }
00481 set lower [string tolower [set mixed [lindex $value 0]]]
00482 if {![string compare $lower content-type]} {
00483 error "use -canonical instead of -header $value"
00484 }
00485 if {![string compare $lower content-transfer-encoding]} {
00486 error "use -encoding instead of -header $value"
00487 }
00488 if {(![string compare $lower content-md5]) \
00489 || (![string compare $lower mime-version])} {
00490 error "don't go there..."
00491 }
00492 if {[lsearch -exact $state(lowerL) $lower] < 0} {
00493 lappend state(lowerL) $lower
00494 lappend state(mixedL) $mixed
00495 }
00496
00497 array set header $state(header)
00498 lappend header($lower) [lindex $value 1]
00499 set state(header) [array get header]
00500 }
00501
00502 -file {
00503 set state(file) $value
00504 }
00505
00506 -parts {
00507 set state(parts) $value
00508 }
00509
00510 -string {
00511 set state(string) $value
00512
00513 set state(lines) [split $value "\n"]
00514 set state(lines.count) [llength $state(lines)]
00515 set state(lines.current) 0
00516 }
00517
00518 -root {
00519 # the following are internal options
00520
00521 set state(root) $value
00522 }
00523
00524 -offset {
00525 set state(offset) $value
00526 }
00527
00528 -count {
00529 set state(count) $value
00530 }
00531
00532 -lineslist {
00533 set state(lines) $value
00534 set state(lines.count) [llength $state(lines)]
00535 set state(lines.current) 0
00536 #state(string) is needed, but will be built when required
00537 set state(string) ""
00538 }
00539
00540 default {
00541 error "unknown option $option"
00542 }
00543 }
00544 }
00545
00546 #We only want one of -file, -parts or -string:
00547 set valueN 0
00548 foreach value [list file parts string] {
00549 if {[info exists state($value)]} {
00550 set state(value) $value
00551 incr valueN
00552 }
00553 }
00554 if {$valueN != 1 && ![info exists state(lines)]} {
00555 error "specify exactly one of -file, -parts, or -string"
00556 }
00557
00558 if {[set state(canonicalP) [info exists state(content)]]} {
00559 switch -- $state(value) {
00560 file {
00561 set state(offset) 0
00562 }
00563
00564 parts {
00565 switch -glob -- $state(content) {
00566 text
00567
00568
00569
00570
00571
00572
00573
00574
00575
00576
00577
00578
00579
00580
00581
00582
00583
00584
00585
00586
00587
00588
00589
00590
00591
00592
00593
00594
00595
00596
00597
00598
00599
00600
00601
00602
00603
00604
00605
00606
00607
00608
00609
00610
00611
00612
00613
00614
00615
00616
00617
00618
00619
00620
00621
00622
00623
00624
00625
00626
00627
00628
00629
00630
00631
00632
00633
00634
00635
00636
00637
00638
00639
00640
00641
00642
00643
00644
00645
00646
00647
00648
00649
00650
00651
00652
00653
00654
00655
00656
00657
00658 ret ::mime::parsepart (type token) {
00659 # FRINK: nocheck
00660 variable $token
00661 upvar 0 $token state
00662
00663 if {[set fileP [info exists state(file)]]} {
00664 seek $state(fd) [set pos $state(offset)] start
00665 set last [expr {$state(offset)+$state(count)-1}]
00666 } else {
00667 set string $state(string)
00668 }
00669
00670 set vline ""
00671 while {1} {
00672 set blankP 0
00673 if {$fileP} {
00674 if {($pos > $last) || ([set x [gets $state(fd) line]] <= 0)} {
00675 set blankP 1
00676 } else {
00677 incr pos [expr {$x+1}]
00678 }
00679 } else {
00680
00681 if { $state(lines.current) >= $state(lines.count) } {
00682 set blankP 1
00683 set line ""
00684 } else {
00685 set line [lindex $state(lines) $state(lines.current)]
00686 incr state(lines.current)
00687 set x [string length $line]
00688 if { $x == 0 } { set blankP 1 }
00689 }
00690
00691 }
00692
00693 if {(!$blankP) && ([string last "\r" $line] == [expr {$x-1}])} {
00694
00695 set line [string range $line 0 [expr {$x-2}]]
00696 if {$x == 1} {
00697 set blankP 1
00698 }
00699 }
00700
00701 if {(!$blankP) \
00702 && (([string first " " $line] == 0) \
00703 || ([string first "\t" $line] == 0))} {
00704 append vline "\n" $line
00705 continue
00706 }
00707
00708 if {![string compare $vline ""]} {
00709 if {$blankP} {
00710 break
00711 }
00712
00713 set vline $line
00714 continue
00715 }
00716
00717 if {([set x [string first ":" $vline]] <= 0) \
00718 || (![string compare \
00719 [set mixed \
00720 [string trimright \
00721 [string range \
00722 $vline 0 [expr {$x-1}]]]] \
00723 ""])} {
00724 error "improper line in header: $vline"
00725 }
00726 set value [string trim [string range $vline [expr {$x+1}] end]]
00727 switch -- [set lower [string tolower $mixed]] {
00728 content-type {
00729 if {[info exists state(content)]} {
00730 error "multiple Content-Type fields starting with $vline"
00731 }
00732
00733 if {![catch { set x [parsetype $token $value] }]} {
00734 set state(content) [lindex $x 0]
00735 set state(params) [lindex $x 1]
00736 }
00737 }
00738
00739 content-md5 {
00740 }
00741
00742 content-transfer-encoding {
00743 if {([string compare $state(encoding) ""]) \
00744 && ([string compare $state(encoding) \
00745 [string tolower $value]])} {
00746 error "multiple Content-Transfer-Encoding fields starting with $vline"
00747 }
00748
00749 set state(encoding) [string tolower $value]
00750 }
00751
00752 mime-version {
00753 set state(version) $value
00754 }
00755
00756 default {
00757 if {[lsearch -exact $state(lowerL) $lower] < 0} {
00758 lappend state(lowerL) $lower
00759 lappend state(mixedL) $mixed
00760 }
00761
00762 array set header $state(header)
00763 lappend header($lower) $value
00764 set state(header) [array get header]
00765 }
00766 }
00767
00768 if {$blankP} {
00769 break
00770 }
00771 set vline $line
00772 }
00773
00774 if {![info exists state(content)]} {
00775 set state(content) text/plain
00776 set state(params) [list charset us-ascii]
00777 }
00778
00779 if {![string match multipart
00780
00781
00782
00783
00784
00785
00786
00787
00788
00789
00790
00791
00792
00793
00794
00795
00796
00797
00798
00799
00800
00801
00802
00803
00804
00805
00806
00807
00808
00809
00810
00811
00812
00813
00814
00815
00816
00817
00818
00819
00820
00821
00822
00823
00824
00825
00826
00827
00828
00829
00830
00831
00832
00833
00834
00835
00836
00837
00838
00839
00840
00841
00842
00843
00844
00845
00846
00847
00848
00849
00850
00851
00852
00853
00854
00855
00856
00857
00858
00859
00860
00861
00862
00863
00864
00865
00866
00867
00868
00869
00870
00871
00872
00873
00874
00875
00876
00877
00878
00879
00880
00881
00882
00883
00884
00885
00886
00887
00888
00889
00890
00891
00892
00893
00894
00895
00896
00897
00898
00899
00900
00901
00902
00903
00904
00905
00906
00907
00908
00909
00910
00911
00912
00913
00914
00915
00916
00917
00918
00919
00920
00921
00922 ret ::mime::parsetype (type token , type string) {
00923 global errorCode errorInfo
00924 # FRINK: nocheck
00925 variable $token
00926 upvar 0 $token state
00927
00928 variable typetokenL
00929 variable typelexemeL
00930
00931 set state(input) $string
00932 set state(buffer) ""
00933 set state(lastC) LX_END
00934 set state(comment) ""
00935 set state(tokenL) $typetokenL
00936 set state(lexemeL) $typelexemeL
00937
00938 set code [catch { mime::parsetypeaux $token $string } result]
00939 set ecode $errorCode
00940 set einfo $errorInfo
00941
00942 unset state(input) \
00943 state(buffer) \
00944 state(lastC) \
00945 state(comment) \
00946 state(tokenL) \
00947 state(lexemeL)
00948
00949 return -code $code -errorinfo $einfo -errorcode $ecode $result
00950 }
00951
00952
00953
00954
00955
00956
00957
00958
00959
00960
00961
00962
00963
00964
00965 ret ::mime::parsetypeaux (type token , type string) {
00966 # FRINK: nocheck
00967 variable $token
00968 upvar 0 $token state
00969
00970 if {[string compare [parselexeme $token] LX_ATOM]} {
00971 error [format "expecting type (found %s)" $state(buffer)]
00972 }
00973 set type [string tolower $state(buffer)]
00974
00975 switch -- [parselexeme $token] {
00976 LX_SOLIDUS {
00977 }
00978
00979 LX_END {
00980 if {[string compare $type message]} {
00981 error "expecting type/subtype (found $type)"
00982 }
00983
00984 return [list message/rfc822 ""]
00985 }
00986
00987 default {
00988 error [format "expecting \"/\" (found %s)" $state(buffer)]
00989 }
00990 }
00991
00992 if {[string compare [parselexeme $token] LX_ATOM]} {
00993 error [format "expecting subtype (found %s)" $state(buffer)]
00994 }
00995 append type [string tolower /$state(buffer)]
00996
00997 array set params ""
00998 while {1} {
00999 switch -- [parselexeme $token] {
01000 LX_END {
01001 return [list $type [array get params]]
01002 }
01003
01004 LX_SEMICOLON {
01005 }
01006
01007 default {
01008 error [format "expecting \";\" (found %s)" $state(buffer)]
01009 }
01010 }
01011
01012 switch -- [parselexeme $token] {
01013 LX_END {
01014 return [list $type [array get params]]
01015 }
01016
01017 LX_ATOM {
01018 }
01019
01020 default {
01021 error [format "expecting attribute (found %s)" $state(buffer)]
01022 }
01023 }
01024
01025 set attribute [string tolower $state(buffer)]
01026
01027 if {[string compare [parselexeme $token] LX_EQUALS]} {
01028 error [format "expecting \"=\" (found %s)" $state(buffer)]
01029 }
01030
01031 switch -- [parselexeme $token] {
01032 LX_ATOM {
01033 }
01034
01035 LX_QSTRING {
01036 set state(buffer) \
01037 [string range $state(buffer) 1 \
01038 [expr {[string length $state(buffer)]-2}]]
01039 }
01040
01041 default {
01042 error [format "expecting value (found %s)" $state(buffer)]
01043 }
01044 }
01045 set params($attribute) $state(buffer)
01046 }
01047 }
01048
01049
01050
01051
01052
01053
01054
01055
01056
01057
01058
01059
01060
01061
01062
01063
01064
01065 ret ::mime::finalize (type token , type args) {
01066 # FRINK: nocheck
01067 variable $token
01068 upvar 0 $token state
01069
01070 array set options [list -subordinates dynamic]
01071 array set options $args
01072
01073 switch -- $options(-subordinates) {
01074 all {
01075 if {![string compare $state(value) parts]} {
01076 foreach part $state(parts) {
01077 eval [linsert $args 0 mime::finalize $part]
01078 }
01079 }
01080 }
01081
01082 dynamic {
01083 for {set cid $state(cid)} {$cid > 0} {incr cid -1} {
01084 eval [linsert $args 0 mime::finalize $token-$cid]
01085 }
01086 }
01087
01088 none {
01089 }
01090
01091 default {
01092 error "unknown value for -subordinates $options(-subordinates)"
01093 }
01094 }
01095
01096 foreach name [array names state] {
01097 unset state($name)
01098 }
01099 # FRINK: nocheck
01100 unset $token
01101 }
01102
01103
01104
01105
01106
01107
01108
01109
01110
01111
01112
01113
01114
01115
01116
01117
01118
01119
01120
01121
01122
01123
01124
01125
01126
01127
01128
01129
01130
01131
01132
01133
01134 ret ::mime::getproperty (type token , optional property ="") {
01135 # FRINK: nocheck
01136 variable $token
01137 upvar 0 $token state
01138
01139 switch -- $property {
01140 "" {
01141 array set properties [list content $state(content) \
01142 encoding $state(encoding) \
01143 params $state(params) \
01144 size [getsize $token]]
01145 if {[info exists state(parts)]} {
01146 set properties(parts) $state(parts)
01147 }
01148
01149 return [array get properties]
01150 }
01151
01152 -names {
01153 set names [list content encoding params]
01154 if {[info exists state(parts)]} {
01155 lappend names parts
01156 }
01157
01158 return $names
01159 }
01160
01161 content
01162 -
01163 encoding
01164 -
01165 params {
01166 return $state($property)
01167 }
01168
01169 parts {
01170 if {![info exists state(parts)]} {
01171 error "MIME part is a leaf"
01172 }
01173
01174 return $state(parts)
01175 }
01176
01177 size {
01178 return [getsize $token]
01179 }
01180
01181 default {
01182 error "unknown property $property"
01183 }
01184 }
01185 }
01186
01187
01188
01189
01190
01191
01192
01193
01194
01195
01196
01197 ret ::mime::getsize (type token) {
01198 # FRINK: nocheck
01199 variable $token
01200 upvar 0 $token state
01201
01202 switch -- $state(value)/$state(canonicalP) {
01203 file/0 {
01204 set size $state(count)
01205 }
01206
01207 file/1 {
01208 return [file size $state(file)]
01209 }
01210
01211 parts/0
01212 -
01213 parts/1 {
01214 set size 0
01215 foreach part $state(parts) {
01216 incr size [getsize $part]
01217 }
01218
01219 return $size
01220 }
01221
01222 string/0 {
01223 set size [string length $state(string)]
01224 }
01225
01226 string/1 {
01227 return [string length $state(string)]
01228 }
01229 default {
01230 error "Unknown combination \"$state(value)/$state(canonicalP)\""
01231 }
01232 }
01233
01234 if {![string compare $state(encoding) base64]} {
01235 set size [expr {($size*3+2)/4}]
01236 }
01237
01238 return $size
01239 }
01240
01241
01242
01243
01244
01245
01246
01247
01248
01249
01250
01251
01252
01253
01254
01255
01256
01257
01258
01259
01260
01261
01262
01263
01264
01265 ret ::mime::getheader (type token , optional key ="") {
01266 # FRINK: nocheck
01267 variable $token
01268 upvar 0 $token state
01269
01270 array set header $state(header)
01271 switch -- $key {
01272 "" {
01273 set result ""
01274 foreach lower $state(lowerL) mixed $state(mixedL) {
01275 lappend result $mixed $header($lower)
01276 }
01277 return $result
01278 }
01279
01280 -names {
01281 return $state(mixedL)
01282 }
01283
01284 default {
01285 set lower [string tolower [set mixed $key]]
01286
01287 if {![info exists header($lower)]} {
01288 error "key $mixed not in header"
01289 }
01290 return $header($lower)
01291 }
01292 }
01293 }
01294
01295
01296
01297
01298
01299
01300
01301
01302
01303
01304
01305
01306
01307
01308
01309
01310
01311
01312
01313
01314
01315
01316
01317
01318
01319
01320
01321
01322
01323
01324 ret ::mime::setheader (type token , type key , type value , type args) {
01325 # FRINK: nocheck
01326 variable $token
01327 upvar 0 $token state
01328
01329 array set options [list -mode write]
01330 array set options $args
01331
01332 switch -- [set lower [string tolower $key]] {
01333 content-md5
01334 -
01335 content-type
01336 -
01337 content-transfer-encoding
01338 -
01339 mime-version {
01340 error "key $key may not be set"
01341 }
01342 default {# Skip key}
01343 }
01344
01345 array set header $state(header)
01346 if {[set x [lsearch -exact $state(lowerL) $lower]] < 0} {
01347 if {![string compare $options(-mode) delete]} {
01348 error "key $key not in header"
01349 }
01350
01351 lappend state(lowerL) $lower
01352 lappend state(mixedL) $key
01353
01354 set result ""
01355 } else {
01356 set result $header($lower)
01357 }
01358 switch -- $options(-mode) {
01359 append {
01360 lappend header($lower) $value
01361 }
01362
01363 delete {
01364 unset header($lower)
01365 set state(lowerL) [lreplace $state(lowerL) $x $x]
01366 set state(mixedL) [lreplace $state(mixedL) $x $x]
01367 }
01368
01369 write {
01370 set header($lower) [list $value]
01371 }
01372
01373 default {
01374 error "unknown value for -mode $options(-mode)"
01375 }
01376 }
01377
01378 set state(header) [array get header]
01379
01380 return $result
01381 }
01382
01383
01384
01385
01386
01387
01388
01389
01390
01391
01392
01393
01394
01395
01396
01397
01398
01399
01400
01401
01402
01403
01404
01405
01406
01407
01408
01409
01410
01411
01412
01413
01414
01415
01416
01417
01418 ret ::mime::getbody (type token , type args) {
01419 global errorCode errorInfo
01420 # FRINK: nocheck
01421 variable $token
01422 upvar 0 $token state
01423
01424 set decode 0
01425 if {[set pos [lsearch -exact $args -decode]] >= 0} {
01426 set decode 1
01427 set args [lreplace $args $pos $pos]
01428 }
01429
01430 array set options [list -command [list mime::getbodyaux $token] \
01431 -blocksize 4096]
01432 array set options $args
01433 if {$options(-blocksize) < 1} {
01434 error "-blocksize expects a positive integer, not $options(-blocksize)"
01435 }
01436
01437 set code 0
01438 set ecode ""
01439 set einfo ""
01440
01441 switch -- $state(value)/$state(canonicalP) {
01442 file/0 {
01443 set fd [open $state(file) { RDONLY }]
01444
01445 set code [catch {
01446 fconfigure $fd -translation binary
01447 seek $fd [set pos $state(offset)] start
01448 set last [expr {$state(offset)+$state(count)-1}]
01449
01450 set fragment ""
01451 while {$pos <= $last} {
01452 if {[set cc [expr {($last-$pos)+1}]] \
01453 > $options(-blocksize)} {
01454 set cc $options(-blocksize)
01455 }
01456 incr pos [set len \
01457 [string length [set chunk [read $fd $cc]]]]
01458 switch -exact -- $state(encoding) {
01459 base64
01460 -
01461 quoted-printable {
01462 if {([set x [string last "\n" $chunk]] > 0) \
01463 && ($x+1 != $len)} {
01464 set chunk [string range $chunk 0 $x]
01465 seek $fd [incr pos [expr {($x+1)-$len}]] start
01466 }
01467 set chunk [$state(encoding) -mode decode \
01468 -- $chunk]
01469 }
01470 7bit - 8bit - binary - "" {
01471 # Bugfix for [#477088]
01472 # Go ahead, leave chunk alone
01473 }
01474 default {
01475 error "Can't handle content encoding \"$state(encoding)\""
01476 }
01477 }
01478 append fragment $chunk
01479
01480 set cc [expr {$options(-blocksize)-1}]
01481 while {[string length $fragment] > $options(-blocksize)} {
01482 uplevel #0 $options(-command) \
01483 [list data \
01484 [string range $fragment 0 $cc]]
01485
01486 set fragment [string range \
01487 $fragment $options(-blocksize) \
01488 end]
01489 }
01490 }
01491 if {[string length $fragment] > 0} {
01492 uplevel #0 $options(-command) [list data $fragment]
01493 }
01494 } result]
01495 set ecode $errorCode
01496 set einfo $errorInfo
01497
01498 catch { close $fd }
01499 }
01500
01501 file/1 {
01502 set fd [open $state(file) { RDONLY }]
01503
01504 set code [catch {
01505 fconfigure $fd -translation binary
01506
01507 while {[string length \
01508 [set fragment \
01509 [read $fd $options(-blocksize)]]] > 0} {
01510 uplevel #0 $options(-command) [list data $fragment]
01511 }
01512 } result]
01513 set ecode $errorCode
01514 set einfo $errorInfo
01515
01516 catch { close $fd }
01517 }
01518
01519 parts/0
01520 -
01521 parts/1 {
01522 error "MIME part isn't a leaf"
01523 }
01524
01525 string/0
01526 -
01527 string/1 {
01528 switch -- $state(encoding)/$state(canonicalP) {
01529 base64/0
01530 -
01531 quoted-printable/0 {
01532 set fragment [$state(encoding) -mode decode \
01533 -- $state(string)]
01534 }
01535
01536 default {
01537 # Not a bugfix for [#477088], but clarification
01538 # This handles no-encoding, 7bit, 8bit, and binary.
01539 set fragment $state(string)
01540 }
01541 }
01542
01543 set code [catch {
01544 set cc [expr {$options(-blocksize)-1}]
01545 while {[string length $fragment] > $options(-blocksize)} {
01546 uplevel #0 $options(-command) \
01547 [list data [string range $fragment 0 $cc]]
01548
01549 set fragment [string range $fragment \
01550 $options(-blocksize) end]
01551 }
01552 if {[string length $fragment] > 0} {
01553 uplevel #0 $options(-command) [list data $fragment]
01554 }
01555 } result]
01556 set ecode $errorCode
01557 set einfo $errorInfo
01558 }
01559 default {
01560 error "Unknown combination \"$state(value)/$state(canonicalP)\""
01561 }
01562 }
01563
01564 set code [catch {
01565 if {$code} {
01566 uplevel #0 $options(-command) [list error $result]
01567 } else {
01568 uplevel #0 $options(-command) [list end]
01569 }
01570 } result]
01571 set ecode $errorCode
01572 set einfo $errorInfo
01573
01574 if {$code} {
01575 return -code $code -errorinfo $einfo -errorcode $ecode $result
01576 }
01577
01578 if {$decode} {
01579 array set params [mime::getproperty $token params]
01580
01581 if {[info exists params(charset)]} {
01582 set charset $params(charset)
01583 } else {
01584 set charset US-ASCII
01585 }
01586
01587 set enc [reversemapencoding $charset]
01588 if {$enc != ""} {
01589 set result [::encoding convertfrom $enc $result]
01590 } else {
01591 return -code error "-decode failed: can't reversemap charset $charset"
01592 }
01593 }
01594
01595 return $result
01596 }
01597
01598
01599
01600
01601
01602
01603
01604
01605
01606
01607
01608
01609
01610
01611
01612
01613
01614
01615 ret ::mime::getbodyaux (type token , type reason , optional fragment ="") {
01616 # FRINK: nocheck
01617 variable $token
01618 upvar 0 $token state
01619
01620 switch -- $reason {
01621 data {
01622 append state(getbody) $fragment
01623 return ""
01624 }
01625
01626 end {
01627 if {[info exists state(getbody)]} {
01628 set result $state(getbody)
01629 unset state(getbody)
01630 } else {
01631 set result ""
01632 }
01633
01634 return $result
01635 }
01636
01637 error {
01638 catch { unset state(getbody) }
01639 error $reason
01640 }
01641
01642 default {
01643 error "Unknown reason \"$reason\""
01644 }
01645 }
01646 }
01647
01648
01649
01650
01651
01652
01653
01654
01655
01656
01657
01658
01659
01660
01661
01662
01663 ret ::mime::copymessage (type token , type channel) {
01664 global errorCode errorInfo
01665 # FRINK: nocheck
01666 variable $token
01667 upvar 0 $token state
01668
01669 set openP [info exists state(fd)]
01670
01671 set code [catch { mime::copymessageaux $token $channel } result]
01672 set ecode $errorCode
01673 set einfo $errorInfo
01674
01675 if {(!$openP) && ([info exists state(fd)])} {
01676 if {![info exists state(root)]} {
01677 catch { close $state(fd) }
01678 }
01679 unset state(fd)
01680 }
01681
01682 return -code $code -errorinfo $einfo -errorcode $ecode $result
01683 }
01684
01685
01686
01687
01688
01689
01690
01691
01692
01693
01694
01695
01696
01697 ret ::mime::copymessageaux (type token , type channel) {
01698 # FRINK: nocheck
01699 variable $token
01700 upvar 0 $token state
01701
01702 array set header $state(header)
01703
01704 if {[string compare $state(version) ""]} {
01705 puts $channel "MIME-Version: $state(version)"
01706 }
01707 foreach lower $state(lowerL) mixed $state(mixedL) {
01708 foreach value $header($lower) {
01709 puts $channel "$mixed: $value"
01710 }
01711 }
01712 if {(!$state(canonicalP)) \
01713 && ([string compare [set encoding $state(encoding)] ""])} {
01714 puts $channel "Content-Transfer-Encoding: $encoding"
01715 }
01716
01717 puts -nonewline $channel "Content-Type: $state(content)"
01718 set boundary ""
01719 foreach {k v} $state(params) {
01720 if {![string compare $k boundary]} {
01721 set boundary $v
01722 }
01723
01724 puts -nonewline $channel ";\n $k=\"$v\""
01725 }
01726
01727 set converter ""
01728 set encoding ""
01729 if {[string compare $state(value) parts]} {
01730 puts $channel ""
01731
01732 if {$state(canonicalP)} {
01733 if {![string compare [set encoding $state(encoding)] ""]} {
01734 set encoding [encoding $token]
01735 }
01736 if {[string compare $encoding ""]} {
01737 puts $channel "Content-Transfer-Encoding: $encoding"
01738 }
01739 switch -- $encoding {
01740 base64
01741 -
01742 quoted-printable {
01743 set converter $encoding
01744 }
01745 7bit - 8bit - binary - "" {
01746 # Bugfix for [#477088], also [#539952]
01747 # Go ahead
01748 }
01749 default {
01750 error "Can't handle content encoding \"$encoding\""
01751 }
01752 }
01753 }
01754 } elseif {([string match multipart
01755
01756
01757
01758
01759
01760
01761
01762
01763
01764
01765
01766
01767
01768
01769
01770
01771
01772
01773
01774
01775
01776
01777
01778
01779
01780
01781
01782
01783
01784
01785
01786
01787
01788
01789
01790
01791
01792
01793
01794
01795
01796
01797
01798
01799
01800
01801
01802
01803
01804
01805
01806
01807
01808
01809
01810
01811
01812
01813
01814
01815
01816
01817
01818
01819
01820
01821
01822
01823
01824
01825
01826
01827
01828
01829
01830
01831
01832
01833
01834
01835
01836
01837
01838
01839
01840
01841
01842
01843
01844
01845
01846
01847
01848
01849
01850
01851
01852
01853
01854
01855
01856
01857
01858
01859
01860
01861
01862
01863
01864
01865
01866
01867
01868
01869
01870
01871
01872
01873
01874
01875
01876
01877
01878
01879
01880
01881
01882
01883
01884
01885
01886
01887
01888
01889
01890
01891
01892
01893
01894
01895
01896
01897
01898
01899
01900
01901 ret ::mime::buildmessage (type token) {
01902 global errorCode errorInfo
01903 # FRINK: nocheck
01904 variable $token
01905 upvar 0 $token state
01906
01907 set openP [info exists state(fd)]
01908
01909 set code [catch { mime::buildmessageaux $token } result]
01910 set ecode $errorCode
01911 set einfo $errorInfo
01912
01913 if {(!$openP) && ([info exists state(fd)])} {
01914 if {![info exists state(root)]} {
01915 catch { close $state(fd) }
01916 }
01917 unset state(fd)
01918 }
01919
01920 return -code $code -errorinfo $einfo -errorcode $ecode $result
01921 }
01922
01923
01924
01925
01926
01927
01928
01929
01930
01931
01932
01933
01934
01935
01936
01937 ret ::mime::buildmessageaux (type token) {
01938 # FRINK: nocheck
01939 variable $token
01940 upvar 0 $token state
01941
01942 array set header $state(header)
01943
01944 set result ""
01945 if {[string compare $state(version) ""]} {
01946 append result "MIME-Version: $state(version)\r\n"
01947 }
01948 foreach lower $state(lowerL) mixed $state(mixedL) {
01949 foreach value $header($lower) {
01950 append result "$mixed: $value\r\n"
01951 }
01952 }
01953 if {(!$state(canonicalP)) \
01954 && ([string compare [set encoding $state(encoding)] ""])} {
01955 append result "Content-Transfer-Encoding: $encoding\r\n"
01956 }
01957
01958 append result "Content-Type: $state(content)"
01959 set boundary ""
01960 foreach {k v} $state(params) {
01961 if {![string compare $k boundary]} {
01962 set boundary $v
01963 }
01964
01965 append result ";\r\n $k=\"$v\""
01966 }
01967
01968 set converter ""
01969 set encoding ""
01970 if {[string compare $state(value) parts]} {
01971 append result \r\n
01972
01973 if {$state(canonicalP)} {
01974 if {![string compare [set encoding $state(encoding)] ""]} {
01975 set encoding [encoding $token]
01976 }
01977 if {[string compare $encoding ""]} {
01978 append result "Content-Transfer-Encoding: $encoding\r\n"
01979 }
01980 switch -- $encoding {
01981 base64
01982 -
01983 quoted-printable {
01984 set converter $encoding
01985 }
01986 7bit - 8bit - binary - "" {
01987 # Bugfix for [#477088]
01988 # Go ahead
01989 }
01990 default {
01991 error "Can't handle content encoding \"$encoding\""
01992 }
01993 }
01994 }
01995 } elseif {([string match multipart
01996
01997
01998
01999
02000
02001
02002
02003
02004
02005
02006
02007
02008
02009
02010
02011
02012
02013
02014
02015
02016
02017
02018
02019
02020
02021
02022
02023
02024
02025
02026
02027
02028
02029
02030
02031
02032
02033
02034
02035
02036
02037
02038
02039
02040
02041
02042
02043
02044
02045
02046
02047
02048
02049
02050
02051
02052
02053
02054
02055
02056
02057
02058
02059
02060
02061
02062
02063
02064
02065
02066
02067
02068
02069
02070
02071
02072
02073
02074
02075
02076
02077
02078
02079
02080
02081
02082
02083
02084
02085
02086
02087
02088
02089
02090
02091
02092
02093
02094
02095
02096
02097
02098
02099
02100
02101
02102
02103
02104
02105
02106
02107
02108
02109
02110
02111
02112
02113
02114
02115
02116
02117
02118
02119
02120
02121
02122
02123
02124
02125
02126
02127
02128
02129
02130
02131
02132
02133
02134
02135
02136
02137
02138
02139
02140 ret ::mime::encoding (type token) {
02141 # FRINK: nocheck
02142 variable $token
02143 upvar 0 $token state
02144
02145 switch -glob -- $state(content) {
02146 audio
02147
02148
02149
02150
02151
02152
02153
02154
02155
02156
02157
02158
02159
02160
02161
02162
02163
02164
02165
02166
02167
02168
02169
02170
02171
02172
02173
02174
02175
02176
02177
02178
02179
02180
02181
02182
02183
02184
02185
02186
02187
02188
02189
02190
02191
02192
02193
02194
02195
02196
02197
02198
02199
02200
02201
02202
02203
02204
02205
02206
02207
02208
02209
02210
02211
02212
02213
02214
02215
02216
02217
02218
02219
02220
02221
02222
02223
02224
02225
02226
02227
02228
02229
02230
02231
02232
02233
02234
02235
02236
02237
02238
02239
02240
02241
02242
02243
02244
02245
02246
02247
02248
02249
02250 ret ::mime::encodingasciiP (type line) {
02251 foreach c [split $line ""] {
02252 switch -- $c {
02253 " " - "\t" - "\r" - "\n" {
02254 }
02255
02256 default {
02257 binary scan $c c c
02258 if {($c < 32) || ($c > 126)} {
02259 return 0
02260 }
02261 }
02262 }
02263 }
02264 if {([set r [string first "\r" $line]] < 0) \
02265 || ($r == [expr {[string length $line]-1}])} {
02266 return 1
02267 }
02268
02269 return 0
02270 }
02271
02272
02273
02274
02275
02276
02277
02278
02279
02280
02281
02282
02283
02284 ret ::mime::encodinglineP (type line) {
02285 if {([string length $line] > 76) \
02286 || ([string compare $line [string trimright $line]]) \
02287 || ([string first . $line] == 0) \
02288 || ([string first "From " $line] == 0)} {
02289 return 0
02290 }
02291
02292 return 1
02293 }
02294
02295
02296
02297
02298
02299
02300
02301
02302
02303
02304 ret ::mime::fcopy (type token , type count , optional error ="") {
02305 # FRINK: nocheck
02306 variable $token
02307 upvar 0 $token state
02308
02309 if {[string compare $error ""]} {
02310 set state(error) $error
02311 }
02312 set state(doneP) 1
02313 }
02314
02315
02316
02317
02318
02319
02320
02321
02322
02323
02324
02325
02326
02327
02328
02329
02330
02331 ret ::mime::scopy (type token , type channel , type offset , type len , type blocksize) {
02332 # FRINK: nocheck
02333 variable $token
02334 upvar 0 $token state
02335
02336 if {$len <= 0} {
02337 set state(doneP) 1
02338 fileevent $channel writable ""
02339 return
02340 }
02341
02342 if {[set cc $len] > $blocksize} {
02343 set cc $blocksize
02344 }
02345
02346 if {[catch { puts -nonewline $channel \
02347 [string range $state(string) $offset \
02348 [expr {$offset+$cc-1}]]
02349 fileevent $channel writable \
02350 [list mime::scopy $token $channel \
02351 [incr offset $cc] \
02352 [incr len -$cc] \
02353 $blocksize]
02354 } result]} {
02355 set state(error) $result
02356 set state(doneP) 1
02357 fileevent $channel writable ""
02358 }
02359 return
02360 }
02361
02362
02363
02364
02365
02366
02367
02368
02369
02370
02371
02372
02373
02374 ret ::mime::qp_encode (type string , optional encoded_word =0 , optional no_softbreak =0) {
02375 # 8.1+ improved string manipulation routines used.
02376 # Replace outlying characters, characters that would normally
02377 # be munged by EBCDIC gateways, and special Tcl characters "[\]{}
02378 # with =xx sequence
02379
02380 regsub -all -- \
02381 {[\x00-\x08\x0B-\x1E\x21-\x24\x3D\x40\x5B-\x5E\x60\x7B-\xFF]} \
02382 $string {[format =%02X [scan "\\&" %c]]} string
02383
02384 # Replace the format commands with their result
02385
02386 set string [subst -novariable $string]
02387
02388 # soft/hard newlines and other
02389 # Funky cases for SMTP compatibility
02390 set mapChars [list " \n" "=20\n" "\t\n" "=09\n" \
02391 "\n\.\n" "\n=2E\n" "\nFrom " "\n=46rom "]
02392 if {$encoded_word} {
02393 # Special processing for encoded words (RFC 2047)
02394 lappend mapChars " " "_"
02395 }
02396 set string [string map $mapChars $string]
02397
02398 # Break long lines - ugh
02399
02400 # Implementation of FR #503336
02401 if {$no_softbreak} {
02402 set result $string
02403 } else {
02404 set result ""
02405 foreach line [split $string \n] {
02406 while {[string length $line] > 72} {
02407 set chunk [string range $line 0 72]
02408 if {[regexp -- (=|=.)$ $chunk dummy end]} {
02409
02410 # Don't break in the middle of a code
02411
02412 set len [expr {72 - [string length $end]}]
02413 set chunk [string range $line 0 $len]
02414 incr len
02415 set line [string range $line $len end]
02416 } else {
02417 set line [string range $line 73 end]
02418 }
02419 append result $chunk=\n
02420 }
02421 append result $line\n
02422 }
02423
02424 # Trim off last \n, since the above code has the side-effect
02425 # of adding an extra \n to the encoded string and return the
02426 # result.
02427 set result [string range $result 0 end-1]
02428 }
02429
02430 # If the string ends in space or tab, replace with =xx
02431
02432 set lastChar [string index $result end]
02433 if {$lastChar==" "} {
02434 set result [string replace $result end end "=20"]
02435 } elseif {$lastChar=="\t"} {
02436 set result [string replace $result end end "=09"]
02437 }
02438
02439 return $result
02440 }
02441
02442
02443
02444
02445
02446
02447
02448
02449
02450
02451
02452
02453
02454 ret ::mime::qp_decode (type string , optional encoded_word =0) {
02455 # 8.1+ improved string manipulation routines used.
02456 # Special processing for encoded words (RFC 2047)
02457
02458 if {$encoded_word} {
02459 # _ == \x20, even if SPACE occupies a different code position
02460 set string [string map [list _ \u0020] $string]
02461 }
02462
02463 # smash the white-space at the ends of lines since that must've been
02464 # generated by an MUA.
02465
02466 regsub -all -- {[ \t]+\n} $string "\n" string
02467 set string [string trimright $string " \t"]
02468
02469 # Protect the backslash for later subst and
02470 # smash soft newlines, has to occur after white-space smash
02471 # and any encoded word modification.
02472
02473 set string [string map [list "\\" "\\\\" "=\n" ""] $string]
02474
02475 # Decode specials
02476
02477 regsub -all -nocase {=([a-f0-9][a-f0-9])} $string {\\u00\1} string
02478
02479 # process \u unicode mapped chars
02480
02481 return [subst -novar -nocommand $string]
02482 }
02483
02484
02485
02486
02487
02488
02489
02490
02491
02492
02493
02494
02495
02496
02497
02498
02499
02500
02501
02502
02503
02504
02505
02506
02507
02508
02509
02510
02511
02512
02513
02514
02515
02516
02517
02518
02519 ret ::mime::parseaddress (type string) {
02520 global errorCode errorInfo
02521
02522 variable mime
02523
02524 set token [namespace current]::[incr mime(uid)]
02525 # FRINK: nocheck
02526 variable $token
02527 upvar 0 $token state
02528
02529 set code [catch { mime::parseaddressaux $token $string } result]
02530 set ecode $errorCode
02531 set einfo $errorInfo
02532
02533 foreach name [array names state] {
02534 unset state($name)
02535 }
02536 # FRINK: nocheck
02537 catch { unset $token }
02538
02539 return -code $code -errorinfo $einfo -errorcode $ecode $result
02540 }
02541
02542
02543
02544
02545
02546
02547
02548
02549
02550
02551
02552
02553
02554
02555
02556
02557
02558
02559
02560
02561
02562
02563
02564
02565
02566
02567
02568
02569
02570
02571
02572
02573
02574
02575
02576 ret ::mime::parseaddressaux (type token , type string) {
02577 # FRINK: nocheck
02578 variable $token
02579 upvar 0 $token state
02580
02581 variable addrtokenL
02582 variable addrlexemeL
02583
02584 set state(input) $string
02585 set state(glevel) 0
02586 set state(buffer) ""
02587 set state(lastC) LX_END
02588 set state(tokenL) $addrtokenL
02589 set state(lexemeL) $addrlexemeL
02590
02591 set result ""
02592 while {[addr_next $token]} {
02593 if {[string compare [set tail $state(domain)] ""]} {
02594 set tail @$state(domain)
02595 } else {
02596 set tail @[info hostname]
02597 }
02598 if {[string compare [set address $state(local)] ""]} {
02599 append address $tail
02600 }
02601
02602 if {[string compare $state(phrase) ""]} {
02603 set state(phrase) [string trim $state(phrase) "\""]
02604 foreach t $state(tokenL) {
02605 if {[string first $t $state(phrase)] >= 0} {
02606 set state(phrase) \"$state(phrase)\"
02607 break
02608 }
02609 }
02610
02611 set proper "$state(phrase) <$address>"
02612 } else {
02613 set proper $address
02614 }
02615
02616 if {![string compare [set friendly $state(phrase)] ""]} {
02617 if {[string compare [set note $state(comment)] ""]} {
02618 if {[string first "(" $note] == 0} {
02619 set note [string trimleft [string range $note 1 end]]
02620 }
02621 if {[string last ")" $note] \
02622 == [set len [expr {[string length $note]-1}]]} {
02623 set note [string range $note 0 [expr {$len-1}]]
02624 }
02625 set friendly $note
02626 }
02627
02628 if {(![string compare $friendly ""]) \
02629 && ([string compare [set mbox $state(local)] ""])} {
02630 set mbox [string trim $mbox "\""]
02631
02632 if {[string first "/" $mbox] != 0} {
02633 set friendly $mbox
02634 } elseif {[string compare \
02635 [set friendly [addr_x400 $mbox PN]] \
02636 ""]} {
02637 } elseif {([string compare \
02638 [set friendly [addr_x400 $mbox S]] \
02639 ""]) \
02640 && ([string compare \
02641 [set g [addr_x400 $mbox G]] \
02642 ""])} {
02643 set friendly "$g $friendly"
02644 }
02645
02646 if {![string compare $friendly ""]} {
02647 set friendly $mbox
02648 }
02649 }
02650 }
02651 set friendly [string trim $friendly "\""]
02652
02653 lappend result [list address $address \
02654 comment $state(comment) \
02655 domain $state(domain) \
02656 error $state(error) \
02657 friendly $friendly \
02658 group $state(group) \
02659 local $state(local) \
02660 memberP $state(memberP) \
02661 phrase $state(phrase) \
02662 proper $proper \
02663 route $state(route)]
02664
02665 }
02666
02667 unset state(input) \
02668 state(glevel) \
02669 state(buffer) \
02670 state(lastC) \
02671 state(tokenL) \
02672 state(lexemeL)
02673
02674 return $result
02675 }
02676
02677
02678
02679
02680
02681
02682
02683
02684
02685
02686
02687 ret ::mime::addr_next (type token) {
02688 global errorCode errorInfo
02689 # FRINK: nocheck
02690 variable $token
02691 upvar 0 $token state
02692
02693 foreach prop {comment domain error group local memberP phrase route} {
02694 catch { unset state($prop) }
02695 }
02696
02697 switch -- [set code [catch { mime::addr_specification $token } result]] {
02698 0 {
02699 if {!$result} {
02700 return 0
02701 }
02702
02703 switch -- $state(lastC) {
02704 LX_COMMA
02705 -
02706 LX_END {
02707 }
02708 default {
02709 # catch trailing comments...
02710 set lookahead $state(input)
02711 mime::parselexeme $token
02712 set state(input) $lookahead
02713 }
02714 }
02715 }
02716
02717 7 {
02718 set state(error) $result
02719
02720 while {1} {
02721 switch -- $state(lastC) {
02722 LX_COMMA
02723 -
02724 LX_END {
02725 break
02726 }
02727
02728 default {
02729 mime::parselexeme $token
02730 }
02731 }
02732 }
02733 }
02734
02735 default {
02736 set ecode $errorCode
02737 set einfo $errorInfo
02738
02739 return -code $code -errorinfo $einfo -errorcode $ecode $result
02740 }
02741 }
02742
02743 foreach prop {comment domain error group local memberP phrase route} {
02744 if {![info exists state($prop)]} {
02745 set state($prop) ""
02746 }
02747 }
02748
02749 return 1
02750 }
02751
02752
02753
02754
02755
02756
02757
02758
02759
02760
02761
02762
02763
02764 ret ::mime::addr_specification (type token) {
02765 # FRINK: nocheck
02766 variable $token
02767 upvar 0 $token state
02768
02769 set lookahead $state(input)
02770 switch -- [parselexeme $token] {
02771 LX_ATOM
02772 -
02773 LX_QSTRING {
02774 set state(phrase) $state(buffer)
02775 }
02776
02777 LX_SEMICOLON {
02778 if {[incr state(glevel) -1] < 0} {
02779 return -code 7 "extraneous semi-colon"
02780 }
02781
02782 catch { unset state(comment) }
02783 return [addr_specification $token]
02784 }
02785
02786 LX_COMMA {
02787 catch { unset state(comment) }
02788 return [addr_specification $token]
02789 }
02790
02791 LX_END {
02792 return 0
02793 }
02794
02795 LX_LBRACKET {
02796 return [addr_routeaddr $token]
02797 }
02798
02799 LX_ATSIGN {
02800 set state(input) $lookahead
02801 return [addr_routeaddr $token 0]
02802 }
02803
02804 default {
02805 return -code 7 \
02806 [format "unexpected character at beginning (found %s)" \
02807 $state(buffer)]
02808 }
02809 }
02810
02811 switch -- [parselexeme $token] {
02812 LX_ATOM
02813 -
02814 LX_QSTRING {
02815 append state(phrase) " " $state(buffer)
02816
02817 return [addr_phrase $token]
02818 }
02819
02820 LX_LBRACKET {
02821 return [addr_routeaddr $token]
02822 }
02823
02824 LX_COLON {
02825 return [addr_group $token]
02826 }
02827
02828 LX_DOT {
02829 set state(local) "$state(phrase)$state(buffer)"
02830 unset state(phrase)
02831 mime::addr_routeaddr $token 0
02832 mime::addr_end $token
02833 }
02834
02835 LX_ATSIGN {
02836 set state(memberP) $state(glevel)
02837 set state(local) $state(phrase)
02838 unset state(phrase)
02839 mime::addr_domain $token
02840 mime::addr_end $token
02841 }
02842
02843 LX_SEMICOLON
02844 -
02845 LX_COMMA
02846 -
02847 LX_END {
02848 set state(memberP) $state(glevel)
02849 if {(![string compare $state(lastC) LX_SEMICOLON]) \
02850 && ([incr state(glevel) -1] < 0)} {
02851 return -code 7 "extraneous semi-colon"
02852 }
02853
02854 set state(local) $state(phrase)
02855 unset state(phrase)
02856 }
02857
02858 default {
02859 return -code 7 [format "expecting mailbox (found %s)" \
02860 $state(buffer)]
02861 }
02862 }
02863
02864 return 1
02865 }
02866
02867
02868
02869
02870
02871
02872
02873
02874
02875
02876
02877
02878 ret ::mime::addr_routeaddr (type token , optional checkP =1) {
02879 # FRINK: nocheck
02880 variable $token
02881 upvar 0 $token state
02882
02883 set lookahead $state(input)
02884 if {![string compare [parselexeme $token] LX_ATSIGN]} {
02885 mime::addr_route $token
02886 } else {
02887 set state(input) $lookahead
02888 }
02889
02890 mime::addr_local $token
02891
02892 switch -- $state(lastC) {
02893 LX_ATSIGN {
02894 mime::addr_domain $token
02895 }
02896
02897 LX_SEMICOLON
02898 -
02899 LX_RBRACKET
02900 -
02901 LX_COMMA
02902 -
02903 LX_END {
02904 }
02905
02906 default {
02907 return -code 7 \
02908 [format "expecting at-sign after local-part (found %s)" \
02909 $state(buffer)]
02910 }
02911 }
02912
02913 if {($checkP) && ([string compare $state(lastC) LX_RBRACKET])} {
02914 return -code 7 [format "expecting right-bracket (found %s)" \
02915 $state(buffer)]
02916 }
02917
02918 return 1
02919 }
02920
02921
02922
02923
02924
02925
02926
02927
02928
02929
02930
02931
02932
02933 ret ::mime::addr_route (type token) {
02934 # FRINK: nocheck
02935 variable $token
02936 upvar 0 $token state
02937
02938 set state(route) @
02939
02940 while {1} {
02941 switch -- [parselexeme $token] {
02942 LX_ATOM
02943 -
02944 LX_DLITERAL {
02945 append state(route) $state(buffer)
02946 }
02947
02948 default {
02949 return -code 7 \
02950 [format "expecting sub-route in route-part (found %s)" \
02951 $state(buffer)]
02952 }
02953 }
02954
02955 switch -- [parselexeme $token] {
02956 LX_COMMA {
02957 append state(route) $state(buffer)
02958 while {1} {
02959 switch -- [parselexeme $token] {
02960 LX_COMMA {
02961 }
02962
02963 LX_ATSIGN {
02964 append state(route) $state(buffer)
02965 break
02966 }
02967
02968 default {
02969 return -code 7 \
02970 [format "expecting at-sign in route (found %s)" \
02971 $state(buffer)]
02972 }
02973 }
02974 }
02975 }
02976
02977 LX_ATSIGN
02978 -
02979 LX_DOT {
02980 append state(route) $state(buffer)
02981 }
02982
02983 LX_COLON {
02984 append state(route) $state(buffer)
02985 return
02986 }
02987
02988 default {
02989 return -code 7 \
02990 [format "expecting colon to terminate route (found %s)" \
02991 $state(buffer)]
02992 }
02993 }
02994 }
02995 }
02996
02997
02998
02999
03000
03001
03002
03003
03004
03005
03006
03007
03008
03009 ret ::mime::addr_domain (type token) {
03010 # FRINK: nocheck
03011 variable $token
03012 upvar 0 $token state
03013
03014 while {1} {
03015 switch -- [parselexeme $token] {
03016 LX_ATOM
03017 -
03018 LX_DLITERAL {
03019 append state(domain) $state(buffer)
03020 }
03021
03022 default {
03023 return -code 7 \
03024 [format "expecting sub-domain in domain-part (found %s)" \
03025 $state(buffer)]
03026 }
03027 }
03028
03029 switch -- [parselexeme $token] {
03030 LX_DOT {
03031 append state(domain) $state(buffer)
03032 }
03033
03034 LX_ATSIGN {
03035 append state(local) % $state(domain)
03036 unset state(domain)
03037 }
03038
03039 default {
03040 return
03041 }
03042 }
03043 }
03044 }
03045
03046
03047
03048
03049
03050
03051
03052
03053
03054
03055
03056 ret ::mime::addr_local (type token) {
03057 # FRINK: nocheck
03058 variable $token
03059 upvar 0 $token state
03060
03061 set state(memberP) $state(glevel)
03062
03063 while {1} {
03064 switch -- [parselexeme $token] {
03065 LX_ATOM
03066 -
03067 LX_QSTRING {
03068 append state(local) $state(buffer)
03069 }
03070
03071 default {
03072 return -code 7 \
03073 [format "expecting mailbox in local-part (found %s)" \
03074 $state(buffer)]
03075 }
03076 }
03077
03078 switch -- [parselexeme $token] {
03079 LX_DOT {
03080 append state(local) $state(buffer)
03081 }
03082
03083 default {
03084 return
03085 }
03086 }
03087 }
03088 }
03089
03090
03091
03092
03093
03094
03095
03096
03097
03098
03099
03100
03101 ret ::mime::addr_phrase (type token) {
03102 # FRINK: nocheck
03103 variable $token
03104 upvar 0 $token state
03105
03106 while {1} {
03107 switch -- [parselexeme $token] {
03108 LX_ATOM
03109 -
03110 LX_QSTRING {
03111 append state(phrase) " " $state(buffer)
03112 }
03113
03114 default {
03115 break
03116 }
03117 }
03118 }
03119
03120 switch -- $state(lastC) {
03121 LX_LBRACKET {
03122 return [addr_routeaddr $token]
03123 }
03124
03125 LX_COLON {
03126 return [addr_group $token]
03127 }
03128
03129 LX_DOT {
03130 append state(phrase) $state(buffer)
03131 return [addr_phrase $token]
03132 }
03133
03134 default {
03135 return -code 7 \
03136 [format "found phrase instead of mailbox (%s%s)" \
03137 $state(phrase) $state(buffer)]
03138 }
03139 }
03140 }
03141
03142
03143
03144
03145
03146
03147
03148
03149
03150
03151
03152 ret ::mime::addr_group (type token) {
03153 # FRINK: nocheck
03154 variable $token
03155 upvar 0 $token state
03156
03157 if {[incr state(glevel)] > 1} {
03158 return -code 7 [format "nested groups not allowed (found %s)" \
03159 $state(phrase)]
03160 }
03161
03162 set state(group) $state(phrase)
03163 unset state(phrase)
03164
03165 set lookahead $state(input)
03166 while {1} {
03167 switch -- [parselexeme $token] {
03168 LX_SEMICOLON
03169 -
03170 LX_END {
03171 set state(glevel) 0
03172 return 1
03173 }
03174
03175 LX_COMMA {
03176 }
03177
03178 default {
03179 set state(input) $lookahead
03180 return [addr_specification $token]
03181 }
03182 }
03183 }
03184 }
03185
03186
03187
03188
03189
03190
03191
03192
03193
03194
03195
03196 ret ::mime::addr_end (type token) {
03197 # FRINK: nocheck
03198 variable $token
03199 upvar 0 $token state
03200
03201 switch -- $state(lastC) {
03202 LX_SEMICOLON {
03203 if {[incr state(glevel) -1] < 0} {
03204 return -code 7 "extraneous semi-colon"
03205 }
03206 }
03207
03208 LX_COMMA
03209 -
03210 LX_END {
03211 }
03212
03213 default {
03214 return -code 7 [format "junk after local@domain (found %s)" \
03215 $state(buffer)]
03216 }
03217 }
03218 }
03219
03220
03221
03222
03223
03224
03225
03226
03227
03228
03229
03230 ret ::mime::addr_x400 (type mbox , type key) {
03231 if {[set x [string first "/$key=" [string toupper $mbox]]] < 0} {
03232 return ""
03233 }
03234 set mbox [string range $mbox [expr {$x+[string length $key]+2}] end]
03235
03236 if {[set x [string first "/" $mbox]] > 0} {
03237 set mbox [string range $mbox 0 [expr {$x-1}]]
03238 }
03239
03240 return [string trim $mbox "\""]
03241 }
03242
03243
03244
03245
03246
03247
03248
03249
03250
03251
03252
03253
03254
03255
03256
03257
03258
03259
03260
03261
03262
03263
03264
03265
03266
03267
03268
03269
03270
03271
03272
03273
03274
03275
03276
03277
03278
03279
03280
03281 namespace ::mime {
03282 variable WDAYS_SHORT [list Sun Mon Tue Wed Thu Fri Sat]
03283 variable WDAYS_LONG [list Sunday Monday Tuesday Wednesday Thursday \
03284 Friday Saturday]
03285
03286
03287
03288 variable MONTHS_SHORT [list "" \
03289 Jan Feb Mar Apr May Jun \
03290 Jul Aug Sep Oct Nov Dec]
03291 variable MONTHS_LONG [list "" \
03292 January February March April May June July \
03293 August Sepember October November December]
03294 }
03295 ret ::mime::parsedatetime (type value , type property) {
03296 if {![string compare $value -now]} {
03297 set clock [clock seconds]
03298 } elseif {[regexp {^(.*) ([+-])([0-9][0-9])([0-9][0-9])$} $value \
03299 -> value zone_sign zone_hour zone_min]} {
03300 set clock [clock scan $value -gmt 1]
03301 if {[info exists zone_min]} {
03302 set zone_min [scan $zone_min %d]
03303 set zone_hour [scan $zone_hour %d]
03304 set zone [expr {60*($zone_min+60*$zone_hour)}]
03305 if {[string equal $zone_sign "+"]} {
03306 set zone -$zone
03307 }
03308 incr clock $zone
03309 }
03310 } else {
03311 set clock [clock scan $value]
03312 }
03313
03314 switch -- $property {
03315 clock {
03316 return $clock
03317 }
03318
03319 hour {
03320 set value [clock format $clock -format %H]
03321 }
03322
03323 lmonth {
03324 variable MONTHS_LONG
03325 return [lindex $MONTHS_LONG \
03326 [scan [clock format $clock -format %m] %d]]
03327 }
03328
03329 lweekday {
03330 variable WDAYS_LONG
03331 return [lindex $WDAYS_LONG [clock format $clock -format %w]]
03332 }
03333
03334 mday {
03335 set value [clock format $clock -format %d]
03336 }
03337
03338 min {
03339 set value [clock format $clock -format %M]
03340 }
03341
03342 mon {
03343 set value [clock format $clock -format %m]
03344 }
03345
03346 month {
03347 variable MONTHS_SHORT
03348 return [lindex $MONTHS_SHORT \
03349 [scan [clock format $clock -format %m] %d]]
03350 }
03351
03352 proper {
03353 set gmt [clock format $clock -format "%Y-%m-%d %H:%M:%S" \
03354 -gmt true]
03355 if {[set diff [expr {($clock-[clock scan $gmt])/60}]] < 0} {
03356 set s -
03357 set diff [expr {-($diff)}]
03358 } else {
03359 set s +
03360 }
03361 set zone [format %s%02d%02d $s [expr {$diff/60}] [expr {$diff%60}]]
03362
03363 variable WDAYS_SHORT
03364 set wday [lindex $WDAYS_SHORT [clock format $clock -format %w]]
03365 variable MONTHS_SHORT
03366 set mon [lindex $MONTHS_SHORT \
03367 [scan [clock format $clock -format %m] %d]]
03368
03369 return [clock format $clock \
03370 -format "$wday, %d $mon %Y %H:%M:%S $zone"]
03371 }
03372
03373 rclock {
03374 if {![string compare $value -now]} {
03375 return 0
03376 } else {
03377 return [expr {[clock seconds]-$clock}]
03378 }
03379 }
03380
03381 sec {
03382 set value [clock format $clock -format %S]
03383 }
03384
03385 wday {
03386 return [clock format $clock -format %w]
03387 }
03388
03389 weekday {
03390 variable WDAYS_SHORT
03391 return [lindex $WDAYS_SHORT [clock format $clock -format %w]]
03392 }
03393
03394 yday {
03395 set value [clock format $clock -format %j]
03396 }
03397
03398 year {
03399 set value [clock format $clock -format %Y]
03400 }
03401
03402 zone {
03403 set value [string trim [string map [list "\t" " "] $value]]
03404 if {[set x [string last " " $value]] < 0} {
03405 return 0
03406 }
03407 set value [string range $value [expr {$x+1}] end]
03408 switch -- [set s [string index $value 0]] {
03409 + - - {
03410 if {![string compare $s +]} {
03411 set s ""
03412 }
03413 set value [string trim [string range $value 1 end]]
03414 if {([string length $value] != 4) \
03415 || ([scan $value %2d%2d h m] != 2) \
03416 || ($h > 12) \
03417 || ($m > 59) \
03418 || (($h == 12) && ($m > 0))} {
03419 error "malformed timezone-specification: $value"
03420 }
03421 set value $s[expr {$h*60+$m}]
03422 }
03423
03424 default {
03425 set value [string toupper $value]
03426 set z1 [list UT GMT EST EDT CST CDT MST MDT PST PDT]
03427 set z2 [list 0 0 -5 -4 -6 -5 -7 -6 -8 -7]
03428 if {[set x [lsearch -exact $z1 $value]] < 0} {
03429 error "unrecognized timezone-mnemonic: $value"
03430 }
03431 set value [expr {[lindex $z2 $x]*60}]
03432 }
03433 }
03434 }
03435
03436 date2gmt
03437 -
03438 date2local
03439 -
03440 dst
03441 -
03442 sday
03443 -
03444 szone
03445 -
03446 tzone
03447 -
03448 default {
03449 error "unknown property $property"
03450 }
03451 }
03452
03453 if {![string compare [set value [string trimleft $value 0]] ""]} {
03454 set value 0
03455 }
03456 return $value
03457 }
03458
03459
03460
03461
03462
03463
03464
03465
03466
03467
03468
03469
03470
03471 ret ::mime::uniqueID () {
03472 variable mime
03473
03474 return "<[pid].[clock seconds].[incr mime(cid)]@[info hostname]>"
03475 }
03476
03477
03478
03479
03480
03481
03482
03483
03484
03485
03486
03487 ret ::mime::parselexeme (type token) {
03488 # FRINK: nocheck
03489 variable $token
03490 upvar 0 $token state
03491
03492 set state(input) [string trimleft $state(input)]
03493
03494 set state(buffer) ""
03495 if {![string compare $state(input) ""]} {
03496 set state(buffer) end-of-input
03497 return [set state(lastC) LX_END]
03498 }
03499
03500 set c [string index $state(input) 0]
03501 set state(input) [string range $state(input) 1 end]
03502
03503 if {![string compare $c "("]} {
03504 set noteP 0
03505 set quoteP 0
03506
03507 while {1} {
03508 append state(buffer) $c
03509
03510 switch -- $c/$quoteP {
03511 "(/0" {
03512 incr noteP
03513 }
03514
03515 "\\/0" {
03516 set quoteP 1
03517 }
03518
03519 ")/0" {
03520 if {[incr noteP -1] < 1} {
03521 if {[info exists state(comment)]} {
03522 append state(comment) " "
03523 }
03524 append state(comment) $state(buffer)
03525
03526 return [parselexeme $token]
03527 }
03528 }
03529
03530 default {
03531 set quoteP 0
03532 }
03533 }
03534
03535 if {![string compare [set c [string index $state(input) 0]] ""]} {
03536 set state(buffer) "end-of-input during comment"
03537 return [set state(lastC) LX_ERR]
03538 }
03539 set state(input) [string range $state(input) 1 end]
03540 }
03541 }
03542
03543 if {![string compare $c "\""]} {
03544 set firstP 1
03545 set quoteP 0
03546
03547 while {1} {
03548 append state(buffer) $c
03549
03550 switch -- $c/$quoteP {
03551 "\\/0" {
03552 set quoteP 1
03553 }
03554
03555 "\"/0" {
03556 if {!$firstP} {
03557 return [set state(lastC) LX_QSTRING]
03558 }
03559 set firstP 0
03560 }
03561
03562 default {
03563 set quoteP 0
03564 }
03565 }
03566
03567 if {![string compare [set c [string index $state(input) 0]] ""]} {
03568 set state(buffer) "end-of-input during quoted-string"
03569 return [set state(lastC) LX_ERR]
03570 }
03571 set state(input) [string range $state(input) 1 end]
03572 }
03573 }
03574
03575 if {![string compare $c "\["]} {
03576 set quoteP 0
03577
03578 while {1} {
03579 append state(buffer) $c
03580
03581 switch -- $c/$quoteP {
03582 "\\/0" {
03583 set quoteP 1
03584 }
03585
03586 "\]/0" {
03587 return [set state(lastC) LX_DLITERAL]
03588 }
03589
03590 default {
03591 set quoteP 0
03592 }
03593 }
03594
03595 if {![string compare [set c [string index $state(input) 0]] ""]} {
03596 set state(buffer) "end-of-input during domain-literal"
03597 return [set state(lastC) LX_ERR]
03598 }
03599 set state(input) [string range $state(input) 1 end]
03600 }
03601 }
03602
03603 if {[set x [lsearch -exact $state(tokenL) $c]] >= 0} {
03604 append state(buffer) $c
03605
03606 return [set state(lastC) [lindex $state(lexemeL) $x]]
03607 }
03608
03609 while {1} {
03610 append state(buffer) $c
03611
03612 switch -- [set c [string index $state(input) 0]] {
03613 "" - " " - "\t" - "\n" {
03614 break
03615 }
03616
03617 default {
03618 if {[lsearch -exact $state(tokenL) $c] >= 0} {
03619 break
03620 }
03621 }
03622 }
03623
03624 set state(input) [string range $state(input) 1 end]
03625 }
03626
03627 return [set state(lastC) LX_ATOM]
03628 }
03629
03630
03631
03632
03633
03634
03635
03636
03637
03638
03639
03640
03641
03642
03643 ret ::mime::mapencoding (type enc) {
03644
03645 variable encodings
03646
03647 if {[info exists encodings($enc)]} {
03648 return $encodings($enc)
03649 }
03650 return ""
03651 }
03652
03653
03654
03655
03656
03657
03658
03659
03660
03661
03662
03663
03664
03665 ret ::mime::reversemapencoding (type mimeType) {
03666
03667 variable reversemap
03668
03669 set lmimeType [string tolower $mimeType]
03670 if {[info exists reversemap($lmimeType)]} {
03671 return $reversemap($lmimeType)
03672 }
03673 return ""
03674 }
03675
03676
03677
03678
03679
03680
03681
03682
03683
03684
03685
03686
03687
03688
03689
03690
03691
03692 ret ::mime::word_encode (type charset , type method , type string , optional args) {
03693
03694 variable encodings
03695
03696 if {![info exists encodings($charset)]} {
03697 error "unknown charset '$charset'"
03698 }
03699
03700 if {$encodings($charset) == ""} {
03701 error "invalid charset '$charset'"
03702 }
03703
03704 if {$method != "base64" && $method != "quoted-printable"} {
03705 error "unknown method '$method', must be base64 or quoted-printable"
03706 }
03707
03708 # default to encoded and a length that won't make the Subject header to long
03709 array set options [list -charset_encoded 1 -maxlength 66]
03710 array set options $args
03711
03712 if { $options(-charset_encoded) } {
03713 set unencoded_string [::encoding convertfrom $charset $string]
03714 } else {
03715 set unencoded_string $string
03716 }
03717
03718 set string_length [string length $unencoded_string]
03719
03720 if {!$string_length} {
03721 return ""
03722 }
03723
03724 set string_bytelength [string bytelength $unencoded_string]
03725
03726 # the 7 is for =?, ?Q?, ?= delimiters of the encoded word
03727 set maxlength [expr {$options(-maxlength) - [string length $encodings($charset)] - 7}]
03728 switch -exact -- $method {
03729 base64 {
03730 if { $maxlength < 4 } {
03731 error "maxlength $options(-maxlength) too short for chosen\
03732 charset and encoding"
03733 }
03734 set count 0
03735 set maxlength [expr {($maxlength / 4) * 3}]
03736 while { $count < $string_length } {
03737 set length 0
03738 set enc_string ""
03739 while { ($length < $maxlength) && ($count < $string_length) } {
03740 set char [string range $unencoded_string $count $count]
03741 set enc_char [::encoding convertto $charset $char]
03742 if { ($length + [string length $enc_char]) > $maxlength } {
03743 set length $maxlength
03744 } else {
03745 append enc_string $enc_char
03746 incr count
03747 incr length [string length $enc_char]
03748 }
03749 }
03750 set encoded_word [string map [list \n {}] \
03751 [base64 -mode encode -- $enc_string]]
03752 append result "=?$encodings($charset)?B?$encoded_word?=\n "
03753 }
03754 # Trim off last "\n ", since the above code has the side-effect
03755 # of adding an extra "\n " to the encoded string.
03756
03757 set result [string range $result 0 end-2]
03758 }
03759 quoted-printable {
03760 if { $maxlength < 1 } {
03761 error "maxlength $options(-maxlength) too short for chosen\
03762 charset and encoding"
03763 }
03764 set count 0
03765 while { $count < $string_length } {
03766 set length 0
03767 set encoded_word ""
03768 while { ($length < $maxlength) && ($count < $string_length) } {
03769 set char [string range $unencoded_string $count $count]
03770 set enc_char [::encoding convertto $charset $char]
03771 set qp_enc_char [qp_encode $enc_char 1]
03772 set qp_enc_char_length [string length $qp_enc_char]
03773 if { $qp_enc_char_length > $maxlength } {
03774 error "maxlength $options(-maxlength) too short for chosen\
03775 charset and encoding"
03776 }
03777 if { ($length + [string length $qp_enc_char]) > $maxlength } {
03778 set length $maxlength
03779 } else {
03780 append encoded_word $qp_enc_char
03781 incr count
03782 incr length [string length $qp_enc_char]
03783 }
03784 }
03785 append result "=?$encodings($charset)?Q?$encoded_word?=\n "
03786 }
03787 # Trim off last "\n ", since the above code has the side-effect
03788 # of adding an extra "\n " to the encoded string.
03789
03790 set result [string range $result 0 end-2]
03791 }
03792 "" {
03793 # Go ahead
03794 }
03795 default {
03796 error "Can't handle content encoding \"$method\""
03797 }
03798 }
03799
03800 return $result
03801 }
03802
03803
03804
03805
03806
03807
03808
03809
03810
03811
03812
03813 ret ::mime::word_decode (type encoded) {
03814
03815 variable reversemap
03816
03817 if {[regexp -- {=\?([^?]+)\?(.)\?([^?]*)\?=} $encoded \
03818 - charset method string] != 1} {
03819 error "malformed word-encoded expression '$encoded'"
03820 }
03821
03822 set enc [reversemapencoding $charset]
03823 if {[string equal "" $enc]} {
03824 error "unknown charset '$charset'"
03825 }
03826
03827 switch -exact -- $method {
03828 b -
03829 B {
03830 set method base64
03831 }
03832 q -
03833 Q {
03834 set method quoted-printable
03835 }
03836 default {
03837 error "unknown method '$method', must be B or Q"
03838 }
03839 }
03840
03841 switch -exact -- $method {
03842 base64 {
03843 set result [base64 -mode decode -- $string]
03844 }
03845 quoted-printable {
03846 set result [qp_decode $string 1]
03847 }
03848 "" {
03849 # Go ahead
03850 }
03851 default {
03852 error "Can't handle content encoding \"$method\""
03853 }
03854 }
03855
03856 return [list $enc $method $result]
03857 }
03858
03859
03860
03861
03862
03863
03864
03865
03866
03867
03868
03869
03870 ret ::mime::field_decode (type field) {
03871 # ::mime::field_decode is broken. Here's a new version.
03872 # This code is in the public domain. Don Libes <don@libes.com>
03873
03874 # Step through a field for mime-encoded words, building a new
03875 # version with unencoded equivalents.
03876
03877 # Sorry about the grotesque regexp. Most of it is sensible. One
03878 # notable fudge: the final $ is needed because of an apparent bug
03879 # in the regexp engine where the preceding .* otherwise becomes
03880 # non-greedy - perhaps because of the earlier ".*?", sigh.
03881
03882 while {[regexp {(.*?)(=\?(?:[^?]+)\?(?:.)\?(?:[^?]*)\?=)(.*)$} $field ignore prefix encoded field]} {
03883 # don't allow whitespace between encoded words per RFC 2047
03884 if {"" != $prefix} {
03885 if {![string is space $prefix]} {
03886 append result $prefix
03887 }
03888 }
03889
03890 set decoded [word_decode $encoded]
03891 foreach {charset - string} $decoded break
03892
03893 append result [::encoding convertfrom $charset $string]
03894 }
03895
03896 append result $field
03897 return $result
03898 }
03899
03900