123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140(*---------------------------------------------------------------------------
Copyright (c) 2024 The webs programmers. All rights reserved.
SPDX-License-Identifier: ISC
---------------------------------------------------------------------------*)(* See https://www.rfc-editor.org/rfc/rfc4648 *)typeencoding=[`Base64|`Base64url]typepadding=[`Padded|`Unpadded]typeerror=|Invalid_lengthofint|Invalid_letterofchar*int|Non_canonical_encodingleterror_messageencerr=letenc=matchencwith`Base64url->"base64url"|`Base64->"base64"inmatcherrwith|Invalid_lengthlen->Printf.sprintf"%s: Invalid input length (%d)"enclen|Invalid_letter(c,i)->Printf.sprintf"%s: Invalid alphabet character %C at index %d"encci|Non_canonical_encoding->Printf.sprintf"%s: Non-canonical encoding"encletalpha="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"letalpha_url="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"letencencpads=matchString.lengthswith|0->""|len->letalpha=matchencwith`Base64url->alpha_url|`Base64->alphainletpad=matchpadwith`Padded->true|`Unpadded->falseinletelen=ifpadthen((len+2)/3)*4else(len*4+2)/3inlete=Bytes.createeleninleti=ref0andei=ref0intrywhiletruedoleti0=!iandi1=!i+1andi2=!i+2inletb0=String.get_uint8si0inletb1=ifi1>=lenthen0else(String.get_uint8si1)inletb2=ifi2>=lenthen0else(String.get_uint8si2)inletu=(b0lsl16)lor(b1lsl8)lorb2inBytes.sete(!ei)(alpha.[ulsr18]);Bytes.sete(!ei+1)(alpha.[(ulsr12)land63]);ifi1>=lenthen(ifpadthenBytes.sete(!ei+2)'='elseraise_notraceExit)else(Bytes.sete(!ei+2)alpha.[(ulsr6)land63]);ifi2>=lenthen(ifpadthenBytes.sete(!ei+3)'='elseraise_notraceExit)else(Bytes.sete(!ei+3)alpha.[uland63]);i:=i2+1;if!i>=lenthenraise_notraceExit;ei:=!ei+4;done;assertfalsewithExit->Bytes.unsafe_to_stringeletdecencpads=letexceptionErroroferrorinleterre=raise_notrace(Errore)inletdecoded_length~paddedlen=letdlen=(len/4)*3inifpaddedthenbeginiflenmod4<>0thenerr(Invalid_lengthlen)elseletdlen=ifs.[len-1]='='thendlen-1elsedleninletdlen=ifs.[len-2]='='thendlen-1elsedlenindlenendelsematchlenmod4with|0->dlen|1->err(Invalid_lengthlen)|2->dlen+1|3->dlen+2|_->assertfalseinletdecode_alphaurli=function|'A'..'Z'asc->Char.codec-0x41|'a'..'z'asc->Char.codec-0x61+26|'0'..'9'asc->Char.codec-0x30+52|'+'asc->ifnoturlthen62elseerr(Invalid_letter(c,i))|'/'asc->ifnoturlthen63elseerr(Invalid_letter(c,i))|'-'asc->ifurlthen62elseerr(Invalid_letter(c,i))|'_'asc->ifurlthen63elseerr(Invalid_letter(c,i))|c->err(Invalid_letter(c,i))inletlen=String.lengthsiniflen=0thenOk""elsetryleturl=matchencwith`Base64url->true|`Base64->falseinletpadded=matchpadwith`Padded->true|`Unpadded->falseinletd=Bytes.create(decoded_length~paddedlen)intryleti=ref0anddi=ref0inwhiletruedoleti0=!iandi1=!i+1andi2=!i+2andi3=!i+3inleta0=String.getsi0inleta1=String.getsi1inleta2=ifi2>=lenthen(* unpadded *)'='elseString.getsi2inleta3=ifi3>=lenthen(* unpadded *)'='elseString.getsi3inletd0=decode_alphaurli0a0inletd1=decode_alphaurli1a1inBytes.set_uint8d!di((d0lsl2)lor(d1lsr4));ifa3='='thenbeginifnot((i3=len-1&&padded)||i3>=len)thenerr(Invalid_letter('=',i3))elseifa2='='thenifnot((i2=len-2&&padded)||i2>=len)thenerr(Invalid_letter('=',i2))elseif(d1land0b1111)<>0thenerrNon_canonical_encodingelseraise_notraceExitelseletd2=decode_alphaurli2a2inif(d2land0b11)<>0thenerrNon_canonical_encodingelseBytes.set_uint8d(!di+1)(((d1land0xF)lsl4)lor(d2lsr2));raise_notraceExitend;letd2=decode_alphaurli2a2inletd3=decode_alphaurli3a3inBytes.set_uint8d(!di+1)(((d1land0xF)lsl4)lor(d2lsr2));Bytes.set_uint8d(!di+2)(((d2land0x3)lsl6)lord3);i:=!i+4;if!i>=lenthenraise_notraceExitelsedi:=!di+3;done;assertfalsewith|Exit->Ok(Bytes.unsafe_to_stringd)with|Errore->Erroreletencodeps=enc`Base64psletdecode'ps=dec`Base64psletdecodeps=Result.map_error(error_message`Base64)(decode'ps)letencode_base64urlps=enc`Base64urlpsletdecode_base64url'ps=dec`Base64urlpsletdecode_base64urlps=Result.map_error(error_message`Base64url)(decode_base64url'ps)