-- Base64 ENCODE/UNENCODE -- -- -- © May 2003, OpenSpark Interactive Ltd -- -- -- For the description of the base64 format: -- -- ---------------------------------------------------------------------- -- 070302 JN: tLookUp converted to a list (rather than a string), -- which improves performance by 20% on Mac G4 -- 040227 JN: base64Encode() handler improved by using a technique -- pioneered by Robert Tweed , -- found at: -- ---------------------------------------------------------------------- -- -- PUBLIC METHODS -- -------------- -- base64Encode(aString) -- base64Unencode(aString) -- base64EncodeFileToString({aSourceFile}) -- base64UnencodeStringToFile(aString {, aTargetFile}) -- ---------------------------------------------------------------------- -- NOTE: If the Progress Broker script is available and suitable -- sprites have been registered with it, a progress bar and display -- will be shown. You may need to comment out the following handlers -- in this script before the progress display will appear: -- - ProgressSetDisplay() -- - ProgressSetMaxValue() -- - ProgressSetValue() ---------------------------------------------------------------------- -- STRING TO STRING METHODS-- on base64Encode(aString) --------------------------------------------- -- INPUT: should be a string -- OUTPUT: an encoded version of aString where each original group -- of 3 letters is converted to a group of 4 letters in the -- range A-Z a-z 0-9 + / = -------------------------------------------------------------------- if not stringP(aString) then return #stringExpected end if -- Initialize the output string tBase64 = "" tLookUp = ["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", "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", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "/"] tCLRF = RETURN&numToChar(10) -- HARDCODED for Windows tBreak = 0 -- used to limit the output to 76 characters per line -- Iterate through the input string tCount = the number of chars of aString tRemainder = tCount mod 3 tCount = tCount - tRemainder repeat with i = 1 to tCount a = charToNum(char i of aString) b = charToNum(char i + 1 of aString) c = charToNum(char i + 2 of aString) -- Convert the first 6 bits of a to a code character put tLookup.getAt(a / 4 + 1) after tBase64 -- Concatenate the last 2 bits of a with the first 4 bits of b -- then convert to 6-bit code character put tLookup.getAt((a mod 4) * 16 + (b / 16) + 1) after tBase64 -- Concatenate the last 4 bits of b with the first 2 bits of c, -- then convert to 6-bit code character, then convert the final -- 6-bits of tCharNum put tLookup.getAt((b mod 16) * 4 + (c / 64) + 1) after tBase64 put tLookup.getAt((c mod 64) + 1) after tBase64 -- Don't allow more than 76 output characters per line tBreak = tBreak + 4 if tBreak = 76 then put tCLRF after tBase64 tBreak = 0 end if -- Iterate in steps of 3 i = i + 2 end repeat -- Pad the end of the string if it is not a multiple of 4 characters case tRemainder of 1: a = charToNum(char i of aString) put tLookup.getAt(a / 4 + 1) after tBase64 put tLookup.getAt((a mod 4) * 16 + 1) after tBase64 put "==" after tBase64 2: a = charToNum(char i of aString) b = charToNum(char i + 1 of aString) put tLookup.getAt(a / 4 + 1) after tBase64 put tLookup.getAt((a mod 4) * 16 + (b / 16) + 1) after tBase64 put tLookup.getAt((b mod 16) * 4 + 1) after tBase64 put "=" after tBase64 end case return tBase64 end base64Encode on base64Unencode(aString) ------------------------------------------- -- INPUT: should be a string. It may contain any -- characters but all those not in the range A-Z a-z 0-9 + / -- will be ignored -- OUTPUT: the unencoded version of aString -------------------------------------------------------------------- if not stringP(aString) then return #stringExpected end if -- Initialize the output string tClear = "" tPosition = 1 -- used to count out groups of 4 input characters -- Iterate through the input string tCount = aString.char.count repeat with i = 1 to tCount tChar = aString.char[1] delete aString.char[1] tTemp = offset(tChar,"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/") if not tTemp then -- Ignore any padding, line feeds or other unexpected characters next repeat end if -- Convert the character code to a 6-bit integer if tTemp < 27 then tTemp = tTemp - 1 + (charToNum(tChar) > 96) * 26 else tTemp = tTemp + 25 end if case tPosition of 1: -- The last 2 bits are stored in the next input character tCarry = tTemp * 4 tPosition = 2 2: -- Concatenate the bits from the previous character with the -- first 2 bits from the current character, and save the -- remaining 4 bits for later put numToChar(tCarry + (tTemp / 16)) after tClear tCarry = (tTemp mod 16) * 16 tPosition = 3 3: -- Concatenate the 4 bits carried over from the previous -- character with the first 4 bits from the current character, -- and save the remaining 2 bits for later put numToChar(tCarry + (tTemp / 4)) after tClear tCarry = (tTemp mod 4) * 64 tPosition = 4 4: -- Concatenate the 2 bits carried over from the previous -- character with the bits from the current character put numToChar(tCarry + tTemp) after tClear -- start a new set tPosition = 1 end case end repeat return tClear end base64Unencode -- STRING TO FILE METHODS -- on base64EncodeFileToString(aSourceFile) ----------------------------- -- INPUT: should be a valid string path to a binary -- file. The file may contain numToChar(0) characters. -- OUTPUT: an encoded string version of aSourceFile (where each -- original group of 3 bytes is converted to a group of 4 -- letters in the range A-Z a-z 0-9 + / =), or an error -- symbol -------------------------------------------------------------------- -- Check that the FileIO xtra is present... if XtrasMissing([#fileIO]) then return #fileIOMissing else -- ... and can be instantiated tFileIO = xtra("fileIO").new() if not objectP(tFileIO) then return #noFileIOObject end if end if if not stringP(aSourceFile) then -- Allow the user to choose a source file aSourceFile = tFileIO.displayOpen() case aSourceFile of "", VOID: return #invalidFilePath end case end if -- Open the file and read in the data tFileIO.openFile(aSourceFile, 1) -- read only if tFileIO.status() then tFileIO.closeFile() return #invalidFile else tLength = tFileIO.getLength() if not tLength then tFileIO.closeFile() return #illegibleFile end if end if -- The following two call are intended for the Progress Broker -- script. If this is not available they will be fielded by the -- placeholder handlers at the end of this script ProgressSetMaxValue(tLength) ProgressSetValue(0) ProgressSetDisplay([\ #starting: "Encoding ^2 to base 64", \ #progress: \ "^1 of ^2 encoded to base 64 (^3) at ^6"&RETURN&\ "Estimated time left: ^4", \ #complete: "^2 encoded in ^5", \ #unit: #data, \ #treated: "^1",\ #total: "^2", \ #percent: "^3", \ #timeLeft: "^4", \ #elapsed: "^5", \ #rate: "^6"]) -- Initialize the output string tBase64 = "" tBreak = 0 -- used to limit the output to 76 characters per line tPosition = 1 -- used to count out groups of 3 input characters -- Iterate through the input string repeat with i = 1 to tLength tChar = tFileIO.readChar() tCharNum = charToNum(tChar) if not (i mod 512) then ProgressSetValue(i) -- in Progress Broker (or see below) updateStage end if case tPosition of 1: -- Convert the first 6 bits of tCharNum to a code character put base64Digit(tCharNum / 4) after tBase64 tCarry = (tCharNum mod 4) * 16 tPosition = 2 2: -- Concatenate the last 2 bits of the previous tCharNum with -- the first 4 bits of this tCharNum, then convert to 6-bit -- code character put base64Digit(tCarry + (tCharNum / 16)) after tBase64 tCarry = (tCharNum mod 16) * 4 tPosition = 3 3: -- Concatenate the last 4 bits of the previous tCharNum with -- the first 2 bits of this tCharNum, then convert to 6-bit -- code character, then convert the final 6-bits of tCharNum put base64Digit(tCarry + (tCharNum / 64)) after tBase64 put base64Digit(tCharNum mod 64) after tBase64 tPosition = 1 -- start another group of 3 letters -- Don't allow more than 76 output characters per line tBreak = tBreak + 4 if tBreak = 76 then put RETURN&numToChar(10) after tBase64 tBreak = 0 end if end case end repeat -- Pad the end of the string if it is not a multiple of 4 characters case tPosition of 2: put base64Digit(tCarry)&"==" after tBase64 3: put base64Digit(tCarry)&"=" after tBase64 end case tFileIO.closeFile() ProgressSetValue(tLength) -- in Progress Broker (or see below) return tBase64 end base64EncodeFileToString on base64UnencodeStringToFile(aString, aTargetFile) ------------------ -- INPUT: should be a string. It may contain any -- characters but all those not in the range A-Z a-z 0-9 + / -- will be ignored -- should be valid path to a file (which will -- be created if necessary). If it is not a string then the -- the user will be asked to choose an output file. -- ACTION: writes the unencoded version of aString out to the -- chosen file -- OUTPUT: returns the file path to the output file, or an error -- symbol -------------------------------------------------------------------- if not stringP(aString) then return #stringExpected end if -- Check that the FileIO xtra is present... if XtrasMissing([#fileIO]) then return #fileIOMissing else -- ... and can be instantiated tFileIO = xtra("fileIO").new() if not objectP(tFileIO) then return #noFileIOObject end if end if if not stringP(aTargetFile) then -- Allow the user to choose a target file aTargetFile = tFileIO.displaySave("", "") case aTargetFile of "", VOID: return #invalidFilePath end case end if -- Open the file and read in the data tFileIO.openFile(aTargetFile, 2) -- write only tStatus = tFileIO.status() case tStatus of 0, -36: -- "OK": The file already exists -- or "I/O Error": The file may exist as read only -- In either case, the user has chosen to replace the existing -- file tFileIO.delete() -37: -- "Bad file name": create the file if possible otherwise:` -- Other error -- put tStatus, tFileIO.error(tStatus) tFileIO.closeFile() return #invalidFile end case tFileIO.createFile(aTargetFile) if tFileIO.status() then return #fileNotCreated else tFileIO.openFile(aTargetFile, 2) -- write only end if tString = "" tPosition = 1 -- used to count out groups of 4 input characters -- Cut the input string into 3K chunks for performance reasons tChunks = (aString.char.count / 3072) + 1 -- The following two call are intended for the Progress Broker -- script. If this is not available they will be fielded by the -- placeholder handlers at the end of this script tValue = 0 ProgressSetMaxValue(tChunks) ProgressSetValue(tValue) ProgressSetDisplay([\ #starting: "Unencoding from base 64", \ #progress: \ "^3 unencoded from base 64 at ^6"&RETURN&\ "Estimated time left: ^4", \ #complete: "File unencoded in ^5", \ #unit: #data, \ #percent: "^3", \ #timeLeft: "^4", \ #elapsed: "^5", \ #rate: "^6"]) repeat while tChunks tChunk = aString.char[1..3072] delete aString.char[1..3072] -- Iterate through the input string tCount = tChunk.char.count repeat with i = 1 to tCount tChar = tChunk.char[i] -- faster than read char 1, delete char 1 if not("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/"contains tChar)then -- Ignore any padding, line feeds or other unexpected -- characters next repeat end if -- Convert the character code to a 6-bit integer tTemp = offset(tChar,"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/") if tValue < 27 then tTemp = tTemp - 1 + (charToNum(tChar) > 96) * 26 else tTemp = tTemp + 25 end if case tPosition of 1: -- The last 2 bits are stored in the next input character tCarry = tTemp * 4 tPosition = 2 2: -- Concatenate the bits from the previous character with the -- first 2 bits from the current character, and save the -- remaining 4 bits for later tFileIO.writeChar(numToChar(tCarry + (tTemp / 16))) tCarry = (tTemp mod 16) * 16 tPosition = 3 3: -- Concatenate the 4 bits carried over from the previous -- character with the first 4 bits from the current -- character, and save the remaining 2 bits for later tFileIO.writeChar(numToChar(tCarry + (tTemp / 4))) tCarry = (tTemp mod 4) * 64 tPosition = 4 4: -- Concatenate the 2 bits carried over from the previous -- character with the bits from the current character tFileIO.writeChar(numToChar(tCarry + tTemp)) tPosition = 1 -- start a new set end case end repeat tChunks = tChunks - 1 tValue = tValue + 1 ProgressSetValue(tValue) -- in Progress Broker (or see below) updateStage end repeat tFileIO.closeFile() return aTargetFile end base64UnencodeStringToFile -- CHARACTER BY CHARACTER METHODS -- on base64Digit(anInteger) -------------------------------------------- -- INPUT: should be an integer in the range 0-63 -- OUTPUT: an alpha-numeric character, + or / -------------------------------------------------------------------- return char (anInteger + 1) of "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" end base64Digit on convertFromBase64(aCharacter)-------------------------------------- -- INPUT: should be an alpha-numeric character, + or / -- OUTPUT: an integer in the range 0-63 -------------------------------------------------------------------- tValue = offset(aCharacter,"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/") if tValue < 27 then return tValue - 1 + (charToNum(aCharacter) > 96) * 26 else return tValue + 25 end if end convertFromBase64 -- PLACEHOLDER HANDLERS -- -- If the Progress Broker script is not available, the following -- handlerl will field the call intended for the Progress Broker. on ProgressSetDisplay() end on ProgressSetMaxValue() end on ProgressSetValue() end -- UTILITY HANDLER -- on XtrasMissing(anXtrasList, showAlert) ------------------------------ -- INPUT: should be a list of string or symbol xtra -- names -- OUTPUT: Returns TRUE if any of the xtras in are -- missing. Once the handler has finished executing, -- contains only the names of the missing -- xtras. -------------------------------------------------------------------- i = the number of xtras repeat while i anXtrasList.deleteOne(xtra(i).name) i = i - 1 end repeat tResult = anXtrasList.count() if tResult then if not showAlert then -- The calling handler will deal with any alerts required else tXtraNames = "" repeat with i = 1 to tResult put RETURN&anXtrasList[i] after tXtraNames end repeat if tResult = 1 then tMessage = "The following xtra is missing:" else tMessage = "The following xtras are missing:" end if alert tMessage&RETURN&tXtraNames end if end if return tResult end XtrasMissing