2018-08-22 20:24:32 +01:00
/ * *
* @ author PenguinGeorge [ george @ penguingeorge . com ]
* @ copyright Crown Copyright 2018
* @ license Apache - 2.0
* /
2019-07-09 12:23:59 +01:00
import Operation from "../Operation.mjs" ;
import OperationError from "../errors/OperationError.mjs" ;
import Utils from "../Utils.mjs" ;
2022-11-25 16:11:14 +00:00
import { ALPHABET _OPTIONS } from "../lib/Base85.mjs" ;
2018-08-22 20:24:32 +01:00
/ * *
* From Base85 operation
* /
class FromBase85 extends Operation {
/ * *
* From Base85 constructor
* /
constructor ( ) {
super ( ) ;
this . name = "From Base85" ;
this . module = "Default" ;
2018-08-23 22:05:31 +01:00
this . description = "Base85 (also called Ascii85) is a notation for encoding arbitrary byte data. It is usually more efficient that Base64.<br><br>This operation decodes data from an ASCII string (with an alphabet of your choosing, presets included).<br><br>e.g. <code>BOu!rD]j7BEbo7</code> becomes <code>hello world</code><br><br>Base85 is commonly used in Adobe's PostScript and PDF file formats." ;
2018-08-22 20:24:32 +01:00
this . infoURL = "https://wikipedia.org/wiki/Ascii85" ;
this . inputType = "string" ;
this . outputType = "byteArray" ;
this . args = [
{
name : "Alphabet" ,
type : "editableOption" ,
value : ALPHABET _OPTIONS
} ,
2022-06-14 10:23:13 +01:00
{
name : "Remove non-alphabet chars" ,
type : "boolean" ,
value : true
} ,
2022-11-25 21:36:17 +08:00
{
name : "All-zero group char" ,
type : "binaryShortString" ,
2022-11-25 16:11:14 +00:00
value : "z" ,
maxLength : 1
}
2018-08-22 20:24:32 +01:00
] ;
2020-05-16 00:42:50 +02:00
this . checks = [
{
2020-05-22 03:30:57 +02:00
pattern :
"^\\s*(?:<~)?" + // Optional whitespace and starting marker
"[\\s!-uz]*" + // Any amount of base85 characters and whitespace
2020-06-10 15:50:26 +02:00
"[!-uz]{15}" + // At least 15 continoues base85 characters without whitespace
2020-05-22 03:30:57 +02:00
"[\\s!-uz]*" + // Any amount of base85 characters and whitespace
"(?:~>)?\\s*$" , // Optional ending marker and whitespace
args : [ "!-u" ] ,
2020-05-16 00:42:50 +02:00
} ,
{
2020-05-22 03:30:57 +02:00
pattern :
"^" +
"[\\s0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]*" +
2020-06-10 15:50:26 +02:00
"[0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]{15}" + // At least 15 continoues base85 characters without whitespace
2020-05-22 03:30:57 +02:00
"[\\s0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]*" +
"$" ,
args : [ "0-9a-zA-Z.\\-:+=^!/*?&<>()[]{}@%$#" ] ,
2020-05-16 00:42:50 +02:00
} ,
{
2020-05-22 03:30:57 +02:00
pattern :
"^" +
"[\\s0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]*" +
2020-06-10 15:50:26 +02:00
"[0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]{15}" + // At least 15 continoues base85 characters without whitespace
2020-05-22 03:30:57 +02:00
"[\\s0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]*" +
"$" ,
args : [ "0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~" ] ,
2020-05-16 00:42:50 +02:00
} ,
] ;
2018-08-22 20:24:32 +01:00
}
/ * *
* @ param { string } input
* @ param { Object [ ] } args
* @ returns { byteArray }
* /
run ( input , args ) {
2018-08-23 22:05:31 +01:00
const alphabet = Utils . expandAlphRange ( args [ 0 ] ) . join ( "" ) ,
2022-06-14 10:23:13 +01:00
removeNonAlphChars = args [ 1 ] ,
2022-11-25 16:11:14 +00:00
allZeroGroupChar = typeof args [ 2 ] === "string" ? args [ 2 ] . slice ( 0 , 1 ) : "" ,
2018-08-22 20:24:32 +01:00
result = [ ] ;
if ( alphabet . length !== 85 ||
[ ] . unique . call ( alphabet ) . length !== 85 ) {
throw new OperationError ( "Alphabet must be of length 85" ) ;
}
2022-11-25 16:11:14 +00:00
if ( allZeroGroupChar && alphabet . includes ( allZeroGroupChar ) ) {
throw new OperationError ( "The all-zero group char cannot appear in the alphabet" ) ;
}
2022-07-08 15:15:53 +01:00
// Remove delimiters if present
const matches = input . match ( /^<~(.+?)~>$/ ) ;
if ( matches !== null ) input = matches [ 1 ] ;
2022-06-14 10:23:13 +01:00
// Remove non-alphabet characters
if ( removeNonAlphChars ) {
2022-11-25 21:36:17 +08:00
const re = new RegExp ( "[^~" + allZeroGroupChar + alphabet . replace ( /[[\]\\\-^$]/g , "\\$&" ) + "]" , "g" ) ;
2022-06-14 10:23:13 +01:00
input = input . replace ( re , "" ) ;
2022-11-25 10:36:08 +08:00
// Remove delimiters again if present (incase of non-alphabet characters in front/behind delimiters)
const matches = input . match ( /^<~(.+?)~>$/ ) ;
if ( matches !== null ) input = matches [ 1 ] ;
2022-06-14 10:23:13 +01:00
}
2018-08-22 20:24:32 +01:00
if ( input . length === 0 ) return [ ] ;
let i = 0 ;
let block , blockBytes ;
while ( i < input . length ) {
2022-11-25 16:11:14 +00:00
if ( input [ i ] === allZeroGroupChar ) {
2018-08-22 20:24:32 +01:00
result . push ( 0 , 0 , 0 , 0 ) ;
i ++ ;
} else {
let digits = [ ] ;
digits = input
. substr ( i , 5 )
. split ( "" )
. map ( ( chr , idx ) => {
const digit = alphabet . indexOf ( chr ) ;
2022-11-25 21:36:17 +08:00
if ( ( digit < 0 || digit > 84 ) && chr !== allZeroGroupChar ) {
2022-11-25 11:08:01 +08:00
throw ` Invalid character ' ${ chr } ' at index ${ i + idx } ` ;
2018-08-22 20:24:32 +01:00
}
return digit ;
} ) ;
block =
digits [ 0 ] * 52200625 +
digits [ 1 ] * 614125 +
( i + 2 < input . length ? digits [ 2 ] : 84 ) * 7225 +
( i + 3 < input . length ? digits [ 3 ] : 84 ) * 85 +
( i + 4 < input . length ? digits [ 4 ] : 84 ) ;
blockBytes = [
( block >> 24 ) & 0xff ,
( block >> 16 ) & 0xff ,
( block >> 8 ) & 0xff ,
block & 0xff
] ;
if ( input . length < i + 5 ) {
blockBytes . splice ( input . length - ( i + 5 ) , 5 ) ;
}
result . push . apply ( result , blockBytes ) ;
i += 5 ;
}
}
return result ;
}
}
export default FromBase85 ;