1
2(********************************************************************)
3(*                                                                  *)
4(*  encoding.s7i  Encoding and decoding functions                   *)
5(*  Copyright (C) 2007, 2008, 2011, 2013, 2015, 2016  Thomas Mertes *)
6(*  Copyright (C) 2019 - 2021  Thomas Mertes                        *)
7(*                                                                  *)
8(*  This file is part of the Seed7 Runtime Library.                 *)
9(*                                                                  *)
10(*  The Seed7 Runtime Library is free software; you can             *)
11(*  redistribute it and/or modify it under the terms of the GNU     *)
12(*  Lesser General Public License as published by the Free Software *)
13(*  Foundation; either version 2.1 of the License, or (at your      *)
14(*  option) any later version.                                      *)
15(*                                                                  *)
16(*  The Seed7 Runtime Library is distributed in the hope that it    *)
17(*  will be useful, but WITHOUT ANY WARRANTY; without even the      *)
18(*  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *)
19(*  PURPOSE.  See the GNU Lesser General Public License for more    *)
20(*  details.                                                        *)
21(*                                                                  *)
22(*  You should have received a copy of the GNU Lesser General       *)
23(*  Public License along with this program; if not, write to the    *)
24(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
25(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
26(*                                                                  *)
27(********************************************************************)
28
29
30include "chartype.s7i";
31include "bytedata.s7i";
32
33
34(**
35 *  Encode a string with the Base64 encoding.
36 *  Base64 encodes a byte string as ASCII string. This is done by
37 *  taking packs of 6-bits and translating them into a radix-64
38 *  representation. The radix-64 digits are encoded with letters
39 *  (upper case followed by lower case), digits and the characters
40 *  '+' and '/'.
41 *  @return the Base64 encoded string.
42 *  @exception RANGE_ERROR If characters beyond '\255;' are present.
43 *)
44const func string: toBase64 (in string: byteStri) is func
45  result
46    var string: base64 is "";
47  local
48    const string: coding is "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
49    var integer: index is 1;
50    var integer: subIndex is 1;
51    var char: ch is ' ';
52    var integer: threeBytes is 0;
53    var string: fourBytes is "    ";
54    var integer: posToAddNewline is 58;
55  begin
56    for index range 1 to length(byteStri) step 3 do
57      threeBytes := 0;
58      for subIndex range index to index + 2 do
59        threeBytes <<:= 8;
60        if subIndex <= length(byteStri) then
61          ch := byteStri[subIndex];
62          if ch >= '\256;' then
63            raise RANGE_ERROR;
64          end if;
65          threeBytes +:= ord(ch);
66        end if;
67      end for;
68      fourBytes @:= [1] coding[succ( threeBytes >> 18)];
69      fourBytes @:= [2] coding[succ((threeBytes >> 12) mod 64)];
70      fourBytes @:= [3] coding[succ((threeBytes >>  6) mod 64)];
71      fourBytes @:= [4] coding[succ( threeBytes        mod 64)];
72      if index = posToAddNewline then
73        base64 &:= "\n";
74        posToAddNewline +:= 57;
75      end if;
76      base64 &:= fourBytes;
77    end for;
78    index := length(base64);
79    if length(byteStri) rem 3 = 2 then
80      base64 @:= [index] '=';
81    elsif length(byteStri) rem 3 = 1 then
82      base64 @:= [pred(index)] "==";
83    end if;
84  end func;
85
86
87(**
88 *  Decode a Base64 encoded string.
89 *  @param base64 Base64 encoded string without leading or trailing
90 *         whitespace characters.
91 *  @return the decoded string.
92 *  @exception RANGE_ERROR If ''base64'' is not in Base64 format.
93 *)
94const func string: fromBase64 (in string: base64) is func
95  result
96    var string: decoded is "";
97  local
98    const array integer: decode is [] (                      # -1 is illegal
99        62, -1, -1, -1, 63,                                  # + /
100        52, 53, 54, 55, 56, 57, 58, 59, 60, 61,              # 0 - 9
101        -1, -1, -1,  0, -1, -1, -1,                          # =
102         0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12,  # A - M
103        13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,  # N - Z
104        -1, -1, -1, -1, -1, -1,
105        26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,  # a - m
106        39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51); # n - z
107    var integer: index is 1;
108    var integer: subIndex is 1;
109    var integer: number is 0;
110    var integer: fourBytes is 0;
111    var string: threeBytes is "   ";
112  begin
113    while index <= length(base64) - 3 do
114      if base64[index] >= '+' then
115        fourBytes := 0;
116        for subIndex range index to index + 3 do
117          number := decode[ord(base64[subIndex]) - ord(pred('+'))];
118          if number = -1 then
119            raise RANGE_ERROR;
120          end if;
121          fourBytes := (fourBytes << 6) + number;
122        end for;
123        threeBytes @:= [1] chr( fourBytes >> 16);
124        threeBytes @:= [2] chr((fourBytes >>  8) mod 256);
125        threeBytes @:= [3] chr( fourBytes        mod 256);
126        decoded &:= threeBytes;
127        index +:= 4;
128      elsif base64[index] = '\n' then
129        incr(index);
130      elsif base64[index] = '\r' and base64[succ(index)] = '\n' then
131        index +:= 2;
132      else
133        raise RANGE_ERROR;
134      end if;
135    end while;
136    if index <> succ(length(base64)) then
137      raise RANGE_ERROR;
138    end if;
139    if base64[length(base64) - 1 len 2] = "==" then
140      decoded := decoded[.. length(decoded) - 2];
141    elsif length(base64) >= 1 and base64[length(base64)] = '=' then
142      decoded := decoded[.. pred(length(decoded))];
143    end if;
144  end func;
145
146
147(**
148 *  Encode a string with the Quoted-printable encoding.
149 *  Quoted-printable encodes a byte string as ASCII string. This
150 *  is done by encoding printable ASCII characters except '=' as
151 *  themself. Other byte values are encoded with '=' followed by two
152 *  hexadecimal digits representing the byte's numeric value.
153 *  @return the encoded string.
154 *  @exception RANGE_ERROR If characters beyond '\255;' are present.
155 *)
156const func string: toQuotedPrintable (in string: byteStri) is func
157  result
158    var string: quoted is "";
159  local
160    var integer: index is 0;
161    var integer: startPos is 1;
162    var integer: counter is 1;
163    var char: ch is ' ';
164  begin
165    for index range 1 to length(byteStri) do
166      ch := byteStri[index];
167      if ch >= '\256;' then
168        raise RANGE_ERROR;
169      elsif ch = '\n' or (ch = '\r' and
170          index < length(byteStri) and byteStri[succ(index)] = '\n') then
171        if index > 1 then
172          ch := byteStri[pred(index)];
173          if ch = ' ' or ch = '\t' then
174            quoted &:= byteStri[startPos .. index - 2];
175            if counter >= 76 then
176              quoted &:= "=\n";
177              counter := 1;
178            end if;
179            quoted &:= "=" <& ord(byteStri[pred(index)]) RADIX 16 lpad0 2;
180            counter +:= 3;
181            startPos := index;
182          end if;
183        end if;
184        counter := 1;
185      elsif ch >= '\127;' or ch = '=' or (ch < ' ' and ch <> '\9;') then
186        quoted &:= byteStri[startPos .. pred(index)];
187        if counter >= 74 then
188          quoted &:= "=\n";
189          counter := 1;
190        end if;
191        quoted &:= "=" <& ord(ch) RADIX 16 lpad0 2;
192        startPos := succ(index);
193        counter +:= 3;
194      elsif counter >= 76 then
195        quoted &:= byteStri[startPos .. pred(index)] & "=\n";
196        startPos := index;
197        counter := 2;
198      else
199        incr(counter);
200      end if;
201    end for;
202    quoted &:= byteStri[startPos ..];
203  end func;
204
205
206(**
207 *  Decode a quoted-printable encoded string.
208 *  @return the decoded string.
209 *  @exception RANGE_ERROR If ''quoted'' is not in quoted-printable format.
210 *)
211const func string: fromQuotedPrintable (in string: quoted) is func
212  result
213    var string: decoded is "";
214  local
215    var integer: startPos is 1;
216    var integer: equalSignPos is 0;
217    var string: twoChars is "";
218  begin
219    equalSignPos := pos(quoted, "=");
220    while equalSignPos <> 0 do
221      decoded &:= quoted[startPos .. pred(equalSignPos)];
222      if equalSignPos < length(quoted) and
223          quoted[succ(equalSignPos)] = '\n' then
224        startPos := equalSignPos + 2;
225      elsif equalSignPos <= length(quoted) - 2 then
226        twoChars := quoted[succ(equalSignPos) len 2];
227        if twoChars[1] in hexdigit_char then
228          decoded &:= chr(integer(twoChars, 16));
229        elsif twoChars <> "\r\n" then
230          raise RANGE_ERROR;
231        end if;
232        startPos := equalSignPos + 3;
233      else
234        raise RANGE_ERROR;
235      end if;
236      equalSignPos := pos(quoted, "=", startPos);
237    end while;
238    decoded &:= quoted[startPos ..];
239  end func;
240
241
242(**
243 *  Encode a string with uuencoding.
244 *  Uuencode encodes a byte string as ASCII string. This is done
245 *  by taking packs of 6-bits and translating them into a radix-64
246 *  representation. The radix-64 digits are encoded with consecutive
247 *  ASCII characters starting from ' ' (which represents 0). Every
248 *  line starts with a radix-64 digit character indicating the number
249 *  of data bytes encoded on that line. Some newer uuencode tools use
250 *  grave accent ('`') instead of space (' ') to encode 0. This can
251 *  be emulated by using: replace(toUuencoded(source), " ", "`").
252 *  @return the encoded string.
253 *  @exception RANGE_ERROR If characters beyond '\255;' are present.
254 *)
255const func string: toUuencoded (in string: byteStri) is func
256  result
257    var string: uuencoded is "";
258  local
259    var integer: index is 1;
260    var integer: subIndex is 1;
261    var char: ch is ' ';
262    var integer: threeBytes is 0;
263    var string: fourBytes is "    ";
264    var integer: posToAddNewline is 43;
265  begin
266    if length(byteStri) <> 0 then
267      if length(byteStri) < 45 then
268        uuencoded &:= chr(32 + length(byteStri));
269      else
270        uuencoded &:= "M";
271      end if;
272      for index range 1 to length(byteStri) step 3 do
273        threeBytes := 0;
274        for subIndex range index to index + 2 do
275          threeBytes <<:= 8;
276          if subIndex <= length(byteStri) then
277            ch := byteStri[subIndex];
278            if ch >= '\256;' then
279              raise RANGE_ERROR;
280            end if;
281            threeBytes +:= ord(ch);
282          end if;
283        end for;
284        fourBytes @:= [1] chr(32 + (threeBytes >> 18));
285        fourBytes @:= [2] chr(32 + (threeBytes >> 12) mod 64);
286        fourBytes @:= [3] chr(32 + (threeBytes >>  6) mod 64);
287        fourBytes @:= [4] chr(32 +  threeBytes        mod 64);
288        uuencoded &:= fourBytes;
289        if index = posToAddNewline and length(byteStri) > index + 2 then
290          if length(byteStri) - index - 2 < 45 then
291            uuencoded &:= "\n" <& chr(32 + length(byteStri) - index - 2);
292          else
293            uuencoded &:= "\nM";
294          end if;
295          posToAddNewline +:= 45;
296        end if;
297      end for;
298      uuencoded &:= "\n";
299    end if;
300    uuencoded &:= "`\n";
301  end func;
302
303
304(**
305 *  Decode an uuencoded string.
306 *  Space (' ') and grave accent ('`') both are used to encode 0.
307 *  @return the decoded string.
308 *  @exception RANGE_ERROR If ''uuencoded'' is not in uuencoded format.
309 *)
310const func string: fromUuencoded (in string: uuencoded) is func
311  result
312    var string: decoded is "";
313  local
314    var integer: lineLength is 1;
315    var integer: index is 1;
316    var integer: subIndex is 1;
317    var integer: number is 0;
318    var integer: fourBytes is 0;
319    var string: threeBytes is "   ";
320  begin
321    lineLength := ord(uuencoded[1]) - 32;
322    while lineLength <> 0 and lineLength <> 64 do
323      incr(index);
324      while lineLength >= 1 do
325        fourBytes := 0;
326        for subIndex range index to index + 3 do
327          number := ord(uuencoded[subIndex]) - 32;
328          if number = 64 then
329            number := 0;
330          elsif number < 0 or number > 64 then
331            raise RANGE_ERROR;
332          end if;
333          fourBytes := (fourBytes << 6) + number;
334        end for;
335        threeBytes @:= [1] chr( fourBytes >> 16);
336        threeBytes @:= [2] chr((fourBytes >>  8) mod 256);
337        threeBytes @:= [3] chr( fourBytes        mod 256);
338        decoded &:= threeBytes[ .. lineLength];
339        lineLength -:= 3;
340        index +:= 4;
341      end while;
342      while index <= length(uuencoded) and uuencoded[index] <> '\n' do
343        incr(index);
344      end while;
345      if index < length(uuencoded) then
346        incr(index);
347        lineLength := ord(uuencoded[index]) - 32;
348      else
349        lineLength := 0;
350      end if;
351    end while;
352  end func;
353
354
355(**
356 *  Encode a string with percent-encoding.
357 *  Percent-encoding encodes a byte string as ASCII string. This is done
358 *  by encoding all characters, which are not in the set of unreserved
359 *  characters (A-Z, a-z, 0-9 - _ . ~). The encoding uses a percent sign
360 *  ('%') followed by two hexadecimal digits, which represent the ordinal
361 *  value of the encoded character.
362 *  @return the encoded string.
363 *  @exception RANGE_ERROR If characters beyond '\255;' are present.
364 *)
365const func string: toPercentEncoded (in string: byteStri) is func
366  result
367    var string: percentEncoded is "";
368  local
369    const set of char: unreservedChars is alphanum_char | {'-', '_', '.', '~'};
370    var integer: pos is 0;
371    var integer: start is 1;
372    var char: ch is ' ';
373  begin
374    for ch key pos range byteStri do
375      if ch > '\255;' then
376        raise RANGE_ERROR;
377      elsif ch not in unreservedChars then
378        percentEncoded &:= byteStri[start .. pred(pos)];
379        percentEncoded &:= "%" <& ord(ch) RADIX 16 lpad0 2;
380        start := succ(pos);
381      end if;
382    end for;
383    percentEncoded &:= byteStri[start ..];
384  end func;
385
386
387(**
388 *  Decode a percent-encoded string.
389 *  Percent-encoding encodes a byte string as ASCII string. It uses
390 *  the percent sign ('%') followed by two hexadecimal digits to
391 *  encode characters that otherwise would not be allowed in an
392 *  URL. Allowed URL characters are encoded as themself.
393 *  @return the decoded string.
394 *)
395const func string: fromPercentEncoded (in string: percentEncoded) is func
396  result
397    var string: decoded is "";
398  local
399    var integer: pos is 0;
400  begin
401    decoded := percentEncoded;
402    pos := pos(decoded, '%', succ(pos));
403    while pos <> 0 do
404      if pos <= length(decoded) - 2 and
405          decoded[succ(pos)] in hexdigit_char and
406          decoded[pos + 2] in hexdigit_char then
407        decoded := decoded[.. pred(pos)] &
408            str(chr(integer(decoded[succ(pos) len 2], 16))) &
409            decoded[pos + 3 ..];
410      end if;
411      pos := pos(decoded, '%', succ(pos));
412    end while;
413  end func;
414
415
416(**
417 *  Encode a string with URL encoding.
418 *  URL encoding encodes a byte string as ASCII string. This is done
419 *  by encoding all characters, which are not in the set of unreserved
420 *  characters (A-Z, a-z, 0-9 - _ . ~). The encoding uses a percent sign
421 *  ('%') followed by two hexadecimal digits, which represent the ordinal
422 *  value of the encoded character. A plus sign ('+') is used to encode
423 *  a space (' ').
424 *  @return the encoded string.
425 *  @exception RANGE_ERROR If characters beyond '\255;' are present.
426 *)
427const func string: toUrlEncoded (in string: byteStri) is func
428  result
429    var string: urlEncoded is "";
430  local
431    const set of char: unreservedChars is alphanum_char | {'-', '_', '.', '~'};
432    var integer: pos is 0;
433    var integer: start is 1;
434    var char: ch is ' ';
435  begin
436    for ch key pos range byteStri do
437      if ch > '\255;' then
438        raise RANGE_ERROR;
439      elsif ch = ' ' then
440        urlEncoded &:= byteStri[start .. pred(pos)];
441        urlEncoded &:= '+';
442        start := succ(pos);
443      elsif ch not in unreservedChars then
444        urlEncoded &:= byteStri[start .. pred(pos)];
445        urlEncoded &:= "%" <& ord(ch) RADIX 16 lpad0 2;
446        start := succ(pos);
447      end if;
448    end for;
449    urlEncoded &:= byteStri[start ..];
450  end func;
451
452
453(**
454 *  Decode an URL encoded string.
455 *  URL encoding encodes a byte string as ASCII string. It uses
456 *  the percent sign ('%') followed by two hexadecimal digits to
457 *  encode characters that otherwise would not be allowed in an
458 *  URL. A plus sign ('+') is used to encode a space (' ').
459 *  Allowed URL characters are encoded as themself.
460 *  @return the decoded string.
461 *)
462const func string: fromUrlEncoded (in string: urlEncoded) is func
463  result
464    var string: decoded is "";
465  local
466    var integer: pos is 0;
467    var integer: start is 1;
468    var char: ch is ' ';
469  begin
470    for ch key pos range urlEncoded do
471      if ch = '%' and pos <= length(urlEncoded) - 2 and
472          urlEncoded[succ(pos)] in hexdigit_char and
473          urlEncoded[pos + 2] in hexdigit_char then
474        decoded &:= urlEncoded[start .. pred(pos)];
475        decoded &:= chr(integer(urlEncoded[succ(pos) len 2], 16));
476        pos +:= 2;
477        start := succ(pos);
478      elsif ch = '+' then
479        decoded &:= urlEncoded[start .. pred(pos)];
480        decoded &:= ' ';
481        start := succ(pos);
482      end if;
483    end for;
484    decoded &:= urlEncoded[start ..];
485  end func;
486
487
488(**
489 *  Encode a string with the Ascii85 encoding.
490 *  Ascii85 encodes a byte string as ASCII string. This is done by
491 *  encoded every four bytes with five printable ASCII characters.
492 *  Five radix 85 digits provide enough possible values to encode
493 *  the possible values of four bytes. The radix 85 digits are encoded
494 *  with the characters '!' (encodes 0) through 'u' (encodes 84).
495 *  If the last block of the byte string contains fewer than 4 bytes,
496 *  the block is padded with up to three null bytes before encoding.
497 *  After encoding, as many bytes as were added as padding are removed
498 *  from the end of the output. In files the end of an Ascii85 encoding
499 *  is marked with "~>" (this end marker is not added by toAscii85).
500 *  @return the Ascii85 encoded string.
501 *  @exception RANGE_ERROR If characters beyond '\255;' are present.
502 *)
503const func string: toAscii85 (in string: byteStri) is func
504  result
505    var string: ascii85 is "";
506  local
507    var integer: index is 0;
508    var integer: subIndex is 0;
509    var integer: fourBytes is 0;
510    var string: fiveBytes is "     ";
511    var char: ch is ' ';
512  begin
513    for index range 1 to length(byteStri) step 4 do
514      fourBytes := 0;
515      for subIndex range index to index + 3 do
516        fourBytes <<:= 8;
517        if subIndex <= length(byteStri) then
518          ch := byteStri[subIndex];
519          if ch >= '\256;' then
520            raise RANGE_ERROR;
521          end if;
522          fourBytes +:= ord(ch);
523        end if;
524      end for;
525      if fourBytes = 0 then
526        ascii85 &:= 'z';
527      else
528        for subIndex range 5 downto 1 do
529          fiveBytes @:= [subIndex] chr(ord('!') + fourBytes rem 85);
530          fourBytes := fourBytes div 85;
531        end for;
532        ascii85 &:= fiveBytes;
533      end if;
534    end for;
535    if length(byteStri) rem 4 <> 0 then
536      ascii85 := ascii85[.. length(ascii85) - 4 + length(byteStri) rem 4];
537    end if;
538  end func;
539
540
541(**
542 *  Decode a Ascii85 encoded string.
543 *  Every block of five radix 85 characters is decoded to four bytes.
544 *  Radix 85 characters are between '!' (encodes 0) and 'u' (encodes 84).
545 *  The character 'z' is used to encode a block of four zero bytes.
546 *  White space in the Ascii85 encoded string is ignored.
547 *  The last block is padded to 5 bytes with the Ascii85 character "u",
548 *  and as many bytes as were added as padding are omitted from the
549 *  end of the output.
550 *  @return the decoded string.
551 *  @exception RANGE_ERROR If ''ascii85'' is not in Ascii85 format.
552 *)
553const func string: fromAscii85 (in string: ascii85) is func
554  result
555    var string: decoded is "";
556  local
557    const set of char: whiteSpace is {'\0;', '\t', '\n', '\f', '\r', ' '};
558    var char: ch is ' ';
559    var integer: digitIndex is 0;
560    var integer: base85Number is 0;
561    var integer: idx is 0;
562  begin
563    for ch range ascii85 until ch = '~' do
564      if ch >= '!' and ch <= 'u' then
565        incr(digitIndex);
566        base85Number := base85Number * 85 + (ord(ch) - ord('!'));
567        if digitIndex = 5 then
568          decoded &:= bytes(base85Number, UNSIGNED, BE, 4);
569          digitIndex := 0;
570          base85Number := 0;
571        end if;
572      elsif ch = 'z' and digitIndex = 0 then
573        decoded &:= "\0;\0;\0;\0;";
574      elsif ch not in whiteSpace then
575        raise RANGE_ERROR;
576      end if;
577    end for;
578    if digitIndex <> 0 then
579      for idx range 1 to 5 - digitIndex do
580        base85Number := base85Number * 85 + 84;
581      end for;
582      decoded &:= bytes(base85Number, UNSIGNED, BE, 4)[.. pred(digitIndex)];
583    end if;
584  end func;
585
586
587(**
588 *  Encode a number with a positional numeric system.
589 *  The encoded string starts with the most significant digit.
590 *  @param number BigInteger number to be encoded.
591 *  @param digits The digits used by the positional numeric system.
592 *  @return the encoded string.
593 *)
594const func string: toBase (in bigInteger: number, in string: digits) is func
595  result
596    var string: encoded is "";
597  local
598    var bigInteger: base is 0_;
599    var quotRem: quotientAndRemainder is quotRem.value;
600  begin
601    base := bigInteger(length(digits));
602    quotientAndRemainder.quotient := number;
603    while quotientAndRemainder.quotient <> 0_ do
604      quotientAndRemainder := quotientAndRemainder.quotient divRem base;
605      encoded &:= digits[succ(ord(quotientAndRemainder.remainder))];
606    end while;
607    encoded := reverse(encoded);
608  end func;
609
610
611(**
612 *  Decode a string that has been encoded with a positional numeric system.
613 *  The encoded string starts with the most significant digit.
614 *  @param encoded String containing the encoded data.
615 *  @param digits The digits used by the positional numeric system.
616 *  @return the decoded bigInteger Number.
617 *  @exception RANGE_ERROR If characters are present, that are not found
618 *                         in ''digits''.
619 *)
620const func bigInteger: fromBaseToBigInt (in string: encoded, in string: digits) is func
621  result
622    var bigInteger: number is 0_;
623  local
624    var bigInteger: base is 0_;
625    var bigInteger: factor is 1_;
626    var integer: index is 0;
627    var integer: digit is 0;
628  begin
629    base := bigInteger(length(digits));
630    for index range length(encoded) downto 1 do
631      digit := pred(pos(digits, encoded[index]));
632      if digit = -1 then
633        raise RANGE_ERROR;
634      else
635        number +:= factor * bigInteger(digit);
636        factor *:= base;
637      end if;
638    end for;
639  end func;
640
641
642(**
643 *  Encode a string of bytes with a positional numeric system.
644 *  The encoded string starts with the most significant digit.
645 *  Leading zero bytes in ''byteStri'' are converted into leading
646 *  zero digits in the encoded string.
647 *  @param byteStri String of bytes to be encoded.
648 *  @param digits The digits used by the positional numeric system.
649 *  @return the encoded string.
650 *  @exception RANGE_ERROR If characters beyond '\255;' are present.
651 *)
652const func string: toBase (in string: byteStri, in string: digits) is func
653  result
654    var string: encoded is "";
655  local
656    var integer: index is 1;
657  begin
658    encoded := toBase(bytes2BigInt(byteStri, UNSIGNED, BE), digits);
659    while index <= length(byteStri) and byteStri[index] = '\0;' do
660      incr(index);
661    end while;
662    if index >= 1 then
663      encoded := "1" mult pred(index) & encoded;
664    end if;
665  end func;
666
667
668(**
669 *  Decode a string that has been encoded with a positional numeric system.
670 *  The encoded string starts with the most significant digit.
671 *  Leading zero digits in the encoded string are converted into leading
672 *  zero bytes in the decoded string.
673 *  @param encoded String containing the encoded data.
674 *  @param digits The digits used by the positional numeric system.
675 *  @return the decoded string.
676 *  @exception RANGE_ERROR If characters are present, that are not found
677 *                         in ''digits''.
678 *)
679const func string: fromBase (in string: encoded, in string: digits) is func
680  result
681    var string: decoded is "";
682  local
683    var bigInteger: number is 0_;
684    var integer: index is 1;
685  begin
686    number := fromBaseToBigInt(encoded, digits);
687    if number <> 0_ then
688      decoded := bytes(number, UNSIGNED, BE);
689    end if;
690    while index <= length(encoded) and encoded[index] = '1' do
691      incr(index);
692    end while;
693    if index >= 1 then
694      decoded := "\0;" mult pred(index) & decoded;
695    end if;
696  end func;
697
698
699const string: defaultBase58Digits is "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
700
701
702(**
703 *  Encode a string with the Base58 encoding used by Bitcoin.
704 *  Leading zero bytes ('\0;') in ''byteStri'' are converted into
705 *  leading zero digits ('1') in the encoded string.
706 *  @param byteStri String of bytes to be encoded.
707 *  @return the encoded string.
708 *  @exception RANGE_ERROR If characters beyond '\255;' are present.
709 *)
710const func string: toBase58 (in string: byteStri) is
711  return toBase(byteStri, defaultBase58Digits);
712
713
714(**
715 *  Decode a Base58 encoded string.
716 *  Leading '1' characters in ''base58'' are converted into
717 *  leading zero bytes ('\0;') in the decoded string.
718 *  @param base58 String containing Base58 encoded data.
719 *  @return the decoded string.
720 *  @exception RANGE_ERROR If characters are present, that are not valid
721 *                         in the Base58 encoding used by Bitcoin.
722 *)
723const func string: fromBase58 (in string: base58) is
724  return fromBase(base58, defaultBase58Digits);
725