changeset: 156:db1ee7036df0 user: Oliver Heimlich date: Tue Jul 07 22:46:46 2015 +0200 summary: base64decode.m: Cleanup of function code and fix of bug #38974 --- a/NEWS +++ b/NEWS @@ -1,3 +1,11 @@ +Summary of important user-visible changes for strings 1.2.1: +------------------------------------------------------------------- + + ** The function base64decode has been almost rewritten and no longer returns + trailing zeros which have been introduced by padding (bug #38974), returns + uint8 vectors instead of binary64 vectors, and should perform faster. + + Summary of important user-visible changes for strings 1.2.0: ------------------------------------------------------------------- --- a/inst/base64decode.m +++ b/inst/base64decode.m @@ -1,4 +1,5 @@ ## Copyright (C) 2007 Muthiah Annamalai +## Copyright (C) 2015 Oliver Heimlich ## ## This program is free software; you can redistribute it and/or modify it under ## the terms of the GNU General Public License as published by the Free Software @@ -18,7 +19,8 @@ ## @deftypefnx {Function File} {@var{rval} =} base64decode (@var{code}, @var{as_string}) ## Convert a base64 @var{code} (a string of printable characters according to RFC 2045) ## into the original ASCII data set of range 0-255. If option @var{as_string} is -## passed, the return value is converted into a string. +## passed, the return value is converted into a string. Otherwise, the return +## value is a uint8 row vector. ## ## @example ## @group @@ -32,125 +34,84 @@ ## @seealso {base64encode} ## @end deftypefn -function z = base64decode (X, as_string) - if (nargin < 1 ) +function Z = base64decode (X, as_string) + if (nargin < 1 || nargin > 2) print_usage; - elseif nargin == 1 - as_string=false; + elseif (nargin == 1) + as_string = false; endif - if ( any(X(:) < 0) || any(X(:) > 255)) - error("base64decode is expecting integers in the range 0 .. 255"); + ## strip white space + X((X == ' ') | (X == "\n") | (X == "\r")) = []; + + if (any (X(:) < 0) || any(X(:) > 255)) + error ("base64decode is expecting integers in the range 0 .. 255"); endif - ## decompose strings into the 4xN matrices - ## formatting issues. - if( rows(X) == 1 ) - Y=[]; - L=length(X); - for z=4:4:L - Y=[Y X(z-3:z)']; #keep adding columns - end - if min(size(Y))==1 - Y=reshape(Y,[L, 1]); - else - Y=reshape(Y,[4,L/4]); - end - X=Y; - Y=[]; - end - - X = toascii(X); - Xa= X; - - ## Work backwards. Starting at step in table, - ## lookup the index of the element in the table. + ## convert into ascii code and 8 bit integers to save memory + X = uint8 (X); - ## 6-bit encoding table, plus 1 for padding - ## 26*2 + 10 + 2 + 1 = 64 + 1, '=' is EOF stop mark. - table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - - S=size(X); - SRows=S(1); - SCols=S(2); - Y=zeros(S); - - ## decode the incoming matrix & + ## decompose into the 4xN matrices - separation of encoded 3 byte blocks + if (rows (X) ~= 4) + if (rem (numel (X), 4) ~= 0) + error ("base64decode is expecting blocks of 4 characters to decode"); + endif + X = reshape (X, [4, numel(X)./4]); + endif + + ## decode 6-bit values from the incoming matrix & ## write the values into Va matrix. - Va = -1*ones(size(Xa)); + Va = ones (size(X), 'uint8') .* 65; - iAZ = (Xa >= 'A').*(Xa <= 'Z') > 0; - Va(iAZ)=Xa(iAZ)-'A'; - - iaz = (Xa >= 'a').*(Xa <= 'z') > 0; - Va(iaz)=Xa(iaz)-'a'+26; + iAZ = ((X >= 'A') & (X <= 'Z')); + Va(iAZ) = X(iAZ) - 'A'; - i09 = (Xa >= '0').*(Xa <= '9') > 0; - Va(i09)=Xa(i09)-'0'+52; + iaz = ((X >= 'a') & (X <= 'z')); + Va(iaz) = X(iaz) - 'a' + 26; - is = (Xa == '/') ; Va(is) = 63; - ip = (Xa == '+') ; Va(ip) = 62; - ieq = (Xa == '=') ; Va(ieq) = 0; - clear is; clear ieq; clear ip; clear i09; - clear iaz; clear iAZ; clear Xa; clear X; + i09 = ((X >= '0') & (X <= '9')); + Va(i09) = X(i09) - '0' + 52; - Y=Va; clear Va; - Y1=Y(1,:); - if (SRows > 1) - Y2=Y(2,:); - else - Y2=zeros(1,SCols); - end; - - if (SRows > 2) - Y3=Y(3,:); - else - Y3=zeros(1,SCols); - end; - - if (SRows > 3) - Y4=Y(4,:); - else - Y4=zeros(1,SCols); - end; + Va(X == '+') = 62; + Va(X == '/') = 63; + + padding = (X == '='); + Va(padding) = 0; + + if (any (any (Va == 65))) + error ('base64decode is expecting valid characters A..Za..z0..9+/='); + endif + + if (not (isempty (X)) && ... + (find (padding, 1) < numel (X) - 1 || ... + (X(end - 1) == '=' && X(end) ~= '='))) + error ('base64decode is expecting max two padding characters at the end'); + endif + + ## decode 4x6 bit into 3x8 bit + B = vertcat (... + Va(1, :) .* 4 + (Va(2, :) - rem (Va(2, :), 16)) ./ 16, ... + rem (Va(2, :), 16) .* 16 + (Va(3, :) - rem (Va(3, :), 4)) ./ 4, ... + rem (Va(3, :), 4) .* 64 + Va(4, :)); - ## +1 not required due to ASCII subtraction - ## actual decoding work - b1 = Y1*4 + fix(Y2/16); - b2 = mod(Y2,16)*16+fix(Y3/4); - b3 = mod(Y3,4)*64 + Y4; - - ZEROS=sum(sum(Y==0)); - L=length(b1)*3; - z=zeros(1,L); - z(1:3:end)=b1; - if (SRows > 1) - z(2:3:end)=b2; - else - z(2:3:end)=[]; - end; - - if (SRows > 2) - z(3:3:end)=b3; - else - z(3:3:end)=[]; - end - - ## FIXME - ## is this expected behaviour? - if ( as_string ) - L=length(z); - while ( ( L > 0) && ( z(L)==0 ) ) - L=L-1; - end - z=char(z(1:L)); + ## Convert blocks into row vector + Z = B(:).'; + + ## Remove byte blocks which have been introduced by padding + if (not (isempty (X))) + Z(end - sum (padding(end - 1 : end)) + 1 : end) = []; + endif + + if (as_string) + Z = char (Z); end endfunction %!assert(base64decode(base64encode('Hakuna Matata'),true),'Hakuna Matata') -%!assert(base64decode(base64encode([1:255])),[1:255]) +%!assert(base64decode(base64encode([1:255])),uint8([1:255])) %!assert(base64decode(base64encode('taken'),true),'taken') %!assert(base64decode(base64encode('sax'),true),'sax') %!assert(base64decode(base64encode('H'),true),'H') +%!assert(base64decode(base64encode('H'),false),uint8('H')) %!assert(base64decode(base64encode('Ta'),true),'Ta')