| 1 | # uuencode - Copyright (C) 2002 Pat Thoyts <patthoyts@users.sourceforge.net> |
|---|
| 2 | # |
|---|
| 3 | # Provide a Tcl only implementation of uuencode and uudecode. |
|---|
| 4 | # |
|---|
| 5 | # ------------------------------------------------------------------------- |
|---|
| 6 | # See the file "license.terms" for information on usage and redistribution |
|---|
| 7 | # of this file, and for a DISCLAIMER OF ALL WARRANTIES. |
|---|
| 8 | # ------------------------------------------------------------------------- |
|---|
| 9 | # @(#)$Id: uuencode.tcl,v 1.21 2006/10/14 06:30:55 andreas_kupries Exp $ |
|---|
| 10 | |
|---|
| 11 | package require Tcl 8.2; # tcl minimum version |
|---|
| 12 | |
|---|
| 13 | # Try and get some compiled helper package. |
|---|
| 14 | if {[catch {package require tcllibc}]} { |
|---|
| 15 | catch {package require Trf} |
|---|
| 16 | } |
|---|
| 17 | |
|---|
| 18 | namespace eval ::uuencode { |
|---|
| 19 | variable version 1.1.4 |
|---|
| 20 | |
|---|
| 21 | namespace export encode decode uuencode uudecode |
|---|
| 22 | } |
|---|
| 23 | |
|---|
| 24 | proc ::uuencode::Enc {c} { |
|---|
| 25 | return [format %c [expr {($c != 0) ? (($c & 0x3f) + 0x20) : 0x60}]] |
|---|
| 26 | } |
|---|
| 27 | |
|---|
| 28 | proc ::uuencode::Encode {s} { |
|---|
| 29 | set r {} |
|---|
| 30 | binary scan $s c* d |
|---|
| 31 | foreach {c1 c2 c3} $d { |
|---|
| 32 | if {$c1 == {}} {set c1 0} |
|---|
| 33 | if {$c2 == {}} {set c2 0} |
|---|
| 34 | if {$c3 == {}} {set c3 0} |
|---|
| 35 | append r [Enc [expr {$c1 >> 2}]] |
|---|
| 36 | append r [Enc [expr {(($c1 << 4) & 060) | (($c2 >> 4) & 017)}]] |
|---|
| 37 | append r [Enc [expr {(($c2 << 2) & 074) | (($c3 >> 6) & 003)}]] |
|---|
| 38 | append r [Enc [expr {($c3 & 077)}]] |
|---|
| 39 | } |
|---|
| 40 | return $r |
|---|
| 41 | } |
|---|
| 42 | |
|---|
| 43 | |
|---|
| 44 | proc ::uuencode::Decode {s} { |
|---|
| 45 | if {[string length $s] == 0} {return ""} |
|---|
| 46 | set r {} |
|---|
| 47 | binary scan [pad $s] c* d |
|---|
| 48 | |
|---|
| 49 | foreach {c0 c1 c2 c3} $d { |
|---|
| 50 | append r [format %c [expr {((($c0-0x20)&0x3F) << 2) & 0xFF |
|---|
| 51 | | ((($c1-0x20)&0x3F) >> 4) & 0xFF}]] |
|---|
| 52 | append r [format %c [expr {((($c1-0x20)&0x3F) << 4) & 0xFF |
|---|
| 53 | | ((($c2-0x20)&0x3F) >> 2) & 0xFF}]] |
|---|
| 54 | append r [format %c [expr {((($c2-0x20)&0x3F) << 6) & 0xFF |
|---|
| 55 | | (($c3-0x20)&0x3F) & 0xFF}]] |
|---|
| 56 | } |
|---|
| 57 | return $r |
|---|
| 58 | } |
|---|
| 59 | |
|---|
| 60 | # ------------------------------------------------------------------------- |
|---|
| 61 | # C coded version of the Encode/Decode functions for base64c package. |
|---|
| 62 | # ------------------------------------------------------------------------- |
|---|
| 63 | if {[package provide critcl] != {}} { |
|---|
| 64 | namespace eval ::uuencode { |
|---|
| 65 | critcl::ccode { |
|---|
| 66 | #include <string.h> |
|---|
| 67 | static unsigned char Enc(unsigned char c) { |
|---|
| 68 | return (c != 0) ? ((c & 0x3f) + 0x20) : 0x60; |
|---|
| 69 | } |
|---|
| 70 | } |
|---|
| 71 | critcl::ccommand CEncode {dummy interp objc objv} { |
|---|
| 72 | Tcl_Obj *inputPtr, *resultPtr; |
|---|
| 73 | int len, rlen, xtra; |
|---|
| 74 | unsigned char *input, *p, *r; |
|---|
| 75 | |
|---|
| 76 | if (objc != 2) { |
|---|
| 77 | Tcl_WrongNumArgs(interp, 1, objv, "data"); |
|---|
| 78 | return TCL_ERROR; |
|---|
| 79 | } |
|---|
| 80 | |
|---|
| 81 | inputPtr = objv[1]; |
|---|
| 82 | input = Tcl_GetByteArrayFromObj(inputPtr, &len); |
|---|
| 83 | if ((xtra = (3 - (len % 3))) != 3) { |
|---|
| 84 | if (Tcl_IsShared(inputPtr)) |
|---|
| 85 | inputPtr = Tcl_DuplicateObj(inputPtr); |
|---|
| 86 | input = Tcl_SetByteArrayLength(inputPtr, len + xtra); |
|---|
| 87 | memset(input + len, 0, xtra); |
|---|
| 88 | len += xtra; |
|---|
| 89 | } |
|---|
| 90 | |
|---|
| 91 | rlen = (len / 3) * 4; |
|---|
| 92 | resultPtr = Tcl_GetObjResult(interp); |
|---|
| 93 | if (Tcl_IsShared(resultPtr)) { |
|---|
| 94 | resultPtr = Tcl_DuplicateObj(resultPtr); |
|---|
| 95 | Tcl_SetObjResult(interp, resultPtr); |
|---|
| 96 | } |
|---|
| 97 | r = Tcl_SetByteArrayLength(resultPtr, rlen); |
|---|
| 98 | memset(r, 0, rlen); |
|---|
| 99 | |
|---|
| 100 | for (p = input; p < input + len; p += 3) { |
|---|
| 101 | char a, b, c; |
|---|
| 102 | a = *p; b = *(p+1), c = *(p+2); |
|---|
| 103 | *r++ = Enc(a >> 2); |
|---|
| 104 | *r++ = Enc(((a << 4) & 060) | ((b >> 4) & 017)); |
|---|
| 105 | *r++ = Enc(((b << 2) & 074) | ((c >> 6) & 003)); |
|---|
| 106 | *r++ = Enc(c & 077); |
|---|
| 107 | } |
|---|
| 108 | |
|---|
| 109 | return TCL_OK; |
|---|
| 110 | } |
|---|
| 111 | |
|---|
| 112 | critcl::ccommand CDecode {dummy interp objc objv} { |
|---|
| 113 | Tcl_Obj *inputPtr, *resultPtr; |
|---|
| 114 | int len, rlen, xtra; |
|---|
| 115 | unsigned char *input, *p, *r; |
|---|
| 116 | |
|---|
| 117 | if (objc != 2) { |
|---|
| 118 | Tcl_WrongNumArgs(interp, 1, objv, "data"); |
|---|
| 119 | return TCL_ERROR; |
|---|
| 120 | } |
|---|
| 121 | |
|---|
| 122 | /* if input is not mod 4, extend it with nuls */ |
|---|
| 123 | inputPtr = objv[1]; |
|---|
| 124 | input = Tcl_GetByteArrayFromObj(inputPtr, &len); |
|---|
| 125 | if ((xtra = (4 - (len % 4))) != 4) { |
|---|
| 126 | if (Tcl_IsShared(inputPtr)) |
|---|
| 127 | inputPtr = Tcl_DuplicateObj(inputPtr); |
|---|
| 128 | input = Tcl_SetByteArrayLength(inputPtr, len + xtra); |
|---|
| 129 | memset(input + len, 0, xtra); |
|---|
| 130 | len += xtra; |
|---|
| 131 | } |
|---|
| 132 | |
|---|
| 133 | /* output will be 1/3 smaller than input and a multiple of 3 */ |
|---|
| 134 | rlen = (len / 4) * 3; |
|---|
| 135 | resultPtr = Tcl_GetObjResult(interp); |
|---|
| 136 | if (Tcl_IsShared(resultPtr)) { |
|---|
| 137 | resultPtr = Tcl_DuplicateObj(resultPtr); |
|---|
| 138 | Tcl_SetObjResult(interp, resultPtr); |
|---|
| 139 | } |
|---|
| 140 | r = Tcl_SetByteArrayLength(resultPtr, rlen); |
|---|
| 141 | memset(r, 0, rlen); |
|---|
| 142 | |
|---|
| 143 | for (p = input; p < input + len; p += 4) { |
|---|
| 144 | char a, b, c, d; |
|---|
| 145 | a = *p; b = *(p+1), c = *(p+2), d = *(p+3); |
|---|
| 146 | *r++ = (((a - 0x20) & 0x3f) << 2) | (((b - 0x20) & 0x3f) >> 4); |
|---|
| 147 | *r++ = (((b - 0x20) & 0x3f) << 4) | (((c - 0x20) & 0x3f) >> 2); |
|---|
| 148 | *r++ = (((c - 0x20) & 0x3f) << 6) | (((d - 0x20) & 0x3f) ); |
|---|
| 149 | } |
|---|
| 150 | |
|---|
| 151 | return TCL_OK; |
|---|
| 152 | } |
|---|
| 153 | } |
|---|
| 154 | } |
|---|
| 155 | |
|---|
| 156 | # ------------------------------------------------------------------------- |
|---|
| 157 | |
|---|
| 158 | # Description: |
|---|
| 159 | # Permit more tolerant decoding of invalid input strings by padding to |
|---|
| 160 | # a multiple of 4 bytes with nulls. |
|---|
| 161 | # Result: |
|---|
| 162 | # Returns the input string - possibly padded with uuencoded null chars. |
|---|
| 163 | # |
|---|
| 164 | proc ::uuencode::pad {s} { |
|---|
| 165 | if {[set mod [expr {[string length $s] % 4}]] != 0} { |
|---|
| 166 | append s [string repeat "`" [expr {4 - $mod}]] |
|---|
| 167 | } |
|---|
| 168 | return $s |
|---|
| 169 | } |
|---|
| 170 | |
|---|
| 171 | # ------------------------------------------------------------------------- |
|---|
| 172 | |
|---|
| 173 | # If the Trf package is available then we shall use this by default but the |
|---|
| 174 | # Tcllib implementations are always visible if needed (ie: for testing) |
|---|
| 175 | if {[info command ::uuencode::CDecode] != {}} { |
|---|
| 176 | # tcllib critcl package |
|---|
| 177 | interp alias {} ::uuencode::encode {} ::uuencode::CEncode |
|---|
| 178 | interp alias {} ::uuencode::decode {} ::uuencode::CDecode |
|---|
| 179 | } elseif {[package provide Trf] != {}} { |
|---|
| 180 | proc ::uuencode::encode {s} { |
|---|
| 181 | return [::uuencode -mode encode -- $s] |
|---|
| 182 | } |
|---|
| 183 | proc ::uuencode::decode {s} { |
|---|
| 184 | return [::uuencode -mode decode -- [pad $s]] |
|---|
| 185 | } |
|---|
| 186 | } else { |
|---|
| 187 | # pure-tcl then |
|---|
| 188 | interp alias {} ::uuencode::encode {} ::uuencode::Encode |
|---|
| 189 | interp alias {} ::uuencode::decode {} ::uuencode::Decode |
|---|
| 190 | } |
|---|
| 191 | |
|---|
| 192 | # ------------------------------------------------------------------------- |
|---|
| 193 | |
|---|
| 194 | proc ::uuencode::uuencode {args} { |
|---|
| 195 | array set opts {mode 0644 filename {} name {}} |
|---|
| 196 | set wrongargs "wrong \# args: should be\ |
|---|
| 197 | \"uuencode ?-name string? ?-mode octal?\ |
|---|
| 198 | (-file filename | ?--? string)\"" |
|---|
| 199 | while {[string match -* [lindex $args 0]]} { |
|---|
| 200 | switch -glob -- [lindex $args 0] { |
|---|
| 201 | -f* { |
|---|
| 202 | if {[llength $args] < 2} { |
|---|
| 203 | return -code error $wrongargs |
|---|
| 204 | } |
|---|
| 205 | set opts(filename) [lindex $args 1] |
|---|
| 206 | set args [lreplace $args 0 0] |
|---|
| 207 | } |
|---|
| 208 | -m* { |
|---|
| 209 | if {[llength $args] < 2} { |
|---|
| 210 | return -code error $wrongargs |
|---|
| 211 | } |
|---|
| 212 | set opts(mode) [lindex $args 1] |
|---|
| 213 | set args [lreplace $args 0 0] |
|---|
| 214 | } |
|---|
| 215 | -n* { |
|---|
| 216 | if {[llength $args] < 2} { |
|---|
| 217 | return -code error $wrongargs |
|---|
| 218 | } |
|---|
| 219 | set opts(name) [lindex $args 1] |
|---|
| 220 | set args [lreplace $args 0 0] |
|---|
| 221 | } |
|---|
| 222 | -- { |
|---|
| 223 | set args [lreplace $args 0 0] |
|---|
| 224 | break |
|---|
| 225 | } |
|---|
| 226 | default { |
|---|
| 227 | return -code error "bad option [lindex $args 0]:\ |
|---|
| 228 | must be -file, -mode, or -name" |
|---|
| 229 | } |
|---|
| 230 | } |
|---|
| 231 | set args [lreplace $args 0 0] |
|---|
| 232 | } |
|---|
| 233 | |
|---|
| 234 | if {$opts(name) == {}} { |
|---|
| 235 | set opts(name) $opts(filename) |
|---|
| 236 | } |
|---|
| 237 | if {$opts(name) == {}} { |
|---|
| 238 | set opts(name) "data.dat" |
|---|
| 239 | } |
|---|
| 240 | |
|---|
| 241 | if {$opts(filename) != {}} { |
|---|
| 242 | set f [open $opts(filename) r] |
|---|
| 243 | fconfigure $f -translation binary |
|---|
| 244 | set data [read $f] |
|---|
| 245 | close $f |
|---|
| 246 | } else { |
|---|
| 247 | if {[llength $args] != 1} { |
|---|
| 248 | return -code error $wrongargs |
|---|
| 249 | } |
|---|
| 250 | set data [lindex $args 0] |
|---|
| 251 | } |
|---|
| 252 | |
|---|
| 253 | set r {} |
|---|
| 254 | append r [format "begin %o %s" $opts(mode) $opts(name)] "\n" |
|---|
| 255 | for {set n 0} {$n < [string length $data]} {incr n 45} { |
|---|
| 256 | set s [string range $data $n [expr {$n + 44}]] |
|---|
| 257 | append r [Enc [string length $s]] |
|---|
| 258 | append r [encode $s] "\n" |
|---|
| 259 | } |
|---|
| 260 | append r "`\nend" |
|---|
| 261 | return $r |
|---|
| 262 | } |
|---|
| 263 | |
|---|
| 264 | # ------------------------------------------------------------------------- |
|---|
| 265 | # Description: |
|---|
| 266 | # Perform uudecoding of a file or data. A file may contain more than one |
|---|
| 267 | # encoded data section so the result is a list where each element is a |
|---|
| 268 | # three element list of the provided filename, the suggested mode and the |
|---|
| 269 | # data itself. |
|---|
| 270 | # |
|---|
| 271 | proc ::uuencode::uudecode {args} { |
|---|
| 272 | array set opts {mode 0644 filename {}} |
|---|
| 273 | set wrongargs "wrong \# args: should be \"uudecode (-file filename | ?--? string)\"" |
|---|
| 274 | while {[string match -* [lindex $args 0]]} { |
|---|
| 275 | switch -glob -- [lindex $args 0] { |
|---|
| 276 | -f* { |
|---|
| 277 | if {[llength $args] < 2} { |
|---|
| 278 | return -code error $wrongargs |
|---|
| 279 | } |
|---|
| 280 | set opts(filename) [lindex $args 1] |
|---|
| 281 | set args [lreplace $args 0 0] |
|---|
| 282 | } |
|---|
| 283 | -- { |
|---|
| 284 | set args [lreplace $args 0 0] |
|---|
| 285 | break |
|---|
| 286 | } |
|---|
| 287 | default { |
|---|
| 288 | return -code error "bad option [lindex $args 0]:\ |
|---|
| 289 | must be -file" |
|---|
| 290 | } |
|---|
| 291 | } |
|---|
| 292 | set args [lreplace $args 0 0] |
|---|
| 293 | } |
|---|
| 294 | |
|---|
| 295 | if {$opts(filename) != {}} { |
|---|
| 296 | set f [open $opts(filename) r] |
|---|
| 297 | set data [read $f] |
|---|
| 298 | close $f |
|---|
| 299 | } else { |
|---|
| 300 | if {[llength $args] != 1} { |
|---|
| 301 | return -code error $wrongargs |
|---|
| 302 | } |
|---|
| 303 | set data [lindex $args 0] |
|---|
| 304 | } |
|---|
| 305 | |
|---|
| 306 | set state false |
|---|
| 307 | set result {} |
|---|
| 308 | |
|---|
| 309 | foreach {line} [split $data "\n"] { |
|---|
| 310 | switch -exact -- $state { |
|---|
| 311 | false { |
|---|
| 312 | if {[regexp {^begin ([0-7]+) ([^\s]*)} $line \ |
|---|
| 313 | -> opts(mode) opts(name)]} { |
|---|
| 314 | set state true |
|---|
| 315 | set r {} |
|---|
| 316 | } |
|---|
| 317 | } |
|---|
| 318 | |
|---|
| 319 | true { |
|---|
| 320 | if {[string match "end" $line]} { |
|---|
| 321 | set state false |
|---|
| 322 | lappend result [list $opts(name) $opts(mode) $r] |
|---|
| 323 | } else { |
|---|
| 324 | scan $line %c c |
|---|
| 325 | set n [expr {($c - 0x21)}] |
|---|
| 326 | append r [string range \ |
|---|
| 327 | [decode [string range $line 1 end]] 0 $n] |
|---|
| 328 | } |
|---|
| 329 | } |
|---|
| 330 | } |
|---|
| 331 | } |
|---|
| 332 | |
|---|
| 333 | return $result |
|---|
| 334 | } |
|---|
| 335 | |
|---|
| 336 | # ------------------------------------------------------------------------- |
|---|
| 337 | |
|---|
| 338 | package provide uuencode $::uuencode::version |
|---|
| 339 | |
|---|
| 340 | # ------------------------------------------------------------------------- |
|---|
| 341 | # |
|---|
| 342 | # Local variables: |
|---|
| 343 | # mode: tcl |
|---|
| 344 | # indent-tabs-mode: nil |
|---|
| 345 | # End: |
|---|
| 346 | |
|---|