mime.tcl

Go to the documentation of this file.
00001 /*  mime.tcl - MIME body parts*/
00002 /* */
00003 /*  (c) 1999-2000 Marshall T. Rose*/
00004 /*  (c) 2000      Brent Welch*/
00005 /*  (c) 2000      Sandeep Tamhankar*/
00006 /*  (c) 2000      Dan Kuchler*/
00007 /*  (c) 2000-2001 Eric Melski*/
00008 /*  (c) 2001      Jeff Hobbs*/
00009 /*  (c) 2001-2007 Andreas Kupries*/
00010 /*  (c) 2002-2003 David Welton*/
00011 /*  (c) 2003-2006 Pat Thoyts*/
00012 /*  (c) 2005      Benjamin Riefenstahl*/
00013 /* */
00014 /* */
00015 /*  See the file "license.terms" for information on usage and redistribution*/
00016 /*  of this file, and for a DISCLAIMER OF ALL WARRANTIES.*/
00017 /* */
00018 /*  Influenced by Borenstein's/Rose's safe-tcl (circa 1993) and Darren New's*/
00019 /*  unpublished package of 1999.*/
00020 /* */
00021 
00022 /*  new string features and inline scan are used, requiring 8.3.*/
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     /*  Fall-back to tcl-based procedures of base64 and quoted-printable encoders*/
00030     /*  Warning!*/
00031     /*  These are a fragile emulations of the more general calling sequence*/
00032     /*  that appears to work with this code here.*/
00033 
00034     package require base64 2.0
00035      ::major =  [lindex [split [package require md5] .] 0]
00036 
00037     /*  Create these commands in the mime namespace so that they*/
00038     /*  won't collide with things at the global namespace level*/
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         /*  md5 v1, result is hex string ready for use.*/
00050         ret  md5 (-- type string) {
00051         return [md5::md5 $string]
00052         }
00053     } else {
00054         /*  md5 v2, need option to get hex string*/
00055         ret  md5 (-- type string) {
00056         return [md5::md5 -hex $string]
00057         }
00058     }
00059     }
00060 
00061     un ::major = 
00062 }        
00063 
00064 /* */
00065 /*  state variables:*/
00066 /* */
00067 /*      canonicalP: input is in its canonical form*/
00068 /*      content: type/subtype*/
00069 /*      params: seralized array of key/value pairs (keys are lower-case)*/
00070 /*      encoding: transfer encoding*/
00071 /*      version: MIME-version*/
00072 /*      header: serialized array of key/value pairs (keys are lower-case)*/
00073 /*      lowerL: list of header keys, lower-case*/
00074 /*      mixedL: list of header keys, mixed-case*/
00075 /*      value: either "file", "parts", or "string"*/
00076 /* */
00077 /*      file: input file*/
00078 /*      fd: cached file-descriptor, typically for root*/
00079 /*      root: token for top-level part, for (distant) subordinates*/
00080 /*      offset: number of octets from beginning of file/string*/
00081 /*      count: length in octets of (encoded) content*/
00082 /* */
00083 /*      parts: list of bodies (tokens)*/
00084 /* */
00085 /*      string: input string*/
00086 /* */
00087 /*      cid: last child-id assigned*/
00088 /* */
00089 
00090 
00091 namespace ::mime {
00092     variable mime
00093     array  mime =  { uid 0 cid 0 }
00094 
00095 /*  822 lexemes*/
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 /*  2045 lexemes*/
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 /*  ::mime::initialize --*/
00354 /* */
00355 /*  Creates a MIME part, and returnes the MIME token for that part.*/
00356 /* */
00357 /*  Arguments:*/
00358 /*  args   Args can be any one of the following:*/
00359 /*                   ?-canonical type/subtype*/
00360 /*                   ?-param    {key value}?...*/
00361 /*                   ?-encoding value?*/
00362 /*                   ?-header   {key value}?... ?*/
00363 /*                   (-file name | -string value | -parts {token1 ... tokenN})*/
00364 /* */
00365 /*        If the -canonical option is present, then the body is in*/
00366 /*        canonical (raw) form and is found by consulting either the -file,*/
00367 /*        -string, or -part option. */
00368 /* */
00369 /*        In addition, both the -param and -header options may occur zero*/
00370 /*        or more times to specify "Content-Type" parameters (e.g.,*/
00371 /*        "charset") and header keyword/values (e.g.,*/
00372 /*        "Content-Disposition"), respectively. */
00373 /* */
00374 /*        Also, -encoding, if present, specifies the*/
00375 /*        "Content-Transfer-Encoding" when copying the body.*/
00376 /* */
00377 /*        If the -canonical option is not present, then the MIME part*/
00378 /*        contained in either the -file or the -string option is parsed,*/
00379 /*        dynamically generating subordinates as appropriate.*/
00380 /* */
00381 /*  Results:*/
00382 /*  An initialized mime token.*/
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 /*  ::mime::initializeaux --*/
00408 /* */
00409 /*  Configures the MIME token created in mime::initialize based on*/
00410 /*        the arguments that mime::initialize supports.*/
00411 /* */
00412 /*  Arguments:*/
00413 /*        token  The MIME token to configure.*/
00414 /*  args   Args can be any one of the following:*/
00415 /*                   ?-canonical type/subtype*/
00416 /*                   ?-param    {key value}?...*/
00417 /*                   ?-encoding value?*/
00418 /*                   ?-header   {key value}?... ?*/
00419 /*                   (-file name | -string value | -parts {token1 ... tokenN})*/
00420 /* */
00421 /*  Results:*/
00422 /*        Either configures the mime token, or throws an error.*/
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                     image/*
00569                         -
00570                     audio/*
00571                         -
00572                     video/* {
00573                         error "-canonical $state(content) and -parts do not mix"
00574                     }
00575     
00576                     default {
00577                         if {[string compare $state(encoding) ""]} {
00578                             error "-encoding and -parts do not mix"
00579                         }
00580                     }
00581                 }
00582             }
00583         default {# Go ahead}
00584         }
00585 
00586         if {[lsearch -exact $state(lowerL) content-id] < 0} {
00587             lappend state(lowerL) content-id
00588             lappend state(mixedL) Content-ID
00589 
00590             array set header $state(header)
00591             lappend header(content-id) [uniqueID]
00592             set state(header) [array get header]
00593         }
00594 
00595         set state(version) 1.0
00596 
00597         return
00598     }
00599 
00600     if {[string compare $state(params) ""]} {
00601         error "-param requires -canonical"
00602     }
00603     if {[string compare $state(encoding) ""]} {
00604         error "-encoding requires -canonical"
00605     }
00606     if {[string compare $state(header) ""]} {
00607         error "-header requires -canonical"
00608     }
00609     if {[info exists state(parts)]} {
00610         error "-parts requires -canonical"
00611     }
00612 
00613     if {[set fileP [info exists state(file)]]} {
00614         if {[set openP [info exists state(root)]]} {
00615         # FRINK: nocheck
00616             variable $state(root)
00617             upvar 0 $state(root) root
00618 
00619             set state(fd) $root(fd)
00620         } else {
00621             set state(root) $token
00622             set state(fd) [open $state(file) { RDONLY }]
00623             set state(offset) 0
00624             seek $state(fd) 0 end
00625             set state(count) [tell $state(fd)]
00626 
00627             fconfigure $state(fd) -translation binary
00628         }
00629     }
00630 
00631     set code [catch { mime::parsepart $token } result]
00632     set ecode $errorCode
00633     set einfo $errorInfo
00634 
00635     if {$fileP} {
00636         if {!$openP} {
00637             unset state(root)
00638             catch { close $state(fd) }
00639         }
00640         unset state(fd)
00641     }
00642 
00643     return -code $code -errorinfo $einfo -errorcode $ecode $result
00644 }
00645 
00646 /*  ::mime::parsepart --*/
00647 /* */
00648 /*        Parses the MIME headers and attempts to break up the message*/
00649 /*        into its various parts, creating a MIME token for each part.*/
00650 /* */
00651 /*  Arguments:*/
00652 /*        token  The MIME token to parse.*/
00653 /* */
00654 /*  Results:*/
00655 /*        Throws an error if it has problems parsing the MIME token,*/
00656 /*        otherwise it just sets up the appropriate variables.*/
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/* $state(content)]} {
00780         if {$fileP} {
00781             set x [tell $state(fd)]
00782             incr state(count) [expr {$state(offset)-$x}]
00783             set state(offset) $x
00784         } else {
00785         # rebuild string, this is cheap and needed by other functions    
00786         set state(string) [join [lrange $state(lines) \
00787                      $state(lines.current) end] "\n"]
00788         }
00789 
00790         if {[string match message/* $state(content)]} {
00791         # FRINK: nocheck
00792             variable [set child $token-[incr state(cid)]]
00793 
00794             set state(value) parts
00795             set state(parts) $child
00796             if {$fileP} {
00797                 mime::initializeaux $child \
00798                     -file $state(file) -root $state(root) \
00799                     -offset $state(offset) -count $state(count)
00800             } else {
00801         mime::initializeaux $child \
00802             -lineslist [lrange $state(lines) \
00803                     $state(lines.current) end] 
00804             }
00805         }
00806 
00807         return
00808     } 
00809 
00810     set state(value) parts
00811 
00812     set boundary ""
00813     foreach {k v} $state(params) {
00814         if {![string compare $k boundary]} {
00815             set boundary $v
00816             break
00817         }
00818     }
00819     if {![string compare $boundary ""]} {
00820         error "boundary parameter is missing in $state(content)"
00821     }
00822     if {![string compare [string trim $boundary] ""]} {
00823         error "boundary parameter is empty in $state(content)"
00824     }
00825 
00826     if {$fileP} {
00827         set pos [tell $state(fd)]
00828     }
00829 
00830     set inP 0
00831     set moreP 1
00832     while {$moreP} {
00833         if {$fileP} {
00834             if {$pos > $last} {
00835                  error "termination string missing in $state(content)"
00836                  set line "--$boundary--"
00837             } else {
00838               if {[set x [gets $state(fd) line]] < 0} {
00839                   error "end-of-file encountered while parsing $state(content)"
00840               }
00841            }
00842             incr pos [expr {$x+1}]
00843         } else {
00844 
00845         if { $state(lines.current) >= $state(lines.count) } {
00846         error "end-of-string encountered while parsing $state(content)"
00847         } else {
00848         set line [lindex $state(lines) $state(lines.current)]
00849         incr state(lines.current)
00850         set x [string length $line]
00851         }
00852 
00853             set x [string length $line]
00854         }
00855         if {[string last "\r" $line] == [expr {$x-1}]} {
00856             set line [string range $line 0 [expr {$x-2}]]
00857         }
00858 
00859         if {[string first "--$boundary" $line] != 0} {
00860              if {$inP && !$fileP} {
00861         lappend start $line
00862              }
00863 
00864              continue
00865         }
00866 
00867         if {!$inP} {
00868             if {![string compare $line "--$boundary"]} {
00869                 set inP 1
00870                 if {$fileP} {
00871                     set start $pos
00872                 } else {
00873             set start [list]
00874                 }
00875             }
00876 
00877             continue
00878         }
00879 
00880         if {([set moreP [string compare $line "--$boundary--"]]) \
00881                 && ([string compare $line "--$boundary"])} {
00882             if {$inP && !$fileP} {
00883         lappend start $line
00884             }
00885             continue
00886         }
00887     # FRINK: nocheck
00888         variable [set child $token-[incr state(cid)]]
00889 
00890         lappend state(parts) $child
00891 
00892         if {$fileP} {
00893             if {[set count [expr {$pos-($start+$x+3)}]] < 0} {
00894                 set count 0
00895             }
00896 
00897             mime::initializeaux $child \
00898                 -file $state(file) -root $state(root) \
00899                 -offset $start -count $count
00900 
00901             seek $state(fd) [set start $pos] start
00902         } else {
00903         mime::initializeaux $child -lineslist $start
00904             set start ""
00905         }
00906     }
00907 }
00908 
00909 /*  ::mime::parsetype --*/
00910 /* */
00911 /*        Parses the string passed in and identifies the content-type and*/
00912 /*        params strings.*/
00913 /* */
00914 /*  Arguments:*/
00915 /*        token  The MIME token to parse.*/
00916 /*        string The content-type string that should be parsed.*/
00917 /* */
00918 /*  Results:*/
00919 /*        Returns the content and params for the string as a two element*/
00920 /*        tcl list.*/
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 /*  ::mime::parsetypeaux --*/
00953 /* */
00954 /*        A helper function for mime::parsetype.  Parses the specified*/
00955 /*        string looking for the content type and params.*/
00956 /* */
00957 /*  Arguments:*/
00958 /*        token  The MIME token to parse.*/
00959 /*        string The content-type string that should be parsed.*/
00960 /* */
00961 /*  Results:*/
00962 /*        Returns the content and params for the string as a two element*/
00963 /*        tcl list.*/
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 /*  ::mime::finalize --*/
01050 /* */
01051 /*    mime::finalize destroys a MIME part.*/
01052 /* */
01053 /*    If the -subordinates option is present, it specifies which*/
01054 /*    subordinates should also be destroyed. The default value is*/
01055 /*    "dynamic".*/
01056 /* */
01057 /*  Arguments:*/
01058 /*        token  The MIME token to parse.*/
01059 /*        args   Args can be optionally be of the following form:*/
01060 /*               ?-subordinates "all" | "dynamic" | "none"?*/
01061 /* */
01062 /*  Results:*/
01063 /*        Returns an empty string.*/
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 /*  ::mime::getproperty --*/
01104 /* */
01105 /*    mime::getproperty returns the properties of a MIME part.*/
01106 /* */
01107 /*    The properties are:*/
01108 /* */
01109 /*        property    value*/
01110 /*        ========    =====*/
01111 /*        content     the type/subtype describing the content*/
01112 /*        encoding    the "Content-Transfer-Encoding"*/
01113 /*        params      a list of "Content-Type" parameters*/
01114 /*        parts       a list of tokens for the part's subordinates*/
01115 /*        size        the approximate size of the content (unencoded)*/
01116 /* */
01117 /*    The "parts" property is present only if the MIME part has*/
01118 /*    subordinates.*/
01119 /* */
01120 /*    If mime::getproperty is invoked with the name of a specific*/
01121 /*    property, then the corresponding value is returned; instead, if*/
01122 /*    -names is specified, a list of all properties is returned;*/
01123 /*    otherwise, a serialized array of properties and values is returned.*/
01124 /* */
01125 /*  Arguments:*/
01126 /*        token      The MIME token to parse.*/
01127 /*        property   One of 'content', 'encoding', 'params', 'parts', and*/
01128 /*                   'size'. Defaults to returning a serialized array of*/
01129 /*                   properties and values.*/
01130 /* */
01131 /*  Results:*/
01132 /*        Returns the properties of a MIME part*/
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 /*  ::mime::getsize --*/
01188 /* */
01189 /*     Determine the size (in bytes) of a MIME part/token*/
01190 /* */
01191 /*  Arguments:*/
01192 /*        token      The MIME token to parse.*/
01193 /* */
01194 /*  Results:*/
01195 /*        Returns the size in bytes of the MIME token.*/
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 /*  ::mime::getheader --*/
01242 /* */
01243 /*     mime::getheader returns the header of a MIME part.*/
01244 /* */
01245 /*     A header consists of zero or more key/value pairs. Each value is a*/
01246 /*     list containing one or more strings.*/
01247 /* */
01248 /*     If mime::getheader is invoked with the name of a specific key, then*/
01249 /*     a list containing the corresponding value(s) is returned; instead,*/
01250 /*     if -names is specified, a list of all keys is returned; otherwise, a*/
01251 /*     serialized array of keys and values is returned. Note that when a*/
01252 /*     key is specified (e.g., "Subject"), the list returned usually*/
01253 /*     contains exactly one string; however, some keys (e.g., "Received")*/
01254 /*     often occur more than once in the header, accordingly the list*/
01255 /*     returned usually contains more than one string.*/
01256 /* */
01257 /*  Arguments:*/
01258 /*        token      The MIME token to parse.*/
01259 /*        key        Either a key or '-names'.  If it is '-names' a list*/
01260 /*                   of all keys is returned.*/
01261 /* */
01262 /*  Results:*/
01263 /*        Returns the header of a MIME part.*/
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 /*  ::mime::setheader --*/
01296 /* */
01297 /*     mime::setheader writes, appends to, or deletes the value associated*/
01298 /*     with a key in the header.*/
01299 /* */
01300 /*     The value for -mode is one of: */
01301 /* */
01302 /*        write: the key/value is either created or overwritten (the*/
01303 /*        default);*/
01304 /* */
01305 /*        append: a new value is appended for the key (creating it as*/
01306 /*        necessary); or,*/
01307 /* */
01308 /*        delete: all values associated with the key are removed (the*/
01309 /*        "value" parameter is ignored).*/
01310 /* */
01311 /*     Regardless, mime::setheader returns the previous value associated*/
01312 /*     with the key.*/
01313 /* */
01314 /*  Arguments:*/
01315 /*        token      The MIME token to parse.*/
01316 /*        key        The name of the key whose value should be set.*/
01317 /*        value      The value for the header key to be set to.*/
01318 /*        args       An optional argument of the form:*/
01319 /*                   ?-mode "write" | "append" | "delete"?*/
01320 /* */
01321 /*  Results:*/
01322 /*        Returns previous value associated with the specified key.*/
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 /*  ::mime::getbody --*/
01384 /* */
01385 /*     mime::getbody returns the body of a leaf MIME part in canonical form.*/
01386 /* */
01387 /*     If the -command option is present, then it is repeatedly invoked*/
01388 /*     with a fragment of the body as this:*/
01389 /* */
01390 /*         uplevel #0 $callback [list "data" $fragment]*/
01391 /* */
01392 /*     (The -blocksize option, if present, specifies the maximum size of*/
01393 /*     each fragment passed to the callback.)*/
01394 /*     When the end of the body is reached, the callback is invoked as:*/
01395 /* */
01396 /*         uplevel #0 $callback "end"*/
01397 /* */
01398 /*     Alternatively, if an error occurs, the callback is invoked as:*/
01399 /* */
01400 /*         uplevel #0 $callback [list "error" reason]*/
01401 /* */
01402 /*     Regardless, the return value of the final invocation of the callback*/
01403 /*     is propagated upwards by mime::getbody.*/
01404 /* */
01405 /*     If the -command option is absent, then the return value of*/
01406 /*     mime::getbody is a string containing the MIME part's entire body.*/
01407 /* */
01408 /*  Arguments:*/
01409 /*        token      The MIME token to parse.*/
01410 /*        args       Optional arguments of the form:*/
01411 /*                   ?-decode? ?-command callback ?-blocksize octets? ?*/
01412 /* */
01413 /*  Results:*/
01414 /*        Returns a string containing the MIME part's entire body, or*/
01415 /*        if '-command' is specified, the return value of the command*/
01416 /*        is returned.*/
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 /*  ::mime::getbodyaux --*/
01599 /* */
01600 /*     Builds up the body of the message, fragment by fragment.  When*/
01601 /*     the entire message has been retrieved, it is returned.*/
01602 /* */
01603 /*  Arguments:*/
01604 /*        token      The MIME token to parse.*/
01605 /*        reason     One of 'data', 'end', or 'error'.*/
01606 /*        fragment   The section of data data fragment to extract a*/
01607 /*                   string from.*/
01608 /* */
01609 /*  Results:*/
01610 /*        Returns nothing, except when called with the 'end' argument*/
01611 /*        in which case it returns a string that contains all of the*/
01612 /*        data that 'getbodyaux' has been called with.  Will throw an*/
01613 /*        error if it is called with the reason of 'error'.*/
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 /*  ::mime::copymessage --*/
01649 /* */
01650 /*     mime::copymessage copies the MIME part to the specified channel.*/
01651 /* */
01652 /*     mime::copymessage operates synchronously, and uses fileevent to*/
01653 /*     allow asynchronous operations to proceed independently.*/
01654 /* */
01655 /*  Arguments:*/
01656 /*        token      The MIME token to parse.*/
01657 /*        channel    The channel to copy the message to.*/
01658 /* */
01659 /*  Results:*/
01660 /*        Returns nothing unless an error is thrown while the message*/
01661 /*        is being written to the channel.*/
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 /*  ::mime::copymessageaux --*/
01686 /* */
01687 /*     mime::copymessageaux copies the MIME part to the specified channel.*/
01688 /* */
01689 /*  Arguments:*/
01690 /*        token      The MIME token to parse.*/
01691 /*        channel    The channel to copy the message to.*/
01692 /* */
01693 /*  Results:*/
01694 /*        Returns nothing unless an error is thrown while the message*/
01695 /*        is being written to the channel.*/
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/* $state(content)]) \
01755                     && (![string compare $boundary ""])} {
01756 # we're doing everything in one pass...
01757         set key [clock seconds]$token[info hostname][array get state]
01758         set seqno 8
01759         while {[incr seqno -1] >= 0} {
01760             set key [md5 -- $key]
01761         }
01762         set boundary "----- =_[string trim [base64 -mode encode -- $key]]"
01763 
01764         puts $channel ";\n              boundary=\"$boundary\""
01765     } else {
01766         puts $channel ""
01767     }
01768 
01769     if {[info exists state(error)]} {
01770         unset state(error)
01771     }
01772                 
01773     switch -- $state(value) {
01774         file {
01775             set closeP 1
01776             if {[info exists state(root)]} {
01777         # FRINK: nocheck
01778                 variable $state(root)
01779                 upvar 0 $state(root) root 
01780 
01781                 if {[info exists root(fd)]} {
01782                     set fd $root(fd)
01783                     set closeP 0
01784                 } else {
01785                     set fd [set state(fd) \
01786                                 [open $state(file) { RDONLY }]]
01787                 }
01788                 set size $state(count)
01789             } else {
01790                 set fd [set state(fd) [open $state(file) { RDONLY }]]
01791         # read until eof
01792                 set size -1
01793             }
01794             seek $fd $state(offset) start
01795             if {$closeP} {
01796                 fconfigure $fd -translation binary
01797             }
01798 
01799             puts $channel ""
01800 
01801         while {($size != 0) && (![eof $fd])} {
01802         if {$size < 0 || $size > 32766} {
01803             set X [read $fd 32766]
01804         } else {
01805             set X [read $fd $size]
01806         }
01807         if {$size > 0} {
01808             set size [expr {$size - [string length $X]}]
01809         }
01810         if {[string compare $converter ""]} {
01811             puts -nonewline $channel [$converter -mode encode -- $X]
01812         } else {
01813             puts -nonewline $channel $X
01814         }
01815         }
01816 
01817             if {$closeP} {
01818                 catch { close $state(fd) }
01819                 unset state(fd)
01820             }
01821         }
01822 
01823         parts {
01824             if {(![info exists state(root)]) \
01825                     && ([info exists state(file)])} {
01826                 set state(fd) [open $state(file) { RDONLY }]
01827                 fconfigure $state(fd) -translation binary
01828             }
01829 
01830             switch -glob -- $state(content) {
01831                 message/* {
01832                     puts $channel ""
01833                     foreach part $state(parts) {
01834                         mime::copymessage $part $channel
01835                         break
01836                     }
01837                 }
01838 
01839                 default {
01840             # Note RFC 2046: See buildmessageaux for details.
01841 
01842                     foreach part $state(parts) {
01843                         puts $channel "\n--$boundary"
01844                         mime::copymessage $part $channel
01845                     }
01846                     puts $channel "\n--$boundary--"
01847                 }
01848             }
01849 
01850             if {[info exists state(fd)]} {
01851                 catch { close $state(fd) }
01852                 unset state(fd)
01853             }
01854         }
01855 
01856         string {
01857             if {[catch { fconfigure $channel -buffersize } blocksize]} {
01858                 set blocksize 4096
01859             } elseif {$blocksize < 512} {
01860                 set blocksize 512
01861             }
01862             set blocksize [expr {($blocksize/4)*3}]
01863 
01864         # [893516]
01865         fconfigure $channel -buffersize $blocksize
01866 
01867             puts $channel ""
01868 
01869             if {[string compare $converter ""]} {
01870                 puts -nonewline $channel [$converter -mode encode -- $state(string)]
01871             } else {
01872         puts -nonewline $channel $state(string)
01873         }
01874         }
01875     default {
01876         error "Unknown value \"$state(value)\""
01877     }
01878     }
01879 
01880     flush $channel
01881 
01882     if {[info exists state(error)]} {
01883         error $state(error)
01884     }
01885 }
01886 
01887 /*  ::mime::buildmessage --*/
01888 /* */
01889 /*      The following is a clone of the copymessage code to build up the*/
01890 /*      result in memory, and, unfortunately, without using a memory channel.*/
01891 /*      I considered parameterizing the "puts" calls in copy message, but*/
01892 /*      the need for this procedure may go away, so I'm living with it for*/
01893 /*      the moment.*/
01894 /* */
01895 /*  Arguments:*/
01896 /*        token      The MIME token to parse.*/
01897 /* */
01898 /*  Results:*/
01899 /*        Returns the message that has been built up in memory.*/
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 /*  ::mime::buildmessageaux --*/
01924 /* */
01925 /*      The following is a clone of the copymessageaux code to build up the*/
01926 /*      result in memory, and, unfortunately, without using a memory channel.*/
01927 /*      I considered parameterizing the "puts" calls in copy message, but*/
01928 /*      the need for this procedure may go away, so I'm living with it for*/
01929 /*      the moment.*/
01930 /* */
01931 /*  Arguments:*/
01932 /*        token      The MIME token to parse.*/
01933 /* */
01934 /*  Results:*/
01935 /*        Returns the message that has been built up in memory.*/
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/* $state(content)]) \
01996                     && (![string compare $boundary ""])} {
01997 # we're doing everything in one pass...
01998         set key [clock seconds]$token[info hostname][array get state]
01999         set seqno 8
02000         while {[incr seqno -1] >= 0} {
02001             set key [md5 -- $key]
02002         }
02003         set boundary "----- =_[string trim [base64 -mode encode -- $key]]"
02004 
02005         append result ";\r\n              boundary=\"$boundary\"\r\n"
02006     } else {
02007         append result "\r\n"
02008     }
02009 
02010     if {[info exists state(error)]} {
02011         unset state(error)
02012     }
02013                 
02014     switch -- $state(value) {
02015         file {
02016             set closeP 1
02017             if {[info exists state(root)]} {
02018         # FRINK: nocheck
02019                 variable $state(root)
02020                 upvar 0 $state(root) root 
02021 
02022                 if {[info exists root(fd)]} {
02023                     set fd $root(fd)
02024                     set closeP 0
02025                 } else {
02026                     set fd [set state(fd) \
02027                                 [open $state(file) { RDONLY }]]
02028                 }
02029                 set size $state(count)
02030             } else {
02031                 set fd [set state(fd) [open $state(file) { RDONLY }]]
02032                 set size -1 ;# Read until EOF
02033             }
02034             seek $fd $state(offset) start
02035             if {$closeP} {
02036                 fconfigure $fd -translation binary
02037             }
02038 
02039             append result "\r\n"
02040 
02041         while {($size != 0) && (![eof $fd])} {
02042         if {$size < 0 || $size > 32766} {
02043             set X [read $fd 32766]
02044         } else {
02045             set X [read $fd $size]
02046         }
02047         if {$size > 0} {
02048             set size [expr {$size - [string length $X]}]
02049         }
02050         if {[string compare $converter ""]} {
02051             append result [$converter -mode encode -- $X]
02052         } else {
02053             append result $X
02054         }
02055         }
02056 
02057             if {$closeP} {
02058                 catch { close $state(fd) }
02059                 unset state(fd)
02060             }
02061         }
02062 
02063         parts {
02064             if {(![info exists state(root)]) \
02065                     && ([info exists state(file)])} {
02066                 set state(fd) [open $state(file) { RDONLY }]
02067                 fconfigure $state(fd) -translation binary
02068             }
02069 
02070             switch -glob -- $state(content) {
02071                 message/* {
02072                     append result "\r\n"
02073                     foreach part $state(parts) {
02074                         append result [buildmessage $part]
02075                         break
02076                     }
02077                 }
02078 
02079                 default {
02080             # Note RFC 2046:
02081             #
02082             # The boundary delimiter MUST occur at the
02083             # beginning of a line, i.e., following a CRLF, and
02084             # the initial CRLF is considered to be attached to
02085             # the boundary delimiter line rather than part of
02086             # the preceding part.
02087             #
02088             # - The above means that the CRLF before $boundary
02089             #   is needed per the RFC, and the parts must not
02090             #   have a closing CRLF of their own. See Tcllib bug
02091             #   1213527, and patch 1254934 for the problems when
02092             #   both file/string brnaches added CRLF after the
02093             #   body parts.
02094 
02095                     foreach part $state(parts) {
02096                         append result "\r\n--$boundary\r\n"
02097                         append result [buildmessage $part]
02098                     }
02099                     append result "\r\n--$boundary--\r\n"
02100                 }
02101             }
02102 
02103             if {[info exists state(fd)]} {
02104                 catch { close $state(fd) }
02105                 unset state(fd)
02106             }
02107         }
02108 
02109         string {
02110             append result "\r\n"
02111 
02112         if {[string compare $converter ""]} {
02113         append result [$converter -mode encode -- $state(string)]
02114         } else {
02115         append result $state(string)
02116         }
02117         }
02118     default {
02119         error "Unknown value \"$state(value)\""
02120     }
02121     }
02122 
02123     if {[info exists state(error)]} {
02124         error $state(error)
02125     }
02126     return $result
02127 }
02128 
02129 /*  ::mime::encoding --*/
02130 /* */
02131 /*      Determines how a token is encoded.*/
02132 /* */
02133 /*  Arguments:*/
02134 /*        token      The MIME token to parse.*/
02135 /* */
02136 /*  Results:*/
02137 /*        Returns the encoding of the message (the null string, base64,*/
02138 /*        or quoted-printable).*/
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         image/*
02149             -
02150         video/* {
02151             return base64
02152         }
02153 
02154         message/*
02155             -
02156         multipart/* {
02157             return ""
02158         }
02159     default {# Skip}
02160     }
02161 
02162     set asciiP 1
02163     set lineP 1
02164     switch -- $state(value) {
02165         file {
02166             set fd [open $state(file) { RDONLY }]
02167             fconfigure $fd -translation binary
02168 
02169             while {[gets $fd line] >= 0} {
02170                 if {$asciiP} {
02171                     set asciiP [encodingasciiP $line]
02172                 }
02173                 if {$lineP} {
02174                     set lineP [encodinglineP $line]
02175                 }
02176                 if {(!$asciiP) && (!$lineP)} {
02177                     break
02178                 }
02179             }
02180 
02181             catch { close $fd }
02182         }
02183 
02184         parts {
02185             return ""
02186         }
02187 
02188         string {
02189             foreach line [split $state(string) "\n"] {
02190                 if {$asciiP} {
02191                     set asciiP [encodingasciiP $line]
02192                 }
02193                 if {$lineP} {
02194                     set lineP [encodinglineP $line]
02195                 }
02196                 if {(!$asciiP) && (!$lineP)} {
02197                     break
02198                 }
02199             }
02200         }
02201     default {
02202         error "Unknown value \"$state(value)\""
02203     }
02204     }
02205 
02206     switch -glob -- $state(content) {
02207         text/* {
02208             if {!$asciiP} {
02209                 foreach {k v} $state(params) {
02210                     if {![string compare $k charset]} {
02211                         set v [string tolower $v]
02212                         if {([string compare $v us-ascii]) \
02213                                 && (![string match {iso-8859-[1-8]} $v])} {
02214                             return base64
02215                         }
02216 
02217                         break
02218                     }
02219                 }
02220             }
02221 
02222             if {!$lineP} {
02223                 return quoted-printable
02224             }
02225         }
02226 
02227         
02228         default {
02229             if {(!$asciiP) || (!$lineP)} {
02230                 return base64
02231             }
02232         }
02233     }
02234 
02235     return ""
02236 }
02237 
02238 /*  ::mime::encodingasciiP --*/
02239 /* */
02240 /*      Checks if a string is a pure ascii string, or if it has a non-standard*/
02241 /*      form.*/
02242 /* */
02243 /*  Arguments:*/
02244 /*        line    The line to check.*/
02245 /* */
02246 /*  Results:*/
02247 /*        Returns 1 if \r only occurs at the end of lines, and if all*/
02248 /*        characters in the line are between the ASCII codes of 32 and 126.*/
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 /*  ::mime::encodinglineP --*/
02273 /* */
02274 /*      Checks if a string is a line is valid to be processed.*/
02275 /* */
02276 /*  Arguments:*/
02277 /*        line    The line to check.*/
02278 /* */
02279 /*  Results:*/
02280 /*        Returns 1 the line is less than 76 characters long, the line*/
02281 /*        contains more characters than just whitespace, the line does*/
02282 /*        not start with a '.', and the line does not start with 'From '.*/
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 /*  ::mime::fcopy --*/
02296 /* */
02297 /*  Appears to be unused.*/
02298 /* */
02299 /*  Arguments:*/
02300 /* */
02301 /*  Results:*/
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 /*  ::mime::scopy --*/
02316 /* */
02317 /*  Copy a portion of the contents of a mime token to a channel.*/
02318 /* */
02319 /*  Arguments:*/
02320 /*  token     The token containing the data to copy.*/
02321 /*        channel   The channel to write the data to.*/
02322 /*        offset    The location in the string to start copying*/
02323 /*                  from.*/
02324 /*        len       The amount of data to write.*/
02325 /*        blocksize The block size for the write operation.*/
02326 /* */
02327 /*  Results:*/
02328 /*  The specified portion of the string in the mime token is*/
02329 /*        copied to the specified channel.*/
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 /*  ::mime::qp_encode --*/
02363 /* */
02364 /*  Tcl version of quote-printable encode*/
02365 /* */
02366 /*  Arguments:*/
02367 /*  string        The string to quote.*/
02368 /*        encoded_word  Boolean value to determine whether or not encoded words*/
02369 /*                      (RFC 2047) should be handled or not. (optional)*/
02370 /* */
02371 /*  Results:*/
02372 /*  The properly quoted string is returned.*/
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 /*  ::mime::qp_decode --*/
02443 /* */
02444 /*  Tcl version of quote-printable decode*/
02445 /* */
02446 /*  Arguments:*/
02447 /*  string        The quoted-prinatble string to decode.*/
02448 /*        encoded_word  Boolean value to determine whether or not encoded words*/
02449 /*                      (RFC 2047) should be handled or not. (optional)*/
02450 /* */
02451 /*  Results:*/
02452 /*  The decoded string is returned.*/
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 /*  ::mime::parseaddress --*/
02485 /* */
02486 /*        This was originally written circa 1982 in C. we're still using it*/
02487 /*        because it recognizes virtually every buggy address syntax ever*/
02488 /*        generated!*/
02489 /* */
02490 /*        mime::parseaddress takes a string containing one or more 822-style*/
02491 /*        address specifications and returns a list of serialized arrays, one*/
02492 /*        element for each address specified in the argument.*/
02493 /* */
02494 /*     Each serialized array contains these properties:*/
02495 /* */
02496 /*        property    value*/
02497 /*        ========    =====*/
02498 /*        address     local@domain*/
02499 /*        comment     822-style comment*/
02500 /*        domain      the domain part (rhs)*/
02501 /*        error       non-empty on a parse error*/
02502 /*        group       this address begins a group*/
02503 /*        friendly    user-friendly rendering*/
02504 /*        local       the local part (lhs)*/
02505 /*        memberP     this address belongs to a group*/
02506 /*        phrase      the phrase part*/
02507 /*        proper      822-style address specification*/
02508 /*        route       822-style route specification (obsolete)*/
02509 /* */
02510 /*     Note that one or more of these properties may be empty.*/
02511 /* */
02512 /*  Arguments:*/
02513 /*  string        The address string to parse*/
02514 /* */
02515 /*  Results:*/
02516 /*  Returns a list of serialized arrays, one element for each address*/
02517 /*        specified in the argument.*/
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 /*  ::mime::parseaddressaux --*/
02543 /* */
02544 /*        This was originally written circa 1982 in C. we're still using it*/
02545 /*        because it recognizes virtually every buggy address syntax ever*/
02546 /*        generated!*/
02547 /* */
02548 /*        mime::parseaddressaux does the actually parsing for mime::parseaddress*/
02549 /* */
02550 /*     Each serialized array contains these properties:*/
02551 /* */
02552 /*        property    value*/
02553 /*        ========    =====*/
02554 /*        address     local@domain*/
02555 /*        comment     822-style comment*/
02556 /*        domain      the domain part (rhs)*/
02557 /*        error       non-empty on a parse error*/
02558 /*        group       this address begins a group*/
02559 /*        friendly    user-friendly rendering*/
02560 /*        local       the local part (lhs)*/
02561 /*        memberP     this address belongs to a group*/
02562 /*        phrase      the phrase part*/
02563 /*        proper      822-style address specification*/
02564 /*        route       822-style route specification (obsolete)*/
02565 /* */
02566 /*     Note that one or more of these properties may be empty.*/
02567 /* */
02568 /*  Arguments:*/
02569 /*        token         The MIME token to work from.*/
02570 /*  string        The address string to parse*/
02571 /* */
02572 /*  Results:*/
02573 /*  Returns a list of serialized arrays, one element for each address*/
02574 /*        specified in the argument.*/
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 /*  ::mime::addr_next --*/
02678 /* */
02679 /*        Locate the next address in a mime token.*/
02680 /* */
02681 /*  Arguments:*/
02682 /*        token         The MIME token to work from.*/
02683 /* */
02684 /*  Results:*/
02685 /*  Returns 1 if there is another address, and 0 if there is not.*/
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 /*  ::mime::addr_specification --*/
02753 /* */
02754 /*    Uses lookahead parsing to determine whether there is another*/
02755 /*    valid e-mail address or not.  Throws errors if unrecognized*/
02756 /*    or invalid e-mail address syntax is used.*/
02757 /* */
02758 /*  Arguments:*/
02759 /*        token         The MIME token to work from.*/
02760 /* */
02761 /*  Results:*/
02762 /*  Returns 1 if there is another address, and 0 if there is not.*/
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 /*  ::mime::addr_routeaddr --*/
02868 /* */
02869 /*        Parses the domain portion of an e-mail address.  Finds the '@'*/
02870 /*        sign and then calls mime::addr_route to verify the domain.*/
02871 /* */
02872 /*  Arguments:*/
02873 /*        token         The MIME token to work from.*/
02874 /* */
02875 /*  Results:*/
02876 /*  Returns 1 if there is another address, and 0 if there is not.*/
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 /*  ::mime::addr_route --*/
02922 /* */
02923 /*     Attempts to parse the portion of the e-mail address after the @.*/
02924 /*     Tries to verify that the domain definition has a valid form.*/
02925 /* */
02926 /*  Arguments:*/
02927 /*        token         The MIME token to work from.*/
02928 /* */
02929 /*  Results:*/
02930 /*  Returns nothing if successful, and throws an error if invalid*/
02931 /*        syntax is found.*/
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 /*  ::mime::addr_domain --*/
02998 /* */
02999 /*     Attempts to parse the portion of the e-mail address after the @.*/
03000 /*     Tries to verify that the domain definition has a valid form.*/
03001 /* */
03002 /*  Arguments:*/
03003 /*        token         The MIME token to work from.*/
03004 /* */
03005 /*  Results:*/
03006 /*  Returns nothing if successful, and throws an error if invalid*/
03007 /*        syntax is found.*/
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 /*  ::mime::addr_local --*/
03047 /* */
03048 /* */
03049 /*  Arguments:*/
03050 /*        token         The MIME token to work from.*/
03051 /* */
03052 /*  Results:*/
03053 /*  Returns nothing if successful, and throws an error if invalid*/
03054 /*        syntax is found.*/
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 /*  ::mime::addr_phrase --*/
03091 /* */
03092 /* */
03093 /*  Arguments:*/
03094 /*        token         The MIME token to work from.*/
03095 /* */
03096 /*  Results:*/
03097 /*  Returns nothing if successful, and throws an error if invalid*/
03098 /*        syntax is found.*/
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 /*  ::mime::addr_group --*/
03143 /* */
03144 /* */
03145 /*  Arguments:*/
03146 /*        token         The MIME token to work from.*/
03147 /* */
03148 /*  Results:*/
03149 /*  Returns nothing if successful, and throws an error if invalid*/
03150 /*        syntax is found.*/
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 /*  ::mime::addr_end --*/
03187 /* */
03188 /* */
03189 /*  Arguments:*/
03190 /*        token         The MIME token to work from.*/
03191 /* */
03192 /*  Results:*/
03193 /*  Returns nothing if successful, and throws an error if invalid*/
03194 /*        syntax is found.*/
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 /*  ::mime::addr_x400 --*/
03221 /* */
03222 /* */
03223 /*  Arguments:*/
03224 /*        token         The MIME token to work from.*/
03225 /* */
03226 /*  Results:*/
03227 /*  Returns nothing if successful, and throws an error if invalid*/
03228 /*        syntax is found.*/
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 /*  ::mime::parsedatetime --*/
03244 /* */
03245 /*     Fortunately the clock command in the Tcl 8.x core does all the heavy */
03246 /*     lifting for us (except for timezone calculations).*/
03247 /* */
03248 /*     mime::parsedatetime takes a string containing an 822-style date-time*/
03249 /*     specification and returns the specified property.*/
03250 /* */
03251 /*     The list of properties and their ranges are:*/
03252 /* */
03253 /*        property     range*/
03254 /*        ========     =====*/
03255 /*        clock        raw result of "clock scan"*/
03256 /*        hour         0 .. 23*/
03257 /*        lmonth       January, February, ..., December*/
03258 /*        lweekday     Sunday, Monday, ... Saturday*/
03259 /*        mday         1 .. 31*/
03260 /*        min          0 .. 59*/
03261 /*        mon          1 .. 12*/
03262 /*        month        Jan, Feb, ..., Dec*/
03263 /*        proper       822-style date-time specification*/
03264 /*        rclock       elapsed seconds between then and now*/
03265 /*        sec          0 .. 59*/
03266 /*        wday         0 .. 6 (Sun .. Mon)*/
03267 /*        weekday      Sun, Mon, ..., Sat*/
03268 /*        yday         1 .. 366*/
03269 /*        year         1900 ...*/
03270 /*        zone         -720 .. 720 (minutes east of GMT)*/
03271 /* */
03272 /*  Arguments:*/
03273 /*        value       Either a 822-style date-time specification or '-now'*/
03274 /*                    if the current date/time should be used.*/
03275 /*        property    The property (from the list above) to return*/
03276 /* */
03277 /*  Results:*/
03278 /*  Returns the string value of the 'property' for the date/time that was*/
03279 /*        specified in 'value'.*/
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         /*  Counting months starts at 1, so just insert a dummy element*/
03287         /*  at index 0.*/
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 /*  ::mime::uniqueID --*/
03460 /* */
03461 /*     Used to generate a 'globally unique identifier' for the content-id.*/
03462 /*     The id is built from the pid, the current time, the hostname, and*/
03463 /*     a counter that is incremented each time a message is sent.*/
03464 /* */
03465 /*  Arguments:*/
03466 /* */
03467 /*  Results:*/
03468 /*  Returns the a string that contains the globally unique identifier*/
03469 /*        that should be used for the Content-ID of an e-mail message.*/
03470 
03471 ret  ::mime::uniqueID () {
03472     variable mime
03473 
03474     return "<[pid].[clock seconds].[incr mime(cid)]@[info hostname]>"
03475 }
03476 
03477 /*  ::mime::parselexeme --*/
03478 /* */
03479 /*     Used to implement a lookahead parser.*/
03480 /* */
03481 /*  Arguments:*/
03482 /*        token    The MIME token to operate on.*/
03483 /* */
03484 /*  Results:*/
03485 /*  Returns the next token found by the parser.*/
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 /*  ::mime::mapencoding --*/
03631 /* */
03632 /*     mime::mapencodings maps tcl encodings onto the proper names for their*/
03633 /*     MIME charset type.  This is only done for encodings whose charset types*/
03634 /*     were known.  The remaining encodings return "" for now.*/
03635 /* */
03636 /*  Arguments:*/
03637 /*        enc      The tcl encoding to map.*/
03638 /* */
03639 /*  Results:*/
03640 /*  Returns the MIME charset type for the specified tcl encoding, or ""*/
03641 /*        if none is known.*/
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 /*  ::mime::reversemapencoding --*/
03654 /* */
03655 /*     mime::reversemapencodings maps MIME charset types onto tcl encoding names.*/
03656 /*     Those that are unknown return "".*/
03657 /* */
03658 /*  Arguments:*/
03659 /*        mimeType  The MIME charset to convert into a tcl encoding type.*/
03660 /* */
03661 /*  Results:*/
03662 /*  Returns the tcl encoding name for the specified mime charset, or ""*/
03663 /*        if none is known.*/
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 /*  ::mime::word_encode --*/
03677 /* */
03678 /*     Word encodes strings as per RFC 2047.*/
03679 /* */
03680 /*  Arguments:*/
03681 /*        charset   The character set to encode the message to.*/
03682 /*        method    The encoding method (base64 or quoted-printable).*/
03683 /*        string    The string to encode.*/
03684 /*        ?-charset_encoded   0 or 1      Whether the data is already encoded*/
03685 /*                                        in the specified charset (default 1)*/
03686 /*        ?-maxlength         maxlength   The maximum length of each encoded*/
03687 /*                                        word to return (default 66)*/
03688 /* */
03689 /*  Results:*/
03690 /*  Returns a word encoded string.*/
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 /*  ::mime::word_decode --*/
03804 /* */
03805 /*     Word decodes strings that have been word encoded as per RFC 2047.*/
03806 /* */
03807 /*  Arguments:*/
03808 /*        encoded   The word encoded string to decode.*/
03809 /* */
03810 /*  Results:*/
03811 /*  Returns the string that has been decoded from the encoded message.*/
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 /*  ::mime::field_decode --*/
03860 /* */
03861 /*     Word decodes strings that have been word encoded as per RFC 2047*/
03862 /*     and converts the string from the original encoding/charset to UTF.*/
03863 /* */
03864 /*  Arguments:*/
03865 /*        field     The string to decode*/
03866 /* */
03867 /*  Results:*/
03868 /*  Returns the decoded string in UTF.*/
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 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1