# ACTIVESTATE TEAPOT-PKG BEGIN TM -*- tcl -*- # -- Tcl Module # @@ Meta Begin # Package ascii85 1.0 # Meta as::build::date 2015-05-25 # Meta as::origin http://sourceforge.net/projects/tcllib # Meta category Text encoding & decoding binary data # Meta description ascii85-encode/decode binary data # Meta license BSD # Meta platform tcl # Meta require {Tcl 8.2} # Meta subject ascii85 encoding # Meta summary ascii85 # @@ Meta End # ACTIVESTATE TEAPOT-PKG BEGIN REQUIREMENTS package require Tcl 8.2 # ACTIVESTATE TEAPOT-PKG END REQUIREMENTS # ACTIVESTATE TEAPOT-PKG BEGIN DECLARE package provide ascii85 1.0 # ACTIVESTATE TEAPOT-PKG END DECLARE # ACTIVESTATE TEAPOT-PKG END TM # ascii85.tcl -- # # Encode/Decode ascii85 for a string # # Copyright (c) Emiliano Gavilan # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. package require Tcl 8.4 namespace eval ascii85 { namespace export encode encodefile decode # default values for encode options variable options array set options [list -wrapchar \n -maxlen 76] } # ::ascii85::encode -- # # Ascii85 encode a given string. # # Arguments: # args ?-maxlen maxlen? ?-wrapchar wrapchar? string # # If maxlen is 0, the output is not wrapped. # # Results: # A Ascii85 encoded version of $string, wrapped at $maxlen characters # by $wrapchar. proc ascii85::encode {args} { variable options set alen [llength $args] if {$alen != 1 && $alen != 3 && $alen != 5} { return -code error "wrong # args:\ should be \"[lindex [info level 0] 0]\ ?-maxlen maxlen?\ ?-wrapchar wrapchar? string\"" } set data [lindex $args end] array set opts [array get options] array set opts [lrange $args 0 end-1] foreach key [array names opts] { if {[lsearch -exact [array names options] $key] == -1} { return -code error "unknown option \"$key\":\ must be -maxlen or -wrapchar" } } if {![string is integer -strict $opts(-maxlen)] || $opts(-maxlen) < 0} { return -code error "expected positive integer but got\ \"$opts(-maxlen)\"" } # perform this check early if {[string length $data] == 0} { return "" } # shorten the names set ml $opts(-maxlen) set wc $opts(-wrapchar) # if maxlen is zero, don't wrap the output if {$ml == 0} { set wc "" } set encoded {} binary scan $data c* X set len [llength $X] set rest [expr {$len % 4}] set lastidx [expr {$len - $rest - 1}] foreach {b1 b2 b3 b4} [lrange $X 0 $lastidx] { # calculate the 32 bit value # this is an inlined version of the [encode4bytes] proc # included here for performance reasons set val [expr { ( (($b1 & 0xff) << 24) |(($b2 & 0xff) << 16) |(($b3 & 0xff) << 8) | ($b4 & 0xff) ) & 0xffffffff }] if {$val == 0} { # four \0 bytes encodes as "z" instead of "!!!!!" append current "z" } else { # no magic numbers here. # 52200625 -> 85 ** 4 # 614125 -> 85 ** 3 # 7225 -> 85 ** 2 append current [binary format ccccc \ [expr { ( $val / 52200625) + 33 }] \ [expr { (($val % 52200625) / 614125) + 33 }] \ [expr { (($val % 614125) / 7225) + 33 }] \ [expr { (($val % 7225) / 85) + 33 }] \ [expr { ( $val % 85) + 33 }]] } if {[string length $current] >= $ml} { append encoded [string range $current 0 [expr {$ml - 1}]] $wc set current [string range $current $ml end] } } if { $rest } { # there are remaining bytes. # pad with \0 and encode not using the "z" convention. # finally, add ($rest + 1) chars. set val 0 foreach {b1 b2 b3 b4} [pad [lrange $X [incr lastidx] end] 4 0] break append current [string range [encode4bytes $b1 $b2 $b3 $b4] 0 $rest] } append encoded [regsub -all -- ".{$ml}" $current "&$wc"] return $encoded } proc ascii85::encode4bytes {b1 b2 b3 b4} { set val [expr { ( (($b1 & 0xff) << 24) |(($b2 & 0xff) << 16) |(($b3 & 0xff) << 8) | ($b4 & 0xff) ) & 0xffffffff }] return [binary format ccccc \ [expr { ( $val / 52200625) + 33 }] \ [expr { (($val % 52200625) / 614125) + 33 }] \ [expr { (($val % 614125) / 7225) + 33 }] \ [expr { (($val % 7225) / 85) + 33 }] \ [expr { ( $val % 85) + 33 }]] } # ::ascii85::encodefile -- # # Ascii85 encode the contents of a file using default values # for maxlen and wrapchar parameters. # # Arguments: # fname The name of the file to encode. # # Results: # An Ascii85 encoded version of the contents of the file. # This is a convenience command proc ascii85::encodefile {fname} { set fd [open $fname] fconfigure $fd -encoding binary -translation binary return [encode [read $fd]][close $fd] } # ::ascii85::decode -- # # Ascii85 decode a given string. # # Arguments: # string The string to decode. # Leading spaces and tabs are removed, along with trailing newlines # # Results: # The decoded value. proc ascii85::decode {data} { # get rid of leading spaces/tabs and trailing newlines set data [string map [list \n {} \t {} { } {}] $data] set len [string length $data] # perform this ckeck early if {! $len} { return "" } set decoded {} set count 0 set group [list] binary scan $data c* X foreach char $X { # we must check that every char is in the allowed range if {$char < 33 || $char > 117 } { # "z" is an exception if {$char == 122} { if {$count == 0} { # if a "z" char appears at the beggining of a group, # it decodes as four null bytes append decoded \x00\x00\x00\x00 continue } else { # if not, is an error return -code error \ "error decoding data: \"z\" char misplaced" } } # char is not in range and not a "z" at the beggining of a group return -code error \ "error decoding data: chars outside the allowed range" } lappend group $char incr count if {$count == 5} { # this is an inlined version of the [decode5chars] proc # included here for performance reasons set val [expr { ([lindex $group 0] - 33) * wide(52200625) + ([lindex $group 1] - 33) * 614125 + ([lindex $group 2] - 33) * 7225 + ([lindex $group 3] - 33) * 85 + ([lindex $group 4] - 33) }] if {$val > 0xffffffff} { return -code error "error decoding data: decoded group overflow" } else { append decoded [binary format I $val] incr count -5 set group [list] } } } set len [llength $group] switch -- $len { 0 { # all input has been consumed # do nothing } 1 { # a single char is a condition error, there should be at least 2 return -code error \ "error decoding data: trailing char" } default { # pad with "u"s, decode and add ($len - 1) bytes append decoded [string range \ [decode5chars [pad $group 5 122]] \ 0 \ [expr {$len - 2}]] } } return $decoded } proc ascii85::decode5chars {group} { set val [expr { ([lindex $group 0] - 33) * wide(52200625) + ([lindex $group 1] - 33) * 614125 + ([lindex $group 2] - 33) * 7225 + ([lindex $group 3] - 33) * 85 + ([lindex $group 4] - 33) }] if {$val > 0xffffffff} { return -code error "error decoding data: decoded group overflow" } return [binary format I $val] } proc ascii85::pad {chars len padchar} { while {[llength $chars] < $len} { lappend chars $padchar } return $chars } package provide ascii85 1.0