# HG changeset patch # User Stefan Vogel # Date 1016448948 -3600 # Node ID 1c8e7e7785ab86eb5645eb2f3314a48636cb325a # Parent 81cd85ca31d7cb57eae281a25795662e74cd21f9 initial checkin diff -r 81cd85ca31d7 -r 1c8e7e7785ab Base64Coder.st --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Base64Coder.st Mon Mar 18 11:55:48 2002 +0100 @@ -0,0 +1,235 @@ +"{ Package: 'stx:libbasic2' }" + +ObjectCoder subclass:#Base64Coder + instanceVariableNames:'buffer bits charCount atEnd' + classVariableNames:'Base64Mapping Base64ReverseMapping' + poolDictionaries:'' + category:'System-Storage' +! + +!Base64Coder class methodsFor:'documentation'! + +documentation +" + Instances of this class perform Base64 en- and decoding as defined in RFC 2045 + 3 bytes are mapped to 4 characters, representing 6 bits each. + + [author:] + Stefan Vogel + + [see also:] + + [instance variables:] + buffer SmallInteger Up to 24 bits of data + bits SmallInteger Number of valid bits in buffer + charCount SmallInteger Number of characters since last cr + atEnd Boolean true if end of Base64 string reached + + [class variables:] + Base64Mapping String Mapping from bytes (with 6 valid bits) + to Base64 characters + Base64ReverseMapping Array Mapping from Base64 characters to 6-bit-Bytes +" +! + +examples +" + + [exBegin] + |coder| + + coder := Base64Coder on:'' writeStream. + coder nextPutAll:#[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19]. + coder flush. + coder contents inspect. + coder reset. + coder nextPut:254. + coder contents inspect. + [exEnd] + + [exBegin] + |coder coder1| + + coder := Base64Coder on:'' writeStream. + coder nextPutAll:#[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20]. + coder flush. + + coder1 := Base64Coder on:coder contents readStream. + [coder1 atEnd] whileFalse:[ + Transcript show:coder1 next + ]. + Transcript cr. + [exEnd] +" +! ! + +!Base64Coder class methodsFor:'initialization'! + +initialize + "initialize class variables" + + "64 characters representing the 6-bit values from 0-63 and one pad character" + Base64Mapping := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='. + Base64ReverseMapping := ByteArray new:256 withAll:255. + Base64Mapping keysAndValuesDo:[:idx :char| + Base64ReverseMapping at:char asciiValue put:idx-1. + ]. + + " + self initialize + " +! ! + +!Base64Coder class methodsFor:'instance creation'! + +new + ^ self basicNew initialize +! ! + +!Base64Coder methodsFor:'encoding'! + +encodeObject:anObject with:aParameter + "not defined. Use nextPut or nextPutAll:. + Could encode the printString here" + + ^ self shouldNotImplement +! + +next + "answer the next decoded byte" + + |b shift| + + bits == 0 ifTrue:[ + "no more bits in buffer + if end has been reached, raise signal" + atEnd ifTrue:[ + ^ stream endOfStreamSignal raiseRequest. + ]. + + "read next 4 characters each representing 6 bits" + buffer := 0. + [ + "read next valid Base64 character, skip invalid characters" + [ + b := Base64ReverseMapping at:stream next asciiValue. + ] doWhile:[b == 255]. + + b == 64 ifTrue:[ + "got #=, end of Base64 string has been reached" + atEnd := true. + bits == 12 ifTrue:[ + "data has been padded to 12, skip 4 bits" + shift := -4. + ] ifFalse:[ + "data has been padded to 18, skip 2 bits" + shift := -2. + ]. + bits := bits + shift. + buffer := buffer bitShift:shift. + ] ifFalse:[ + "got valid Base64 character, append to buffer" + buffer := (buffer bitShift:6) bitOr:b. + bits := bits + 6. + ]. + ] doWhile:[bits ~~ 24 and:[atEnd not]]. + ]. + + b := (buffer bitShift:(8 - bits)) bitAnd:16rFF. + bits := bits - 8. + + ^ b. +! + +nextPut:aByte + "encode aByte on the output stream" + + |b1 b2 b3 b4| + + buffer := (buffer bitShift:8) bitOr:aByte. + bits := bits + 8. + bits == 24 ifTrue:[ + b4 := buffer bitAnd:16r3F. + b3 := (buffer bitShift:-6) bitAnd:16r3F. + b2 := (buffer bitShift:-12) bitAnd:16r3F. + b1 := (buffer bitShift:-18) bitAnd:16r3F. + buffer := bits := 0. + stream nextPut:(Base64Mapping at:b1+1); + nextPut:(Base64Mapping at:b2+1); + nextPut:(Base64Mapping at:b3+1); + nextPut:(Base64Mapping at:b4+1). + + "RFC 2045 says: max 76 characters in one line" + charCount >= 68 ifTrue:[ + stream cr. + charCount := 0. + ] ifFalse:[ + charCount := charCount + 4. + ] + ]. +! ! + +!Base64Coder methodsFor:'initialization'! + +initialize + + buffer := bits := charCount := 0. + atEnd := false. +! ! + +!Base64Coder methodsFor:'misc'! + +flush + "flush the remaining bits of buffer. + The number of bits in buffer is not a multiple of 6, so we pad + the buffer and signal that padding has been done via $= characters." + + |b1 b2 b3 b4| + + bits == 0 ifTrue:[ + "buffer is empty, nothing to do" + ^ self. + ]. + + bits == 8 ifTrue:[ + buffer := buffer bitShift:4. + b4 := b3 := 64. "pad with '=='" + b1 := (buffer bitShift:-6) bitAnd:16r3F. + b2 := buffer bitAnd:16r3F. + ] ifFalse:[bits = 16 ifTrue:[ + buffer := buffer bitShift:2. + b4 := 64. "pad with '='" + b3 := buffer bitAnd:16r3F. + b2 := (buffer bitShift:-6) bitAnd:16r3F. + b1 := (buffer bitShift:-12) bitAnd:16r3F. + ]]. + bits := buffer := 0. + + stream nextPut:(Base64Mapping at:b1+1); + nextPut:(Base64Mapping at:b2+1); + nextPut:(Base64Mapping at:b3+1); + nextPut:(Base64Mapping at:b4+1). +! + +reset + "reset to initial state" + + super reset. + buffer := bits := charCount := 0. + atEnd := false. +! ! + +!Base64Coder methodsFor:'queries'! + +atEnd + "answer true, if no more bytes can be read" + + ^ atEnd or:[bits == 0 and:[stream atEnd]] +! ! + +!Base64Coder class methodsFor:'documentation'! + +version + ^ '$Header: /cvs/stx/stx/libbasic2/Base64Coder.st,v 1.1 2002-03-18 10:55:48 stefan Exp $' +! ! +Base64Coder initialize!