base64.tcl

Go to the documentation of this file.
00001 /*  base64.tcl --*/
00002 /* */
00003 /*  Encode/Decode base64 for a string*/
00004 /*  Stephen Uhler / Brent Welch (c) 1997 Sun Microsystems*/
00005 /*  The decoder was done for exmh by Chris Garrigues*/
00006 /* */
00007 /*  Copyright (c) 1998-2000 by Ajuba Solutions.*/
00008 /*  See the file "license.terms" for information on usage and redistribution*/
00009 /*  of this file, and for a DISCLAIMER OF ALL WARRANTIES.*/
00010 /*  */
00011 /*  RCS: @(#) $Id: base64.tcl,v 1.27 2005/12/09 18:27:15 andreas_kupries Exp $*/
00012 
00013 /*  Version 1.0   implemented Base64_Encode, Base64_Decode*/
00014 /*  Version 2.0   uses the base64 namespace*/
00015 /*  Version 2.1   fixes various decode bugs and adds options to encode*/
00016 /*  Version 2.2   is much faster, Tcl8.0 compatible*/
00017 /*  Version 2.2.1 bugfixes*/
00018 /*  Version 2.2.2 bugfixes*/
00019 /*  Version 2.3   bugfixes and extended to support Trf*/
00020 
00021 /*  @mdgen EXCLUDE: base64c.tcl*/
00022 
00023 package require Tcl 8.2
00024 namespace ::base64 {
00025     namespace export encode decode
00026 }
00027 
00028 if {![catch {package require Trf 2.0}]} {
00029     /*  Trf is available, so implement the functionality provided here*/
00030     /*  in terms of calls to Trf for speed.*/
00031 
00032     /*  ::base64::encode --*/
00033     /* */
00034     /*  Base64 encode a given string.*/
00035     /* */
00036     /*  Arguments:*/
00037     /*  args    ?-maxlen maxlen? ?-wrapchar wrapchar? string*/
00038     /*  */
00039     /*      If maxlen is 0, the output is not wrapped.*/
00040     /* */
00041     /*  Results:*/
00042     /*  A Base64 encoded version of $string, wrapped at $maxlen characters*/
00043     /*  by $wrapchar.*/
00044     
00045     ret  ::base64::encode (type args) {
00046     # Set the default wrapchar and maximum line length to match the output
00047     # of GNU uuencode 4.2.  Various RFCs allow for different wrapping 
00048     # characters and wraplengths, so these may be overridden by command line
00049     # options.
00050     set wrapchar "\n"
00051     set maxlen 60
00052 
00053     if { [llength $args] == 0 } {
00054         error "wrong # args: should be \"[lindex [info level 0] 0]\
00055             ?-maxlen maxlen? ?-wrapchar wrapchar? string\""
00056     }
00057 
00058     set optionStrings [list "-maxlen" "-wrapchar"]
00059     for {set i 0} {$i < [llength $args] - 1} {incr i} {
00060         set arg [lindex $args $i]
00061         set index [lsearch -glob $optionStrings "${arg}*"]
00062         if { $index == -1 } {
00063         error "unknown option \"$arg\": must be -maxlen or -wrapchar"
00064         }
00065         incr i
00066         if { $i >= [llength $args] - 1 } {
00067         error "value for \"$arg\" missing"
00068         }
00069         set val [lindex $args $i]
00070 
00071         # The name of the variable to assign the value to is extracted
00072         # from the list of known options, all of which have an
00073         # associated variable of the same name as the option without
00074         # a leading "-". The [string range] command is used to strip
00075         # of the leading "-" from the name of the option.
00076         #
00077         # FRINK: nocheck
00078         set [string range [lindex $optionStrings $index] 1 end] $val
00079     }
00080     
00081     # [string is] requires Tcl8.2; this works with 8.0 too
00082     if {[catch {expr {$maxlen % 2}}]} {
00083         error "expected integer but got \"$maxlen\""
00084     }
00085 
00086     set string [lindex $args end]
00087     set result [::base64 -mode encode -- $string]
00088     set result [string map [list \n ""] $result]
00089 
00090     if {$maxlen > 0} {
00091         set res ""
00092         set edge [expr {$maxlen - 1}]
00093         while {[string length $result] > $maxlen} {
00094         append res [string range $result 0 $edge]$wrapchar
00095         set result [string range $result $maxlen end]
00096         }
00097         if {[string length $result] > 0} {
00098         append res $result
00099         }
00100         set result $res
00101     }
00102 
00103     return $result
00104     }
00105 
00106     /*  ::base64::decode --*/
00107     /* */
00108     /*  Base64 decode a given string.*/
00109     /* */
00110     /*  Arguments:*/
00111     /*  string  The string to decode.  Characters not in the base64*/
00112     /*      alphabet are ignored (e.g., newlines)*/
00113     /* */
00114     /*  Results:*/
00115     /*  The decoded value.*/
00116 
00117     ret  ::base64::decode (type string) {
00118     regsub -all {\s} $string {} string
00119 	::base64 -mode decode -- $string
00120     }
00121 
00122 } else {
00123     /*  Without Trf use a pure tcl implementation*/
00124 
00125     namespace base64 {
00126     variable base64 {}
00127     variable base64_en {}
00128 
00129     /*  We create the auxiliary array base64_tmp, it will be unset later.*/
00130 
00131      i =  0
00132     foreach char {A B C D E F G H I J K L M N O P Q R S T U V W X Y Z \
00133         a b c d e f g h i j k l m n o p q r s t u v w x y z \
00134         0 1 2 3 4 5 6 7 8 9 + /} {
00135          base64 = _tmp($char) $i
00136         lappend base64_en $char
00137         incr i
00138     }
00139 
00140     /* */
00141     /*  Create base64 as list: to code for instance C<->3, specify*/
00142     /*  that [lindex $base64 67] be 3 (C is 67 in ascii); non-coded*/
00143     /*  ascii chars get a {}. we later use the fact that lindex on a*/
00144     /*  non-existing index returns {}, and that [expr {} < 0] is true*/
00145     /* */
00146 
00147     /*  the last ascii char is 'z'*/
00148     scan z %c len
00149     for { i =  0} {$i <= $len} {incr i} {
00150          char =  [format %c $i]
00151          val =  {}
00152         if {[info exists base64_tmp($char)]} {
00153          val =  $base64_tmp($char)
00154         } else {
00155          val =  {}
00156         }
00157         lappend base64 $val
00158     }
00159 
00160     /*  code the character "=" as -1; used to signal end of message*/
00161     scan = %c i
00162      base64 =  [lreplace $base64 $i $i -1]
00163 
00164     /*  remove unneeded variables*/
00165     un base64 = _tmp i char len val
00166 
00167     namespace export encode decode
00168     }
00169 
00170     /*  ::base64::encode --*/
00171     /* */
00172     /*  Base64 encode a given string.*/
00173     /* */
00174     /*  Arguments:*/
00175     /*  args    ?-maxlen maxlen? ?-wrapchar wrapchar? string*/
00176     /*  */
00177     /*      If maxlen is 0, the output is not wrapped.*/
00178     /* */
00179     /*  Results:*/
00180     /*  A Base64 encoded version of $string, wrapped at $maxlen characters*/
00181     /*  by $wrapchar.*/
00182     
00183     ret  ::base64::encode (type args) {
00184     set base64_en $::base64::base64_en
00185     
00186     # Set the default wrapchar and maximum line length to match the output
00187     # of GNU uuencode 4.2.  Various RFCs allow for different wrapping 
00188     # characters and wraplengths, so these may be overridden by command line
00189     # options.
00190     set wrapchar "\n"
00191     set maxlen 60
00192 
00193     if { [llength $args] == 0 } {
00194         error "wrong # args: should be \"[lindex [info level 0] 0]\
00195             ?-maxlen maxlen? ?-wrapchar wrapchar? string\""
00196     }
00197 
00198     set optionStrings [list "-maxlen" "-wrapchar"]
00199     for {set i 0} {$i < [llength $args] - 1} {incr i} {
00200         set arg [lindex $args $i]
00201         set index [lsearch -glob $optionStrings "${arg}*"]
00202         if { $index == -1 } {
00203         error "unknown option \"$arg\": must be -maxlen or -wrapchar"
00204         }
00205         incr i
00206         if { $i >= [llength $args] - 1 } {
00207         error "value for \"$arg\" missing"
00208         }
00209         set val [lindex $args $i]
00210 
00211         # The name of the variable to assign the value to is extracted
00212         # from the list of known options, all of which have an
00213         # associated variable of the same name as the option without
00214         # a leading "-". The [string range] command is used to strip
00215         # of the leading "-" from the name of the option.
00216         #
00217         # FRINK: nocheck
00218         set [string range [lindex $optionStrings $index] 1 end] $val
00219     }
00220     
00221     # [string is] requires Tcl8.2; this works with 8.0 too
00222     if {[catch {expr {$maxlen % 2}}]} {
00223         error "expected integer but got \"$maxlen\""
00224     }
00225 
00226     set string [lindex $args end]
00227 
00228     set result {}
00229     set state 0
00230     set length 0
00231 
00232 
00233     # Process the input bytes 3-by-3
00234 
00235     binary scan $string c* X
00236     foreach {x y z} $X {
00237         # Do the line length check before appending so that we don't get an
00238         # extra newline if the output is a multiple of $maxlen chars long.
00239         if {$maxlen && $length >= $maxlen} {
00240         append result $wrapchar
00241         set length 0
00242         }
00243     
00244         append result [lindex $base64_en [expr {($x >>2) & 0x3F}]] 
00245         if {$y != {}} {
00246         append result [lindex $base64_en [expr {(($x << 4) & 0x30) | (($y >> 4) & 0xF)}]] 
00247         if {$z != {}} {
00248             append result \
00249                 [lindex $base64_en [expr {(($y << 2) & 0x3C) | (($z >> 6) & 0x3)}]]
00250             append result [lindex $base64_en [expr {($z & 0x3F)}]]
00251         } else {
00252             set state 2
00253             break
00254         }
00255         } else {
00256         set state 1
00257         break
00258         }
00259         incr length 4
00260     }
00261     if {$state == 1} {
00262         append result [lindex $base64_en [expr {(($x << 4) & 0x30)}]]== 
00263     } elseif {$state == 2} {
00264         append result [lindex $base64_en [expr {(($y << 2) & 0x3C)}]]=  
00265     }
00266     return $result
00267     }
00268 
00269     /*  ::base64::decode --*/
00270     /* */
00271     /*  Base64 decode a given string.*/
00272     /* */
00273     /*  Arguments:*/
00274     /*  string  The string to decode.  Characters not in the base64*/
00275     /*      alphabet are ignored (e.g., newlines)*/
00276     /* */
00277     /*  Results:*/
00278     /*  The decoded value.*/
00279 
00280     ret  ::base64::decode (type string) {
00281     if {[string length $string] == 0} {return ""}
00282 
00283     set base64 $::base64::base64
00284     set output "" ; # Fix for [Bug 821126]
00285 
00286     binary scan $string c* X
00287     foreach x $X {
00288         set bits [lindex $base64 $x]
00289         if {$bits >= 0} {
00290         if {[llength [lappend nums $bits]] == 4} {
00291             foreach {v w z y} $nums break
00292             set a [expr {($v << 2) | ($w >> 4)}]
00293             set b [expr {(($w & 0xF) << 4) | ($z >> 2)}]
00294             set c [expr {(($z & 0x3) << 6) | $y}]
00295             append output [binary format ccc $a $b $c]
00296             set nums {}
00297         }       
00298         } elseif {$bits == -1} {
00299         # = indicates end of data.  Output whatever chars are left.
00300         # The encoding algorithm dictates that we can only have 1 or 2
00301         # padding characters.  If x=={}, we have 12 bits of input 
00302         # (enough for 1 8-bit output).  If x!={}, we have 18 bits of
00303         # input (enough for 2 8-bit outputs).
00304         
00305         foreach {v w z} $nums break
00306         set a [expr {($v << 2) | (($w & 0x30) >> 4)}]
00307         if {$z == {}} {
00308             append output [binary format c $a ]
00309         } else {
00310             set b [expr {(($w & 0xF) << 4) | (($z & 0x3C) >> 2)}]
00311             append output [binary format cc $a $b]
00312         }       
00313         break
00314         } else {
00315         # RFC 2045 says that line breaks and other characters not part
00316         # of the Base64 alphabet must be ignored, and that the decoder
00317         # can optionally emit a warning or reject the message.  We opt
00318         # not to do so, but to just ignore the character. 
00319         continue
00320         }
00321     }
00322     return $output
00323     }
00324 }
00325 
00326 package provide base64 2.3.2
00327 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1