123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294(*
* Copyright (c) 2006-2009 Citrix Systems Inc.
* Copyright (c) 2010 Thomas Gazagnaire <thomas@gazagnaire.com>
* Copyright (c) 2014-2016 Anil Madhavapeddy <anil@recoil.org>
* Copyright (c) 2016 David Kaloper Meršinjak
* Copyright (c) 2018 Romain Calascibetta <romain.calascibetta@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*)open!Stdlibtype('a,'b)result=|Okof'a|Errorof'btypealphabet={emap:intarray;dmap:intarray}typesub=string*int*intlet(//)xy=ifx>0then1+((x-1)/y)else0letunsafe_get_uint8toff=Char.code(String.unsafe_gettoff)letunsafe_set_uint8toffv=Bytes.unsafe_settoff(Char.chrv)externalunsafe_set_uint16:bytes->int->int->unit="%caml_bytes_set16u"[@@noalloc]externalunsafe_get_uint16:string->int->int="%caml_string_get16u"[@@noalloc]externalswap16:int->int="%bswap16"[@@noalloc]letnone=-1(* We mostly want to have an optional array for [dmap] (e.g. [int option
array]). So we consider the [none] value as [-1]. *)letmake_alphabetalphabet=ifString.lengthalphabet<>64theninvalid_arg"Length of alphabet must be 64";ifString.containsalphabet'='theninvalid_arg"Alphabet can not contain padding character";letemap=Array.init(String.lengthalphabet)~f:(funi->Char.codealphabet.[i])inletdmap=Array.make256noneinString.iteri~f:(funidxchr->dmap.(Char.codechr)<-idx)alphabet;{emap;dmap}letlength_alphabet{emap;_}=Array.lengthemapletalphabet{emap;_}=emapletdefault_alphabet=make_alphabet"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"leturi_safe_alphabet=make_alphabet"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"letunsafe_set_be_uint16=ifSys.big_endianthenfuntoffv->unsafe_set_uint16toffvelsefuntoffv->unsafe_set_uint16toff(swap16v)(* We make this exception to ensure to keep a control about which exception we
can raise and avoid appearance of unknown exceptions like an ex-nihilo
magic rabbit (or magic money?). *)exceptionOut_of_boundsletget_uint8toff=ifoff<0||off>=String.lengthtthenraiseOut_of_bounds;unsafe_get_uint8toffletpadding=int_of_char'='leterror_msgffmt=Format.ksprintf(funerr->Error(`Msgerr))fmtletencode_subpad{emap;_}?(off=0)?leninput=letlen=matchlenwith|Somelen->len|None->String.lengthinput-offiniflen<0||off<0||off>String.lengthinput-lenthenerror_msgf"Invalid bounds"elseletn=leninletn'=n//3*4inletres=Bytes.createn'inletemapi=Array.unsafe_getemapiinletemitb1b2b3i=unsafe_set_be_uint16resi((emap((b1lsr2)land0x3f)lsl8)loremap((b1lsl4)lor(b2lsr4)land0x3f));unsafe_set_be_uint16res(i+2)((emap((b2lsl2)lor(b3lsr6)land0x3f)lsl8)loremap(b3land0x3f))inletrecencji=ifi=nthen()elseifi=n-1thenemit(unsafe_get_uint8input(off+i))00jelseifi=n-2thenemit(unsafe_get_uint8input(off+i))(unsafe_get_uint8input(off+i+1))0jelse(emit(unsafe_get_uint8input(off+i))(unsafe_get_uint8input(off+i+1))(unsafe_get_uint8input(off+i+2))j;enc(j+4)(i+3))inletrecunsafe_fix=function|0->()|i->unsafe_set_uint8res(n'-i)padding;unsafe_fix(i-1)inenc00;letpad_to_write=(3-(nmod3))mod3inifpadthen(unsafe_fixpad_to_write;Ok(Bytes.unsafe_to_stringres,0,n'))elseOk(Bytes.unsafe_to_stringres,0,n'-pad_to_write)(* [pad = false], we don't want to write them. *)letencode?(pad=true)?(alphabet=default_alphabet)?off?leninput=matchencode_subpadalphabet?off?leninputwith|Ok(res,off,len)->Ok(String.subres~pos:off~len)|Error_aserr->errletencode_string?pad?alphabetinput=matchencode?pad?alphabetinputwith|Okres->res|Error_->assertfalseletencode_sub?(pad=true)?(alphabet=default_alphabet)?off?leninput=encode_subpadalphabet?off?leninputletencode_exn?pad?alphabet?off?leninput=matchencode?pad?alphabet?off?leninputwith|Okv->v|Error(`Msgerr)->invalid_argerrletdecode_sub?(pad=true){dmap;_}?(off=0)?leninput=letlen=matchlenwith|Somelen->len|None->String.lengthinput-offiniflen<0||off<0||off>String.lengthinput-lenthenerror_msgf"Invalid bounds"elseletn=len//4*4inletn'=n//4*3inletres=Bytes.createn'inletget_uint8_or_padding=ifpadthen(funti->ifi>=lenthenraiseOut_of_bounds;get_uint8t(off+i))elsefunti->tryifi<lenthenget_uint8t(off+i)elsepaddingwithOut_of_bounds->paddinginletset_be_uint16toffv=(* can not write 2 bytes. *)ifoff<0||off+1>Bytes.lengthtthen()(* can not write 1 byte but can write 1 byte *)elseifoff<0||off+2>Bytes.lengthtthenunsafe_set_uint8toff(vlsr8)(* can write 2 bytes. *)elseunsafe_set_be_uint16toffvinletset_uint8toffv=ifoff<0||off>=Bytes.lengthtthen()elseunsafe_set_uint8toffvinletemitabcdj=letx=(alsl18)lor(blsl12)lor(clsl6)lordinset_be_uint16resj(xlsr8);set_uint8res(j+2)(xland0xff)inletdmapi=letx=Array.unsafe_getdmapiinifx=nonethenraiseNot_found;xinletonly_paddingpadidx=(* because we round length of [res] to the upper bound of how many
characters we should have from [input], we got at this stage only padding
characters and we need to delete them, so for each [====], we delete 3
bytes. *)letpad=ref(pad+3)inletidx=refidxinwhile!idx+4<lendo(* use [unsafe_get_uint16] instead [unsafe_get_uint32] to avoid allocation
of [int32]. Of course, [3d3d3d3d] is [====]. *)ifunsafe_get_uint16input(off+!idx)<>0x3d3d||unsafe_get_uint16input(off+!idx+2)<>0x3d3dthenraiseNot_found;(* We got something bad, should be a valid character according to
[alphabet] but outside the scope. *)idx:=!idx+4;pad:=!pad+3done;while!idx<lendoifunsafe_get_uint8input(off+!idx)<>paddingthenraiseNot_found;incridxdone;!padinletrecdecji=ifi=nthen0elseletd,pad=letx=get_uint8_or_paddinginput(i+3)intrydmapx,0withNot_foundwhenx=padding->0,1in(* [Not_found] iff [x ∉ alphabet and x <> '='] can leak. *)letc,pad=letx=get_uint8_or_paddinginput(i+2)intrydmapx,padwithNot_foundwhenx=padding&&pad=1->0,2in(* [Not_found] iff [x ∉ alphabet and x <> '='] can leak. *)letb,pad=letx=get_uint8_or_paddinginput(i+1)intrydmapx,padwithNot_foundwhenx=padding&&pad=2->0,3in(* [Not_found] iff [x ∉ alphabet and x <> '='] can leak. *)leta,pad=letx=get_uint8_or_paddinginputiintrydmapx,padwithNot_foundwhenx=padding&&pad=3->0,4in(* [Not_found] iff [x ∉ alphabet and x <> '='] can leak. *)emitabcdj;ifi+4=n(* end of input in anyway *)thenmatchpadwith|0->0|4->3(* [get_uint8] lies and if we get [4], that mean we got one or more (at
most 4) padding character. In this situation, because we round length
of [res] (see [n // 4]), we need to delete 3 bytes. *)|pad->padelsematchpadwith|0->dec(j+3)(i+4)|4->only_padding3(i+4)(* Same situation than above but we should get only more padding
characters then. *)|pad->only_paddingpad(i+4)inmatchdec00with|0->Ok(Bytes.unsafe_to_stringres,0,n')|pad->Ok(Bytes.unsafe_to_stringres,0,n'-pad)|exceptionOut_of_bounds->error_msgf"Wrong padding"(* appear only when [pad = true] and when length of input is not a multiple of 4. *)|exceptionNot_found->(* appear when one character of [input] ∉ [alphabet] and this character <> '=' *)error_msgf"Malformed input"letdecode?pad?(alphabet=default_alphabet)?off?leninput=matchdecode_sub?padalphabet?off?leninputwith|Ok(res,off,len)->Ok(String.subres~pos:off~len)|Error_aserr->errletdecode_sub?pad?(alphabet=default_alphabet)?off?leninput=decode_sub?padalphabet?off?leninputletdecode_exn?pad?alphabet?off?leninput=matchdecode?pad?alphabet?off?leninputwith|Okres->res|Error(`Msgerr)->invalid_argerr