diff --git a/.codespellignore b/.codespellignore new file mode 100644 index 0000000..1cd3c8c --- /dev/null +++ b/.codespellignore @@ -0,0 +1 @@ +rouge diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 0000000..d110d10 --- /dev/null +++ b/.codespellrc @@ -0,0 +1,9 @@ +[codespell] +skip = + *.po, + *.ts, + tests/* +count = +quiet-level = 3 +ignore-words-list = + placeholder diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c740ef3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# EditorConfig: https://EditorConfig.org. Provides sensible defaults for +# non vscode editors. + +# top-most EditorConfig file +root = true + +# Every file. +[*] +charset = "utf8" +end_of_line = lf +insert_final_newline = true + +indent_style = space +indent_size = 4 + +trim_trailing_whitespace = true + +# Python. (Duplicates used as placeholders) +[*.py] +indent_style = space +indent_size = 4 diff --git a/.gitignore b/.gitignore index dfb53b5..72845b1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ *.crt *.key *.csr +sigdev +signature-development-utility diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 0000000..ee26c42 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,8 @@ +--- +default: true + +# Two headings in the file. +MD025: False + +# Line-length. +MD013: False diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 0000000..e69de29 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..36835f7 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,25 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-yaml + - id: check-json + - id: check-toml + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-case-conflict +- repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.35.0 + hooks: + - id: markdownlint +- repo: https://github.com/codespell-project/codespell + rev: v2.3.0 + hooks: + - id: codespell + args: [-I, .codespellignore] + exclude: > + (?x)^( + .*\.js| + .*\.css| + .*\.php| + )$ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..537262c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,41 @@ +{ + "editor.insertSpaces": true, + "editor.tabSize": 4, + "editor.rulers": [ + 79 + ], + "editor.detectIndentation": false, + "files.trimTrailingWhitespace": true, + "files.insertFinalNewline": true, + "git.inputValidationSubjectLength": 50, + "git.inputValidationLength": 72, + "[git-commit]": { + "editor.rulers": [ + 50, + 72 + ] + }, + "[python]": { + "editor.rulers": [ + 72, + 79, + 120 + ], + "editor.formatOnSave": true, + "editor.defaultFormatter": "ms-python.black-formatter" + }, + "[go]": { + "editor.rulers": [ + 72, + 79 + ] + }, + "[markdown]": { + "editor.rulers": [80] + }, + "[makefile]": { + "editor.insertSpaces": false, + "editor.tabSize": 4 + }, + "files.eol": "\n" +} diff --git a/README.md b/README.md index ceb76f1..98c628e 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,32 @@ signature workflow. It has also been written so that new features can be developed easier as I tend not to write in PHP anymore. There is more information at [ffdev.info][ffdev-1]. -## Legacy version... +### Installation + +The current version bootstrapped to the PHP back-end of Signature development +utility 1.0 for standard signatures. You can run this code by building the go +component: + +* `go build` + +And then running it: + +* `./signature-development-utility -port [optional]` + +Without a port defined you'll be able to access the utility on port `8080`. + +To run the PHP server, you first need to install `php-dom` with +`sudo apt-get install php-dom`. + +And then to run up the server: `php -S localhost:8000` + +#### Custom ports + +You can also run this using custom ports e.g. + +* `./signature-development-utility -port 80 -bootstrap 8000` + +## Legacy version The first iteration of this application is hosted by [The National Archives][tna-1] and mirrored on [my own site][expo-1]. It is @@ -32,7 +57,7 @@ hope to be working on. [droid-1]: http://www.nationalarchives.gov.uk/information-management/manage-information/preserving-digital-records/droid/ [tna-1]: http://www.nationalarchives.gov.uk/pronom/sigdev/index.htm [expo-1]: http://exponentialdecay.co.uk/sd/index.htm -[issues-1]: https://github.com/exponential-decay/signature-development-utility/issues +[issues-1]: https://github.com/ffdev-info/signature-development-utility/issues [coptr-1]: http://coptr.digipres.org/PRONOM_Signature_Development_Utility [ffdev-1]: http://ffdev.info -[gh-1]: https://github.com/exponential-decay/signature-development-utility/releases/tag/1.0 +[gh-1]: https://github.com/ffdev-info/signature-development-utility/releases/tag/1.0 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c1a3dc4 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/ffdev-info/signature-development-utility + +go 1.23.5 diff --git a/justfile b/justfile new file mode 100644 index 0000000..93ebfec --- /dev/null +++ b/justfile @@ -0,0 +1,25 @@ +# CLI helpers + +# Help +help: + @just -l + +# Run all pre-commit checks +all-checks: + pre-commit run --all-files + +# Setup pre-commit +setup: + pre-commit install --install-hooks # uninstall: `pre-commit uninstall` + +# Remove pre-commit +teardown: + pre-commit uninstall + +# Run go server +goserver: + ./signature-development-utility -port 8001 + +# Run PHP server +phpserver: + php -S localhost:8000 diff --git a/php/generatebytecode/generatebytecode.php b/php/generatebytecode/generatebytecode.php new file mode 100644 index 0000000..40fcb40 --- /dev/null +++ b/php/generatebytecode/generatebytecode.php @@ -0,0 +1,897 @@ +saveXML(); + } + + /********************************************************************* + * + * Function : + * + * Description : description... + * + * parameters : + * + * Returns : n/a + * + *********************************************************************/ + public function generateSignatureFromObject($signature_collection) + { + $xml = new XMLHelper(); + unset($doc); + + $doc = $xml->newDOMDocument(); + $is = $xml->xmlCreateElement('InternalSignature', $doc, $doc); + + $xml->xmlAddAttribute('ID', $signature_collection->sig_id, $doc, $is); + $xml->xmlAddAttribute('Specificity', $signature_collection->specificity, $doc, $is); + + for($x = 0; $x < sizeof($signature_collection->byteSequence); $x++) + { + unset($this->suboffs); + $this->bofMax = 0; + $this->eofMax = 0; + + $byte = $signature_collection->byteSequence[$x]; + + //most basic thing we can do to ease validation... + //always ensure we have an upper case string to work with... + $string = strtoupper($byte->byte_sequence); + + $anchor = $byte->position; + $offset = $byte->offset; + $max = $byte->maxoffset; + + $anchor === 'Variable' ? $this->varoff = true : $this->varoff = false; + $anchor === 'EOFoffset' ? $this->eof = true : $this->eof = false; + + $subsequences = $this->splitIntoSubsequences($string); + + //compute subsequence stuff beforehand... + for ($i = 0; $i < sizeof($subsequences); $i++) + { + ($i == (sizeof($subsequences)-1)) ? $bLast = true : $bLast = false; + ($i == 0) ? $bFirst = true : $bFirst = false; + + //Once we've got our subsequences below we can output the xml nodes for + //the values we've gathered... + $subsequences[$i] = $this->stripCurlyWildcards($subsequences[$i], $bFirst, $bLast); + } + + $len_offset_array = sizeof($this->suboffs); + for($head=1; $head < $len_offset_array-1; $head+=2) + { + //get the value from the head of the next subsequence + //and store in a temporary variable to add to the tail of the current + if($head != ($len_offset_array-2)) + { + $tmp = $this->suboffs[$head+1]; + unset($this->suboffs[$head+1]); //unset head from array to contract values + } + + //new tail... add head value... + if($head != 0 && $head != ($len_offset_array-1)) + { + $this->suboffs[$head] = $this->suboffs[$head] += $tmp; + } + } + + $this->suboffs = array_values($this->suboffs); + + $this->suboffs[0] = $this->suboffs[0] += $offset; + $this->suboffs[sizeof($this->suboffs)-1] = $this->suboffs[sizeof($this->suboffs)-1] += $offset; + + $this->bofMax += $max; + $this->eofMax += $max; + + if($this->eof) + { + unset($this->suboffs[0]); + $this->suboffs = array_values($this->suboffs); + $this->eofMax += end($this->suboffs); + } + else + { + $this->bofMax += $this->suboffs[0]; + } + + //need to do some work to bring some of this code together... member variables? + $doc = $this->bringXMLTogether($doc, $is, $subsequences, $anchor, $offset, $max, $xml); + + } + + return $doc->saveXML(); + } + + /********************************************************************* + * + * Function : + * + * Description : + * + * parameters : + * + * Returns : + * + *********************************************************************/ + private function bringXMLTogether($doc, $is, $subsequences, $anchor, $offset, $max, $xml) + { + $byteSeq = $xml->xmlCreateElement('ByteSequence', $doc, $is); + + if($this->varoff === false) + { + $xml->xmlAddAttribute('Reference', $anchor, $doc, $byteSeq); + } + + for($i = 0; $i < sizeof($subsequences); $i++) + { + //Within subsequence tags is the breakdown of the rest of the signatue... + //longest sequence, left right fragments etc. output using the functions below... + $subSeq = $xml->xmlCreateElement('SubSequence', $doc, $byteSeq); + + $fragment_longest_pair = $this->longestUnambiguousSequence($subsequences[$i]); + $this->handleStringFragments($fragment_longest_pair, $doc, $subSeq, $xml); + + //use outputFragPos() via handleStringFragments to increment + //MinFragLength and output here... + $xml->xmlAddAttribute('MinFragLength', $this->minFragLen, $doc, $subSeq); + $this->Len = 0; + //reset minfraglength to zero for next fragment... + $this->minFragLen = 0; + + $xml->xmlAddAttribute('Position', $i+1, $doc, $subSeq); + + if($this->varoff === false) + { + if ($this->eof === false && $i === 0) //i.e. if $i == 0 (1) + { + $xml->xmlAddAttribute('SubSeqMaxOffset', $this->bofMax, $doc, $subSeq); + } + elseif ($this->eof === true && $i === sizeof($subsequences)-1) + { + $xml->xmlAddAttribute('SubSeqMaxOffset', $this->eofMax, $doc, $subSeq); + } + } + + $xml->xmlAddAttribute('SubSeqMinOffset', $this->suboffs[$i], $doc, $subSeq); + + } + + return $doc; + } + + /********************************************************************* + * + * Function : + * + * Description : + * + * parameters : + * + * Returns : + * + *********************************************************************/ + private function splitIntoSubsequences($string) + { + $subsequences = array(); + $asterisk_count = substr_count($string, '*'); + + if ($asterisk_count) + { + for($i = 0; $i < $asterisk_count; $i++) + { + $pos = strpos($string,'*'); + + + $bracket_test = substr($string, $pos, 2); + + //if asterisk appears within a bracket split but handle differently + if(strpos($bracket_test, '}')) + { + $subsequences[] = substr($string, 0, $pos+2); + $string = substr($string, $pos+2); + } + else //we can simply split the string as in normal circumstances + { + $subsequences[] = substr($string, 0, $pos); + $string = substr($string, $pos+1); + } + } + } + + //should have one fragment left from loop, add to array... + //or put frag in here if no wildcards... + $subsequences[] = $string; + + return $subsequences; + } + + + /********************************************************************* + * + * Function : + * + * Description : + * + * parameters : + * + * Returns : + * + *********************************************************************/ + private function stripCurlyWildcards($string, $bFirst, $bLast) + { + //will only ever have two values here bos / eos + $start = 0; // start of sequence wildcard + $end = 0; // end of sequence wildcard + + //array indexes. assign var names for ease of reading + if(!defined("MIN_")) { define("MIN_", 0); } + if(!defined("MAX_")) { define("MAX_", 1); } + + $s_pos = strpos($string, '{'); + if(!is_bool($s_pos)) + { + if($s_pos == 0) + { + $len = strpos($string, '}') + 1; + $start = substr($string, 0, $len); + $string = substr($string, $len); + } + } + + //reverse string, look for first occurrence + //of closing curly bracket '}'... + $string = strrev($string); + + $s_pos = strpos($string, '}'); + if(is_bool($s_pos) != true) + { + if($s_pos == 0) + { + $len = strpos($string, '{') + 1; + $end = strrev(substr($string, 0, $len)); + $string = substr($string, $len); + } + } + + //reverse string finally to correct its orientation + $string = strrev($string); + + //Strip wildcard vals of hypens and record value pair + if ($start) + { + $optarr1 = $this->getBracketedValues('-', $start); + + if(sizeof($optarr1) == 2) + { + if($optarr1[MAX_] == '*') + { + if($bFirst) //for BOF sequences, if we have * it is variable... + { + $this->varoff = true; + } + $optarr1[MAX_] = 0; //means little, only affects BOF or EOF if we have a MAXOFFSET attribute + } + } + + //add the difference between the two values to max offset, not the entire value again... + if($bFirst && sizeof($optarr1) == 2) { $this->bofMax += ($optarr1[MAX_]-$optarr1[MIN_]); } + if ($optarr1[MIN_]) { $this->suboffs[] = $optarr1[MIN_]; } + } + else + { + $this->suboffs[] = 0; + } + + if ($end) + { + $optarr2 = $this->getBracketedValues('-', $end); + + if(sizeof($optarr2) == 2) + { + if($optarr2[MAX_] == '*') + { + if($bLast) //for EOF sequences, if we have * it is variable... + { + $this->varoff = true; + } + $optarr2[MAX_] = 0; //means little, only affects BOF or EOF if we have a MAXOFFSET attribute + } + } + + //add the difference between the two values to max offset, not the entire value again... + if($bLast && sizeof($optarr2) == 2) { $this->eofMax += ($optarr2[MAX_]-$optarr2[MIN_]); } + if ($optarr2[MIN_]) { $this->suboffs[] = $optarr2[MIN_]; } + } + else + { + $this->suboffs[] = 0; + } + + return $string; + } + + /********************************************************************* + * + * Function : + * + * Description : given a string find longest unambiguous sequence + * i.e. longest sequence that doesn't contain syntax + * + * parameters : + * + * Returns : + * + *********************************************************************/ + private function longestUnambiguousSequence($string) + { + $fragments = array(); + $all_fragments = array(); + $len = strlen($string); + + for($i = 0; $i < $len; $i++) + { + $frag_stored = false; + + $char = $string[$i]; + + // ?? {n} {k-m} (a|b) [!a:b] + if ($char == '?' || $char == '{' + || $char == '(' || $char == '[') + { + //if array is empty can take first substring as-is + if (sizeof($fragments) == 0) + { + $fragments[] = substr($string, 0, $i); + $all_fragments[] = substr($string, 0, $i); + $string = substr($string, $i); + $this->setStrLen($len, $i, $string); + } + else + { + if ($i != 0) + { + //should work, but need more data to test... + $fragments[] = substr($string, 0, $i); + $all_fragments[] = substr($string, 0, $i); + } + + switch($char) + { + case '?': + //put wildcard into secondary array... + $all_fragments[] = substr($string, $i, 2); + $string = substr($string, $i+2); + $this->setStrLen($len, $i, $string); + break; + + case '[': + //put wildcard into secondary array... + $all_fragments[] = substr($string, $i, (strpos($string, ']') - $i) + 1); + $string = substr($string, strpos($string, ']')+1); + $this->setStrLen($len, $i, $string); + break; + + case '{': + $all_fragments[] = substr($string, $i, (strpos($string, '}') - $i) + 1); + $string = substr($string, strpos($string, '}')+1); + $this->setStrLen($len, $i, $string); + break; + + case '(': + $all_fragments[] = substr($string, $i, (strpos($string, ')') - $i) + 1); + $string = substr($string, strpos($string, ')')+1); + $this->setStrLen($len, $i, $string); + break; + } + } + } + } + + if ($string) + { + $fragments[] = $string; + $all_fragments[] = $string; + } + + $maxlen = max(array_map('strlen', $fragments)); + + for($i = 0; $i < sizeof($fragments); $i++) + { + if(strlen($fragments[$i]) == $maxlen) + { + $longest_frag = $fragments[$i]; + $longest_index = $i; + break; + } + } + + $frag_return = array(); + $frag_return[] = $longest_frag; + $frag_return[] = $all_fragments; + return $frag_return; + } + + /********************************************************************* + * + * Function : + * + * Description : + * + * parameters : + * + * Returns : + * + *********************************************************************/ + //reset iterator and string count as required... + private function setStrLen(&$len, &$i, $string) + { + $len = strlen($string); + $i = -1; //$i = -1 set to zero when enters loop + } + + /********************************************************************* + * + * Function : + * + * Description : + * + * parameters : + * + * Returns : + * + *********************************************************************/ + //handle the different signature string fragments belonging to a single + //subsequence. output xml as appropriate as we cycle through the string components. + private function handleStringFragments($fragment_longest_pair, $doc, $parent, $xml) + { + $longest_frag = $fragment_longest_pair[0]; + $fragments = $fragment_longest_pair[1]; + + $seq = $xml->xmlCreateElement('Sequence', $doc, $parent); + $xml->xmlAddTextValue($longest_frag, $doc, $seq); + + $longestLen = strlen($longest_frag) / $this->byte_len; + + //default and value for BOF sequences... + $shiftVal = $longestLen; + + $seq = $xml->xmlCreateElement('DefaultShift', $doc, $parent); + + if($this->eof) + { + $shiftVal = -1; + $xml->xmlAddTextValue(-($longestLen+1), $doc, $seq); + } + else + { + $xml->xmlAddTextValue($longestLen+1, $doc, $seq); + } + + $uniqueByte = array(); + + for ($i = 0; $i < strlen($longest_frag); $i+=$this->byte_len) + //for ($i = strlen($longest_frag)-2; $i >= 0; $i-=$this->byte_len) + { + $bExists = false; + $byte = substr($longest_frag, $i, $this->byte_len); + + for ($j = 0; $j < sizeof($uniqueByte); $j++) + { + if($byte === $uniqueByte[$j][0]) + { + $bExists = true; + if(!$this->eof) { $uniqueByte[$j][1] = $shiftVal; } //update to get distance from end + break; + } + } + + if ($bExists === false) + { + $uniqueByte[] = array($byte, $shiftVal); + } + + $shiftVal--; + } + + for ($j = 0; $j < sizeof($uniqueByte); $j++) + { + //loop to output xml elements + $shift = $xml->xmlCreateElement('Shift', $doc, $parent); + $xml->xmlAddTextValue($uniqueByte[$j][1], $doc, $shift); + $xml->xmlAddAttribute('Byte', $uniqueByte[$j][0], $doc, $shift); + } + + //arrarys for left and right fragments... + $left = array(); + $right = array(); + + $first = &$left; + + for ($i = 0; $i < sizeof($fragments); $i++) + { + if ($fragments[$i] === $longest_frag) + { + $first = &$right; + } + else + { + $first[] = $fragments[$i]; + } + } + + if ($left) { $this->outputFragPos(true, $left, $doc, $parent, $xml); } + if ($right) { $this->outputFragPos(false, $right, $doc, $parent, $xml); } + + } + + /********************************************************************* + * + * Function : + * + * Description : + * + * parameters : + * + * Returns : + * + *********************************************************************/ + private function recombineArray($fragments) + { + //array to bring together multiple parts of same fragment + //such as those parts of array containing square brackets + $recombine_arr = array(); + + $string = ''; + + for ($i = 0; $i < sizeof($fragments); $i++) + { + $char = $fragments[$i][0]; + + if($char != '?' && $char != '{' && $char != '(') + { + $string = $string . $fragments[$i]; + } + else + { + if($string != '') + $recombine_arr[] = $string; + + $string = ''; + $recombine_arr[] = $fragments[$i]; + } + } + + //Add string remainder to array + if($string != '') + $recombine_arr[] = $string; + + return $recombine_arr; + } + + /********************************************************************* + * + * Function : + * + * Description : + * + * parameters : + * + * Returns : + * + *********************************************************************/ + //output correct stuff for left or right fragments... + private function outputFragPos($bLeft, $fragments, $doc, $parent, $xml) + { + $fragments = $this->recombineArray($fragments); + + //print_r($fragments); + + $orientation = ($bLeft) ? "LeftFragment" : "RightFragment"; + + //if bLeft need to work through array backwards... + if($bLeft) + { + $fragments = array_reverse($fragments, false); + } + + $fraglen = 0; + //TODO: if bLeft and BOFSequence then min frag calculated from beginning + //TODO: if !bLeft and EOFSequence then we calculate from the end minfraglength + + $string = ''; + $fragPos = 0; + + $minoffset = 0; //output and reset every time we output string. + $maxoffset = 0; + + for ($i = 0; $i < sizeof($fragments); $i++) + { + //charAt(0) will always be syntactical character + //check to see if this matches anything but '[' + $char = substr($fragments[$i], 0, 1); + + switch ($char) + { + case '?': + //offset count + one byte... output string. + + //potential to have two ?? or more after one another. + if(strlen($string) > 0) + { + $this->fragXMLOutput($orientation, $string, $fragPos+1, $minoffset, $maxoffset, $doc, $parent, $xml); + $fraglen = $this->handleFragLength($fraglen, $minoffset, $string); + $fragPos += 1; + $minoffset = 0; + $maxoffset = 0; + } + + $minoffset += 1; + $maxoffset += 1; + $string = ''; + break; + + case '{': + //output $string var and... + //values in curly brackets become offset values... + + if(strlen($string) > 0) + { + $this->fragXMLOutput($orientation, $string, $fragPos+1, $minoffset, $maxoffset, $doc, $parent, $xml); + $fraglen = $this->handleFragLength($fraglen, $minoffset, $string); + $fragPos += 1; + $minoffset = 0; + $maxoffset = 0; + } + + $optarr = $this->getBracketedValues('-', $fragments[$i]); + + if(sizeof($optarr) == 2) + { + $minoffset = $minoffset + $optarr[0]; + $maxoffset = $maxoffset + $optarr[1]; + } + elseif (sizeof($optarr) == 1) + { + $minoffset = $minoffset + $optarr[0]; + $maxoffset = $maxoffset + $optarr[0]; + } + //else we've a bum array... not sure what to do. + + $string = ''; + break; + + case '(': + //output $string var and... + //output current array string into each of the potential options... + if(strlen($string) > 0) + { + $this->fragXMLOutput($orientation, $string, $fragPos+1, $minoffset, $maxoffset, $doc, $parent, $xml); + $fraglen = $this->handleFragLength($fraglen, $minoffset, $string); + $fragPos += 1; + $minoffset = 0; + $maxoffset = 0; + } + + $optarr = $this->getBracketedValues('|', $fragments[$i]); + + //minimum length of stirng in options array to increment min $fraglength + //handle differently as we don't want $fraglength to increase for each option + $minlen = (min(array_map('strlen', $optarr))) / 2; + + //output identical nodes for both options, e.g. if pos == 6, pos == 6 for both of these + for($j = 0; $j < sizeof($optarr); $j++) + { + $this->fragXMLOutput($orientation, $optarr[$j], $fragPos+1, $minoffset, $maxoffset, $doc, $parent, $xml); + } + $fraglen += $minlen; + $fragPos += 1; + $minoffset = 0; + $maxoffset = 0; + + $string = ''; + break; + + default: + $string = $string . $fragments[$i]; + break; + } + } + + if(strlen($string) > 0) + { + $this->fragXMLOutput($orientation, $string, $fragPos+1, $minoffset, $maxoffset, $doc, $parent, $xml); + $fragPos += 1; + $fraglen = $this->handleFragLength($fraglen, $minoffset, $string); + } + + //output the fragment length count here as required... + if (!$this->eof && $bLeft) + { + $this->minFragLen = $fraglen; + } + elseif ($this->eof && $bLeft == false) + { + $this->minFragLen = $fraglen; + } + } + + /********************************************************************* + * + * Function : + * + * Description : + * + * parameters : + * + * Returns : + * + *********************************************************************/ + //count the number of characters output when we output $string + private function handleFragLength($fraglen, $minoffset, $string) + { + $fraglen = $fraglen + $minoffset; + + //for each set of bracketed values remove len '[a4:a5]' minus two + //e.g. [a4:a5] equals seven in length. remove five and we are left + //with two. this divided by two equals one byte... + //len to remove: + $n_len = 6; //negated length + $p_len = 5; //plain length + + $complete_string_len = strlen($string); + + //This is very much a shortcut but one that does the trick remove + //occurrances of ':' to allow us to count the number of [AB:AC] + //wildcards in a string to then calculate the minfraglength from... + $strlen_card_removed = strlen(str_replace(':', '', $string)); + + $no_bracketed_wildcards = $complete_string_len - $strlen_card_removed; + + $negation_no = 0; + + if ($no_bracketed_wildcards > 0) + { + $strlen_negation_removed = strlen(str_replace('!', '', $string)); + + //number of wildcards with negation in... + $negation_no = $complete_string_len - $strlen_negation_removed; + + //number of wildcards left without negation... + $plain_count = $no_bracketed_wildcards - $negation_no; + + for ($i = 0; $i < $negation_no; $i++) + { + $complete_string_len = $complete_string_len - $n_len; + } + + for ($i = 0; $i < $plain_count; $i++) + { + $complete_string_len = $complete_string_len - $p_len; + } + } + + $bytes = $complete_string_len / 2; + + //add value to fraglength count... + $fraglen = $fraglen + $bytes; + + return $fraglen; + } + + + + + /********************************************************************* + * + * Function : + * + * Description : + * + * parameters : + * + * Returns : + * + *********************************************************************/ + //small function to ease the pain out outputting a new fragment value... + private function fragXMLOutput($orientation, $string, $fragPos, $min, $max, $doc, $parent, $xml) + { + $frag = $xml->xmlCreateElement($orientation, $doc, $parent); + $xml->xmlAddAttribute('MaxOffset', $max, $doc, $frag); + $xml->xmlAddAttribute('MinOffset', $min, $doc, $frag); + $xml->xmlAddAttribute('Position', $fragPos, $doc, $frag); + $xml->xmlAddTextValue($string, $doc, $frag); + } + + + + + /********************************************************************* + * + * Function : + * + * Description : + * + * parameters : + * + * Returns : + * + *********************************************************************/ + //split bracketed values into an array or single variable depending + //on delimeter inbetween {'-'} ('|') + private function getBracketedValues($delimeter, $fragments) + { + $len = strlen($fragments); + $options = ''; + + //might not need array as we figure out how to output nodes + //might do on the fly. array structures things nicely at the moment + $optarr = array(); + + //exit condition after open-bracket end before close-bracket + for ($j = 1; $j < $len-1; $j++) + { + $optchar = $fragments[$j]; + + if ($optchar == $delimeter) + { + $optarr[] = $options; + $options = ''; + } + else + { + $options = $options . $optchar; + } + } + + $optarr[] = $options; + + return $optarr; + } +} +?> diff --git a/php/process_signature_form.php b/php/process_signature_form.php new file mode 100644 index 0000000..05fcee9 --- /dev/null +++ b/php/process_signature_form.php @@ -0,0 +1,222 @@ +newDOMDocument(); + + $rdf = $xml->xmlCreateRootElement('rdf:RDF', null, $doc, $doc); + $xml->xmlAddAttribute('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema#', $doc, $rdf); + $xml->xmlAddAttribute('xmlns:sigdev', 'http://nationalarchives.gov.uk/preservation/sigdev/signature/', $doc, $rdf); + $xml->xmlAddAttribute('xmlns:bytes', 'http://nationalarchives.gov.uk/preservation/sigdev/signature/byteSequence/', $doc, $rdf); + $xml->xmlAddAttribute('xmlns:rdf', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', $doc, $rdf); + $xml->xmlAddAttribute('xmlns:rdfs', 'http://www.w3.org/2000/01/rdf-schema#', $doc, $rdf); + + $sigdev = $xml->xmlCreateElement('sigdev:DevelopmentSignature', $doc, $rdf); + $xml->xmlAddAttribute('rdf:about', 'http://nationalarchives.gov.uk/preservation/sigdev/signature/' . UUID::mint( 4 ), $doc, $sigdev); + + $xml->xmlCreateTextElement('rdfs:label', $_POST[GET_NAME], $doc, $sigdev); + $xml->xmlCreateTextElement('sigdev:version', $_POST[GET_VERSION], $doc, $sigdev); + $xml->xmlCreateTextElement('sigdev:extension', $_POST['extension1'], $doc, $sigdev); + $xml->xmlCreateTextElement('sigdev:internetMediaType', $_POST['mimetype1'], $doc, $sigdev); + $xml->xmlCreateTextElement('sigdev:puid', $_POST['puid1'], $doc, $sigdev); + + $count = $_POST['counter']; + + $uuid_var = UUID::mint( 4 ); + + for($x = 1; $x <= $count; $x++) + { + $sequence_url = 'http://nationalarchives.gov.uk/preservation/sigdev/signature/byteSequence/' . $uuid_var . '/' . $x; + + $byteseq = $xml->xmlCreateElement('sigdev:byteSequence', $doc, $sigdev); + $seq = $xml->xmlCreateElement('rdf:Description', $doc, $byteseq); + $xml->xmlAddAttribute('rdf:about', $sequence_url, $doc, $seq); + + $str = $xml->xmlCreateTextElement('bytes:string', $_POST['signature'.$x], $doc, $seq); + $xml->xmlAddAttribute('rdf:datatype', 'http://nationalarchives.gov.uk/preservation/sigdev/signature/droidRegularExpression', $doc, $str); + + $xml->xmlCreateTextElement('bytes:anchor', $_POST['anchor'.$x], $doc, $seq); + + $offset = $xml->xmlCreateTextElement('bytes:offset', $_POST['offset'.$x], $doc, $seq); + $xml->xmlAddAttribute('rdf:datatype', 'http://www.w3.org/2001/XMLSchema#integer', $doc, $offset); + + $maxOffset = $xml->xmlCreateTextElement('bytes:maxOffset', $_POST['maxoffset'.$x], $doc, $seq); + $xml->xmlAddAttribute('rdf:datatype', 'http://www.w3.org/2001/XMLSchema#integer', $doc, $maxOffset); + } + + $filename = 'Content-disposition: attachment; filename=' . str_replace(' ', '-', $_POST[GET_NAME]) . '-v' . str_replace(' ', '-', $_POST[GET_VERSION]) . '.rdf'; + + header($filename); + header ('Content-Type: text/xml'); + print $doc->saveXML(); + } + + + /********************************************************************* + * + * Function : + * + * Description : description... + * + * parameters : + * + * Returns : n/a + * + *********************************************************************/ + function generateSignatureCollection($count) + { + $gen = new SignatureGenerator(); + $signature_collection = new SignatureCollection(); + + for($i = 1; $i < $count + 1; $i++) + { + $new_sig = 'signature' . $i; + $new_anc = 'anchor' . $i; + $new_off = 'offset' . $i; + $new_max = 'maxoffset' . $i; + + $byte_sequence = new ByteSequence(); + + $signature_collection->sig_id = 1; + $signature_collection->specificity = 'Specific'; + + $byte_sequence->position = $_POST[$new_anc]; + $byte_sequence->offset = $_POST[$new_off]; + $byte_sequence->maxoffset = $_POST[$new_max]; + $byte_sequence->byte_sequence = $_POST[$new_sig]; + + $signature_collection->byteSequence[] = $byte_sequence; + } + + return $gen->generateSignatureFromObject($signature_collection); + } + + /********************************************************************* + * + * Function : + * + * Description : description... + * + * parameters : + * + * Returns : n/a + * + *********************************************************************/ + function generateFormatCollection($i) + { + $new_name = 'name' . $i; + $new_version = 'version' . $i; + $new_puid = 'puid' . $i; + $new_mime = 'mimetype' . $i; + $new_ext = 'extension' . $i; + + $xml = new XMLHelper(); + + $collection = $xml->newDOMDocument(); + + $ffc = $xml->xmlCreateElement('FileFormatCollection', $collection, $collection); + $ff = $xml->xmlCreateElement('FileFormat', $collection, $ffc); + + $xml->xmlAddAttribute('ID', $i, $collection, $ff); + $xml->xmlAddAttribute('Name', $_POST[$new_name], $collection, $ff); + + if(isset($_POST[$new_puid]) == true) + { + $xml->xmlAddAttribute('PUID', $_POST[$new_puid], $collection, $ff); + } + else + { + $test_puid = substr($_POST[$new_name], 0, 3) . '/' . $i; + $xml->xmlAddAttribute('PUID', $test_puid, $collection, $ff); + } + + if(isset($_POST[$new_version]) == true) + { + $xml->xmlAddAttribute('Version', $_POST[$new_version], $collection, $ff); + } + + if(isset($_POST[$new_mime]) == true) + { + $xml->xmlAddAttribute('MIMEType', $_POST[$new_mime], $collection, $ff); + } + + $sig = $xml->xmlCreateElement('InternalSignatureID', $collection, $ff); + $xml->xmlAddTextValue($i, $collection, $sig); + + if(isset($_POST[$new_ext]) == true) + { + $sig = $xml->xmlCreateElement('Extension', $collection, $ff); + $xml->xmlAddTextValue($_POST[$new_ext], $collection, $sig); + } + + //header ('Content-Type: text/xml'); + return $collection->saveXML(); + } + + /********************************************************************* + * + * Function : + * + * Description : description... + * + * parameters : + * + * Returns : n/a + * + *********************************************************************/ + function generateSignatureFile($byte_sequences, $file_formats) + { + $xml = new XMLHelper(); + $collection = $xml->newDOMDocument(); + + $xml->xmlCreateElement('InternalSignatureCollection', $collection, $collection); + + $collection = $collection->saveXML(); + + $collection = $xml->combine_xml($collection, $byte_sequences, 'InternalSignatureCollection', 'InternalSignature'); + + //Create the FFSignatureFile Portion of the document + $xmlns = 'http://www.nationalarchives.gov.uk/pronom/SignatureFile'; + + // Current date/time in your computer's time zone. + $date = new DateTime(); + $xml = new XMLHelper(); + $doc1 = $xml->newDOMDocument(); + + $signatureFile = $xml->xmlCreateElement('FFSignatureFile', $doc1, $doc1); + $xml->xmlAddAttribute('Version', $_POST['fileVer1'] ?? 1, $doc1, $signatureFile); + $xml->xmlAddAttribute('xmlns', $xmlns, $doc1, $signatureFile); + + $xml->xmlAddAttribute('DateCreated', $date->format(DateTime::W3C), $doc1, $signatureFile); + + $doc1 = $doc1->saveXML(); + + $doc1 = $xml->combine_xml($doc1, $collection, 'FFSignatureFile', 'InternalSignatureCollection'); + $doc1 = $xml->combine_xml($doc1, $file_formats, 'FFSignatureFile', 'FileFormatCollection'); + + header ('Content-Type: text/xml'); + echo $doc1; + } +?> diff --git a/php/uuid/lib.uuid.php b/php/uuid/lib.uuid.php new file mode 100644 index 0000000..3f25ab6 --- /dev/null +++ b/php/uuid/lib.uuid.php @@ -0,0 +1,314 @@ +string; + } + + public function __get($var) { + switch($var) { + case "bytes": + return $this->bytes; + case "hex": + return bin2hex($this->bytes); + case "string": + return $this->__toString(); + case "urn": + return "urn:uuid:".$this->__toString(); + case "version": + return ord($this->bytes[6]) >> 4; + case "variant": + $byte = ord($this->bytes[8]); + if ($byte >= self::varRes) + return 3; + if ($byte >= self::varMS) + return 2; + if ($byte >= self::varRFC) + return 1; + else + return 0; + case "node": + if (ord($this->bytes[6])>>4==1) + return bin2hex(substr($this->bytes,10)); + else + return NULL; + case "time": + if (ord($this->bytes[6])>>4==1) { + // Restore contiguous big-endian byte order + $time = bin2hex($this->bytes[6].$this->bytes[7].$this->bytes[4].$this->bytes[5].$this->bytes[0].$this->bytes[1].$this->bytes[2].$this->bytes[3]); + // Clear version flag + $time[0] = "0"; + // Do some reverse arithmetic to get a Unix timestamp + $time = (hexdec($time) - self::interval) / 10000000; + return $time; + } + else + return NULL; + default: + return NULL; + } + } + + protected function __construct($uuid) { + if (strlen($uuid) != 16) + throw new UUIDException("Input must be a valid UUID."); + $this->bytes = $uuid; + // Optimize the most common use + $this->string = + bin2hex(substr($uuid,0,4))."-". + bin2hex(substr($uuid,4,2))."-". + bin2hex(substr($uuid,6,2))."-". + bin2hex(substr($uuid,8,2))."-". + bin2hex(substr($uuid,10,6)); + } + + protected static function mintTime($node = NULL, $seq = NULL, $time = NULL) { + /* Generates a Version 1 UUID. + These are derived from the time at which they were generated. */ + // Do a sanity check on clock sequence + if ($seq !== NULL && strlen($seq) != 2) { + throw UUIDException("Clock sequence most be a two-byte binary string."); + } + // If no time is specified, get time since Gregorian calendar + // reform in 100ns intervals. This is exceedingly difficult + // because of PHP's (and pack()'s) integer size limits + // Note that this will never be more accurate than to the microsecond + // Specifying a time for this method should only ever be used for + // debugging purposes, lest uniqueness be compromised + $time = ($time !== NULL) ? (float) $time : microtime(1); + $time = $time * 10000000 + self::interval; + // Convert to a string representation + $time = sprintf("%F", $time); + preg_match("/^\d+/", $time, $time); //strip decimal point + // And now to a 64-bit binary representation + $time = base_convert($time[0], 10, 16); + $time = pack("H*", str_pad($time, 16, "0", STR_PAD_LEFT)); + // Reorder bytes to their proper locations in the UUID + $uuid = $time[4].$time[5].$time[6].$time[7].$time[2].$time[3].$time[0].$time[1]; + // Generate a random clock sequence if one is not provided + // Please consult Sections 4.1.5 and 4.2.1 of RFC 4122 for + // guidance regarding when to use a new clock sequence + $uuid .= ($seq !== NULL) ? $seq : self::randomBytes(2); + // set variant + $uuid[8] = chr(ord($uuid[8]) & self::clearVar | self::varRFC); + // set version + $uuid[6] = chr(ord($uuid[6]) & self::clearVer | self::version1); + // Set the final 'node' parameter, a MAC address + if ($node) + $node = self::makeBin($node, 6); + if (!$node) { + // If no node was provided or if the node was invalid, + // generate a random MAC address and set the multicast bit + $node = self::randomBytes(6); + $node[0] = pack("C", ord($node[0]) | 1); + } + $uuid .= $node; + return $uuid; + } + + protected static function mintRand() { + /* Generate a Version 4 UUID. + These are derived soly from random numbers. */ + // generate random fields + $uuid = self::randomBytes(16); + // set variant + $uuid[8] = chr(ord($uuid[8]) & self::clearVar | self::varRFC); + // set version + $uuid[6] = chr(ord($uuid[6]) & self::clearVer | self::version4); + return $uuid; + } + + protected static function mintName($ver, $node, $ns) { + /* Generates a Version 3 or Version 5 UUID. + These are derived from a hash of a name and its namespace, in binary form. */ + if (!$node) + throw new UUIDException("A name-string is required for Version 3 or 5 UUIDs."); + // if the namespace UUID isn't binary, make it so + $ns = self::makeBin($ns, 16); + if (!$ns) + throw new UUIDException("A valid UUID namespace is required for Version 3 or 5 UUIDs."); + switch($ver) { + case self::MD5: + $version = self::version3; + $uuid = md5($ns.$node,1); + break; + case self::SHA1: + $version = self::version5; + $uuid = substr(sha1($ns.$node,1),0, 16); + break; + } + // set variant + $uuid[8] = chr(ord($uuid[8]) & self::clearVar | self::varRFC); + // set version + $uuid[6] = chr(ord($uuid[6]) & self::clearVer | $version); + return ($uuid); + } + + protected static function makeBin($str, $len) { + /* Insure that an input string is either binary or hexadecimal. + Returns binary representation, or false on failure. */ + if ($str instanceof self) + return $str->bytes; + if (strlen($str)==$len) + return $str; + else + $str = preg_replace("/^urn:uuid:/is", "", $str); // strip URN scheme and namespace + $str = preg_replace("/[^a-f0-9]/is", "", $str); // strip non-hex characters + if (strlen($str) != ($len * 2)) + return FALSE; + else + return pack("H*", $str); + } + + public static function initRandom() { + /* Look for a system-provided source of randomness, which is usually crytographically secure. + /dev/urandom is tried first because tests suggest CAPICOM is quite slow to initialize. */ + if (is_readable('/dev/urandom')) { + self::$randomSource = fopen('/dev/urandom', 'rb'); + self::$randomFunc = 'randomFRead'; + } + else if (class_exists('COM', 0)) { + try { + self::$randomSource = new COM('CAPICOM.Utilities.1'); // See http://msdn.microsoft.com/en-us/library/aa388182(VS.85).aspx + self::$randomFunc = 'randomCOM'; + } + catch(Exception $e) {} + } + return self::$randomFunc; + } + + public static function randomBytes($bytes) { + return call_user_func(array('self', self::$randomFunc), $bytes); + } + + protected static function randomTwister($bytes) { + /* Get the specified number of random bytes, using mt_rand(). + Randomness is returned as a string of bytes. */ + $rand = ""; + for ($a = 0; $a < $bytes; $a++) { + $rand .= chr(mt_rand(0, 255)); + } + return $rand; + } + + protected static function randomFRead($bytes) { + /* Get the specified number of random bytes using a file handle + previously opened with UUID::initRandom(). + Randomness is returned as a string of bytes. */ + return fread(self::$randomSource, $bytes); + } + + protected static function randomCOM($bytes) { + /* Get the specified number of random bytes using Windows' + randomness source via a COM object previously created by UUID::initRandom(). + Randomness is returned as a string of bytes. */ + return base64_decode(self::$randomSource->GetRandom($bytes,0)); // straight binary mysteriously doesn't work, hence the base64 + } +} + +class UUIDException extends Exception { +} diff --git a/php/xmllib/xmllib.php b/php/xmllib/xmllib.php new file mode 100644 index 0000000..9ea2bd7 --- /dev/null +++ b/php/xmllib/xmllib.php @@ -0,0 +1,212 @@ +formatOutput = true; + $doc->encoding='UTF-8'; + return $doc; + } + + /********************************************************************* + * + * Function : xmlCreateRootElement + * + * Description : create root element and append to given DOM document + * + * parameters : $name - name of element + * $namespace - optional namespace + * $doc - DOM document + * $parent - parent element to append new element + * + * Returns : $element - xml root element + * + *********************************************************************/ + public function xmlCreateRootElement($name, $namespace, $doc, $parent) + { + if($namespace === null) + $element = $doc->createElement($name); + else + $element = $doc->createElementNS($namespace, $name); + + $parent->appendChild($element); + return $element; + } + + /********************************************************************* + * + * Function : xmlCreateElement + * + * Description : create a vanilla element and append to given parent node + * + * parameters : $name - name of element + * $doc - DOM document + * $parent - parent element to append new element + * + * Returns : new xml node + * + *********************************************************************/ + public function xmlCreateElement($name, $doc, $parent) + { + $element = $doc->createElement($name); + $parent->appendChild($element); + return $element; + } + + /********************************************************************* + * + * Function : xmlCreateTextElement + * + * Description : create a new xml element containing text + * + * parameters : $name - name of element + * $value - value of text to add to element + * $doc - DOM document + * $parent - parent element to append new element + * + * Returns : new xml node + * + *********************************************************************/ + public function xmlCreateTextElement($name, $value, $doc, $parent) + { + $element = $doc->createElement($name); + $parent->appendChild($element); + $this->xmlAddTextValue($value, $doc, $element); + return $element; + } + + /********************************************************************* + * + * Function : xmlAddAttribute + * + * Description : create xml attribute and add to given node + * + * parameters : $name - name of attribute + * $value - value to give attribute + * $doc - DOM document + * $parent - parent element to append new attribute + * + * Returns : n/a + * + *********************************************************************/ + public function xmlAddAttribute($name, $value, $doc, $parent) + { + $attribute = $doc->createAttribute($name); + $this->xmlAddTextValue($value, $doc, $attribute); + $parent->appendChild($attribute); + } + + /********************************************************************* + * + * Function : xmlAddTextValue + * + * Description : public function to add text values to nodes or attributes + * wraps the function to be used inside 'this' class or + * externally if required. + * + * parameters : $value - text value for element or attribute + * $doc - DOM document + * $parent - parent element to append value + * + * Returns : n/a + * + *********************************************************************/ + //add a text value to xml node or attribute. used inside this + //class for attributes or externally when working on the DOM... + public function xmlAddTextValue($value, $doc, $parent) + { + $textValue = $doc->createTextNode($value); + $parent->appendChild($textValue); + } + + /********************************************************************* + * + * Function : xmlOutput + * + * Description : convert DOM to XML document and output to broweser. + * set http header to allow browser to handle correctly. + * + * parameters : $doc - DOM document + * + * Returns : outputs xml to browser + * + *********************************************************************/ + public function xmlOutput($doc) + { + header ('Content-Type: text/xml'); + print $doc->saveXML(); + } + + /********************************************************************* + * + * Function : combine_xml + * + * Description : combine two DOM documents and return the new XML document + * + * parameters : $xml1 - primary XML to concatenate with + * $xml2 - secondary XML to be appended + * $xml1_parent - primary node to afix secondary DOM to + * $xml2_node - secondary node from which to afix DOM + * + * Returns : new XML document + * + *********************************************************************/ + public function combine_xml($xml1, $xml2, $xml1_parent, $xml2_node) + { + //TODO: Additional work needed here to solidify function a bit better + //combining two DOM objects is a fairly precarious operation depending + //a lot of calling class/function. Add better error handling. Fix variable + //names. + + //TODO: assumption made here that we have a DOM object. Need better checking + if(!is_object($xml1)) + { + $doc1 = new DOMDocument(); + $doc1->loadXML($xml1); + $doc1->formatOutput = true; + } + else + { + $doc1 = $xml1; + } + + if(!is_object($xml2)) + { + $doc2 = new DOMDocument(); + $doc2->loadXML($xml2); + $doc2->formatOutput = true; + } + else + { + $doc2 = $xml2; + } + + $node = $doc2->getElementsByTagName($xml2_node)->item(0); + $new_node = $doc1->importNode($node, true); + $doc1->getElementsByTagName($xml1_parent)->item(0)->appendChild($new_node); + + //TODO: return DOM or XML... function parameter (?) + return $doc1->saveXML(); + } +} + +?> diff --git a/pkg/sigdevutil/container_interface_structs.go b/pkg/sigdevutil/container_interface_structs.go index 730b33c..5c9936e 100644 --- a/pkg/sigdevutil/container_interface_structs.go +++ b/pkg/sigdevutil/container_interface_structs.go @@ -157,7 +157,7 @@ func (container *ContainerSignatureInterface) ToDROIDContainer() ContainerSignat // GetFileName is a small helper function that helps us make some // useful metadata about our output. func (container *ContainerSignatureInterface) GetFileName() string { - const devSig = "development-container-signature" - nicePUID := strings.Replace(container.PUID, "/", "-", 1) - return fmt.Sprintf("%s-%s", devSig, nicePUID) + niceName := formatFilenameString(container.Description) + nicePUID := formatFilenameString(container.PUID) + return fmt.Sprintf("%s-%s-%s", niceName, nicePUID, generateDateNoSpaces()) } diff --git a/pkg/sigdevutil/go.mod b/pkg/sigdevutil/go.mod deleted file mode 100644 index 9c49e30..0000000 --- a/pkg/sigdevutil/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/exponential-decay/signature-development-utility/pkg/sigdevutil - -go 1.13 diff --git a/pkg/sigdevutil/interfaces_test.go b/pkg/sigdevutil/interfaces_test.go index 8efe989..3eda8b6 100644 --- a/pkg/sigdevutil/interfaces_test.go +++ b/pkg/sigdevutil/interfaces_test.go @@ -29,10 +29,10 @@ func setupSignatureInterface() SignatureInterface { return sig } -// TestStandardSingatureGeneration will test the standard signature +// TestStandardSignatureGeneration will test the standard signature // generation to make sure we're making something we know that works // with DROID. -func TestStandardSingatureGeneration(t *testing.T) { +func TestStandardSignatureGeneration(t *testing.T) { // Replace time.Now() with a placeholder we can guarantee the result // of when we call it. now = func() time.Time { return time.Date(1970, 1, 1, 0, 0, 0, 651387237, time.UTC) } @@ -84,12 +84,12 @@ func setupContainerSignatureInterface() ContainerSignatureInterface { // container signature files compatible with DROID. func TestContainerSignatureGeneration(t *testing.T) { signatureInterface := setupContainerSignatureInterface() - containerSingatureFile := signatureInterface.ToDROIDContainer() + containerSignatureFile := signatureInterface.ToDROIDContainer() // Compare the output with our fixture. - if containerSingatureFile.String() != containerSignatureOne { + if containerSignatureFile.String() != containerSignatureOne { t.Errorf( "Test XML doesn't match expected XML, outputting test XML: %s", - containerSingatureFile.String(), + containerSignatureFile.String(), ) } } diff --git a/pkg/sigdevutil/shared.go b/pkg/sigdevutil/shared.go index 420c144..1648698 100644 --- a/pkg/sigdevutil/shared.go +++ b/pkg/sigdevutil/shared.go @@ -4,6 +4,7 @@ package sigdevutil import ( + "strings" "time" ) @@ -27,3 +28,12 @@ func generateDateNoSpaces() string { currentTime := now() return currentTime.Format(dateFormat) } + +// formatFilenameString returns a normalized component of a filename to +// the caller. +func formatFilenameString(input string) string { + niceName := strings.Replace(input, "/", "-", -1) + niceName = strings.Replace(niceName, " ", "-", -1) + niceName = strings.Replace(niceName, "*", "-", -1) + return niceName +} diff --git a/pkg/sigdevutil/standard_interface_structs.go b/pkg/sigdevutil/standard_interface_structs.go index 75effd4..118c11d 100644 --- a/pkg/sigdevutil/standard_interface_structs.go +++ b/pkg/sigdevutil/standard_interface_structs.go @@ -5,9 +5,13 @@ package sigdevutil import ( "fmt" + "io/ioutil" + "net/http" "net/url" + "os" "strconv" "strings" + "time" ) // SignatureInterface provides a modern mapping for the DROID signature @@ -20,6 +24,7 @@ type SignatureInterface struct { MimeType string // File format MIMEtype. Extension string // File format extension. Sequences []sequences // File format signature sequences. + FileVersion string // Signature file version number. } // sequences ... @@ -48,6 +53,7 @@ func (signature *SignatureInterface) ProcessSignature(form url.Values) { signature.VersionNumber = strings.TrimSpace(form[version][0]) signature.MimeType = strings.TrimSpace(form[mimetype][0]) signature.Extension = strings.TrimSpace(form[ext][0]) + signature.FileVersion = fmt.Sprintf("%d", time.Now().Unix()) // Signature information. const signatureField = "signature-input-0" const offsetField = "offset-0" @@ -170,7 +176,78 @@ func (signature *SignatureInterface) ToDROID(triggers bool) FFSignatureFile { // GetFileName is a small helper function that helps us make some // useful metadata about our output. func (signature *SignatureInterface) GetFileName() string { - const devSig = "development-signature" - nicePUID := strings.Replace(signature.PUID, "/", "-", 1) - return fmt.Sprintf("%s-%s", devSig, nicePUID) + const sigFile = "signature-file" + niceName := formatFilenameString(signature.FormatName) + niceVersion := formatFilenameString(signature.VersionNumber) + return fmt.Sprintf("%s-%s-%s", niceName, niceVersion, sigFile) +} + +// Bootstrap signature development utility 2.0 to Signature development +// utility 1.0. +func (signature *SignatureInterface) ToPHP(port string) string { + + counter := strconv.Itoa(len(signature.Sequences)) + + const ORIGINALURL = "http://localhost:%s/php/process_signature_form.php" + + const count = "counter" + const name = "name1" + const version = "version1" + const ext = "extension1" + const mime = "mimetype1" + const puid = "puid1" + const fileVer = "fileVer1" + + data := url.Values{ + count: {counter}, + name: {signature.FormatName}, + version: {signature.VersionNumber}, + ext: {signature.Extension}, + mime: {signature.MimeType}, + puid: {signature.PUID}, + fileVer: {signature.FileVersion}, + } + + const sig = "signature" + const anchor = "anchor" + const offset = "offset" + const maxoffset = "maxoffset" + + const variableAnchor = "Variable" + + for idx := 1; idx <= len(signature.Sequences); idx++ { + sigField := fmt.Sprintf("%s%d", sig, idx) + anchorField := fmt.Sprintf("%s%d", anchor, idx) + offsetField := fmt.Sprintf("%s%d", offset, idx) + maxOffsetField := fmt.Sprintf("%s%d", maxoffset, idx) + + sequence := signature.Sequences[idx-1] + + data[sigField] = []string{sequence.Sequence} + + // Variable sequence handling for 1.0. + data[anchorField] = []string{sequence.Relativity} + // Variable offsets are no longer set by the time it reaches + // this point in the code, so identify it by negating BOF and EOF. + if sequence.Relativity != BOF && sequence.Relativity != EOF { + data[anchorField] = []string{variableAnchor} + } + + data[offsetField] = []string{strconv.Itoa(sequence.Offset)} + data[maxOffsetField] = []string{strconv.Itoa(sequence.MaxOffset)} + } + + originalURL := fmt.Sprintf(ORIGINALURL, port) + fmt.Fprintf(os.Stderr, "Bootstrap URL: %s", originalURL) + + resp, err := http.PostForm(originalURL, data) + if err != nil { + return fmt.Sprintf("Error sending request: %s", err) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return fmt.Sprintf("Error sending request: %s", err) + } + return string(body) } diff --git a/pkg/sigdevutil/trigger_scaffolding.go b/pkg/sigdevutil/trigger_scaffolding.go index cda217e..18cdb7f 100644 --- a/pkg/sigdevutil/trigger_scaffolding.go +++ b/pkg/sigdevutil/trigger_scaffolding.go @@ -6,6 +6,94 @@ package sigdevutil // Create the entries needed for the ZIP trigger in the standard // signature file. func getZIPTrigger() (byteSeq, ffformat) { + var zipSig SignatureInterface + zipSig.PUID = "x-fmt/263" + zipSig.FormatName = "ZIP Format" + zipSig.MimeType = "application/zip" + zipSig.Extension = "zip" + seqs := []string{"504B0304", "504B01", "504B0506"} + rels := []string{BOF, EOF, EOF} + minOffs := []int{0, 61, 0} + maxOffs := []int{4, 65565, 65535} + zipSequences := make([]sequences, len(seqs)) + for idx := 0; idx < len(seqs); idx++ { + zipSequences[idx].Sequence = seqs[idx] + zipSequences[idx].Offset = minOffs[idx] + zipSequences[idx].MaxOffset = maxOffs[idx] + zipSequences[idx].Relativity = rels[idx] + } + zipSig.Sequences = zipSequences + zip := zipSig.ToDROID(false) + zip.InternalSignatureCollection.InternalSignature[0].ID = "2" + zip.FileFormatCollection.FileFormat[0].InternalSignatureID = "2" + zip.FileFormatCollection.FileFormat[0].ID = "2" + return zip.InternalSignatureCollection.InternalSignature[0], zip.FileFormatCollection.FileFormat[0] +} + +// Create the entries needed for the OOXML trigger in the standard +// signature file. +func getOOXMLTrigger() (byteSeq, ffformat) { + var ooxmlSig SignatureInterface + ooxmlSig.PUID = "fmt/189" + ooxmlSig.FormatName = "Microsoft Office Open XML" + ooxmlSig.MimeType = "application/octet-stream" + ooxmlSig.Extension = "" + seqs := []string{"504B0304", "5B436F6E74656E745F54797065735D2E786D6C20A2", "504B0102", "504B0506"} + rels := []string{BOF, BOF, EOF, EOF} + minOffs := []int{0, 4, 0, 0} + maxOffs := []int{0, 30, 65535, 65535} + ooxmlSequences := make([]sequences, len(seqs)) + for idx := 0; idx < len(seqs); idx++ { + ooxmlSequences[idx].Sequence = seqs[idx] + ooxmlSequences[idx].Offset = minOffs[idx] + ooxmlSequences[idx].MaxOffset = maxOffs[idx] + ooxmlSequences[idx].Relativity = rels[idx] + } + ooxmlSig.Sequences = ooxmlSequences + ooxml := ooxmlSig.ToDROID(false) + ooxml.InternalSignatureCollection.InternalSignature[0].ID = "3" + ooxml.FileFormatCollection.FileFormat[0].InternalSignatureID = "3" + ooxml.FileFormatCollection.FileFormat[0].ID = "3" + return ooxml.InternalSignatureCollection.InternalSignature[0], ooxml.FileFormatCollection.FileFormat[0] +} + +// Create the entries needed for the OLE2 trigger in the standard +// signature file. +func getOLE2Trigger() (byteSeq, ffformat) { + // Create OLE2 signature sequence. + /* + FMT/111 + Position type Absolute from BOF + Offset 0 + Byte order + Value D0CF11E0A1B11AE1{20}FEFF + */ + var ole2Sig SignatureInterface + ole2Sig.PUID = "fmt/111" + ole2Sig.FormatName = "OLE2 Compound Document Format" + ole2Sig.MimeType = "application/octet-stream" + ole2Sig.Extension = "" + seqs := []string{"D0CF11E0A1B11AE1", "FEFF"} + rels := []string{BOF, BOF} + maxOffs := []int{0, 28} + ole2Sequences := make([]sequences, len(seqs)) + for idx := 0; idx < len(seqs); idx++ { + ole2Sequences[idx].Sequence = seqs[idx] + ole2Sequences[idx].Offset = 0 + ole2Sequences[idx].MaxOffset = maxOffs[idx] + ole2Sequences[idx].Relativity = rels[idx] + } + ole2Sig.Sequences = ole2Sequences + ole2 := ole2Sig.ToDROID(false) + ole2.InternalSignatureCollection.InternalSignature[0].ID = "4" + ole2.FileFormatCollection.FileFormat[0].InternalSignatureID = "4" + ole2.FileFormatCollection.FileFormat[0].ID = "4" + return ole2.InternalSignatureCollection.InternalSignature[0], ole2.FileFormatCollection.FileFormat[0] +} + +// Create the entries needed for the ZIP trigger in the standard +// signature file. +func getZIPTriggerUnused() (byteSeq, ffformat) { // Create ZIP file signature sequence. /* X-FMT/263 @@ -45,7 +133,7 @@ func getZIPTrigger() (byteSeq, ffformat) { // Create the entries needed for the OOXML trigger in the standard // signature file. -func getOOXMLTrigger() (byteSeq, ffformat) { +func getOOXMLTriggerUnused() (byteSeq, ffformat) { // Create OOXML sequence. /* FMT/189 @@ -81,7 +169,7 @@ func getOOXMLTrigger() (byteSeq, ffformat) { // Create the entries needed for the OLE2 trigger in the standard // signature file. -func getOLE2Trigger() (byteSeq, ffformat) { +func getOLE2TriggerUnused() (byteSeq, ffformat) { // Create OLE2 signature sequence. /* FMT/111 diff --git a/pronom-technical-paper-one/README.md b/pronom-technical-paper-one/README.md index 229a9dc..b35172f 100644 --- a/pronom-technical-paper-one/README.md +++ b/pronom-technical-paper-one/README.md @@ -1,11 +1,15 @@ -#PRONOM Technical Paper One +# PRONOM Technical Paper One -Documentation used to develop the singature utility. +Documentation used to develop the signature utility. -Original source: http://www.nationalarchives.gov.uk/aboutapps/pronom/ +Original source: [nationalarchives.gov.uk/aboutapps/pronom/][pro-1] -###License +[pro-1]: http://www.nationalarchives.gov.uk/aboutapps/pronom/ -Ownership is that of The National Archives, UK. +* [Format identification guide][ff-1]. +* [Pre-processing signature guide][ff-2]. +* [Signature syntax][ff-3]. -**OGL:** http://www.nationalarchives.gov.uk/doc/open-government-licence/version/2/ \ No newline at end of file +[ff-1]: ./complete-automatic_format_identification.pdf +[ff-2]: ./extract-signature-pre-processing.pdf +[ff-3]: ./extract-signature-syntax.pdf diff --git a/signature-development-utility.go b/signature-development-utility.go index 9d7a2de..9055c6d 100644 --- a/signature-development-utility.go +++ b/signature-development-utility.go @@ -12,19 +12,21 @@ import ( "strconv" "strings" - "github.com/exponential-decay/signature-development-utility/pkg/sigdevutil" + "github.com/ffdev-info/signature-development-utility/pkg/sigdevutil" ) var ( - port string - https bool - cert string - key string + port string + bootsrapPort string + https bool + cert string + key string ) // Initialize the variables used for the flags func init() { flag.StringVar(&port, "port", "8080", "port to run the utility on") + flag.StringVar(&bootsrapPort, "bootstrap", "8000", "port to run the utility on") flag.BoolVar(&https, "https", false, "run a HTTPS server") flag.StringVar(&cert, "cert", "localhost.crt", "certificate file") flag.StringVar(&key, "key", "localhost.key", "private key file") @@ -64,7 +66,7 @@ func main() { // setHeaders will set the HTTP response headers for downloading of a file. func setHeaders(w http.ResponseWriter, contentLength int, signatureFileName string) { - const userAgent = "signature-development-utility/2.0 (https://github.com/exponential-decay/signature-development-utility; by @beet_keeper" + const userAgent = "signature-development-utility/2.0 (https://github.com/ffdev-info/signature-development-utility; by @beet_keeper" const headerUserAgent = "User-agent" const headerDisposition = "Content-Disposition" @@ -102,9 +104,10 @@ func signatureProcessing(writer http.ResponseWriter, request *http.Request, cont log.Printf("ParseForm() err: %#v", err) return } - log.Printf( + log.Printf("Signature submission, thank you!") + /* log.Printf( "Request from client (request.PostFrom): %#v\n", request.PostForm, - ) + ) */ if container { // Process the form values and return a container signature // file. @@ -139,8 +142,8 @@ func processStandardForm(form url.Values) (string, string) { droid := signatureFile.ToDROID(true) return droid.String(), signatureFile.GetFileName() } - droid := signatureFile.ToDROID(false) - return droid.String(), signatureFile.GetFileName() + droid := signatureFile.ToPHP(bootsrapPort) + return droid, signatureFile.GetFileName() } // processContainerForm will initiate the processing of a container diff --git a/static/about.htm b/static/about.htm index 9ce7620..50c0edd 100644 --- a/static/about.htm +++ b/static/about.htm @@ -1,54 +1,84 @@ - + + +
+
+ +
+
+ diff --git a/static/css/normalize.css b/static/css/normalize.css index 81c6f31..458eea1 100644 --- a/static/css/normalize.css +++ b/static/css/normalize.css @@ -424,4 +424,4 @@ table { td, th { padding: 0; -} \ No newline at end of file +} diff --git a/static/css/sf.css b/static/css/sf.css new file mode 100644 index 0000000..71ecd95 --- /dev/null +++ b/static/css/sf.css @@ -0,0 +1,28 @@ +/* Additional CSS */ + +textarea.results { + height: 600px; + font-family: 'Courier New', Courier, monospace; +} + +/* w3schools callout */ + +div.info { + margin-bottom: 0px; + padding: 0px 12px; +} + +.danger { + background-color: #ffdddd; + border-left: 6px solid #f44336; +} + +.success { + background-color: #ddffdd; + border-left: 6px solid #04AA6D; +} + +.info { + background-color: #222; + border-left: 6px solid #2196F3; +} diff --git a/static/css/signature-development.css b/static/css/signature-development.css index 7dd88fd..07fd846 100644 --- a/static/css/signature-development.css +++ b/static/css/signature-development.css @@ -49,9 +49,17 @@ input.signature { p.gh { font-family: courier; - font-color: black; + color: #d3cfc9; } +a { + color: #d3cfc9; +} a:visited { - color: black; + color: #d3cfc9; +} + +pre.nospace { + margin-bottom: 0px; + padding-bottom: 0px; } diff --git a/static/css/skeleton-tabs.css b/static/css/skeleton-tabs.css index ac2a8dc..8b0d1db 100644 --- a/static/css/skeleton-tabs.css +++ b/static/css/skeleton-tabs.css @@ -2,7 +2,7 @@ ul.tab-nav { list-style: none; - border-bottom: 1px solid #bbb; + border-bottom: 1px solid #3d4245; padding-left: 5px; } @@ -18,7 +18,7 @@ ul.tab-nav li a.button { } ul.tab-nav li a.active.button { - border-bottom: 0.175em solid #fff; + border-bottom: 0.175em solid #2b2f31; } .tab-content .tab-pane { @@ -30,5 +30,5 @@ ul.tab-nav li a.active.button { } .tab-color { - background-color: #dddddd; + background-color: #222; } diff --git a/static/css/skeleton.css b/static/css/skeleton.css index 7667488..c4abc17 100644 --- a/static/css/skeleton.css +++ b/static/css/skeleton.css @@ -31,7 +31,7 @@ .container { position: relative; width: 100%; - max-width: 750px; + max-width: 775px; margin: 0 auto; padding: 0 20px; box-sizing: border-box; } @@ -119,13 +119,14 @@ html is set to 62.5% so that all the REM measurements throughout Skeleton are based on 10px sizing. So basically 1.5rem = 15px :) */ html { - font-size: 62.5%; } + font-size: 58.5%; } body { font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */ line-height: 1.6; font-weight: 400; font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; - color: #222; } + background-color: #181a1b; + color: #d3cfc9; } /* Typography @@ -171,20 +172,20 @@ input[type="submit"], input[type="reset"], input[type="button"] { display: inline-block; - height: 38px; - padding: 0 30px; - color: #555; + height: 36px; + padding: 0 31px; + color: #d3cfc9; text-align: center; - font-size: 11px; + font-size: 10px; font-weight: 600; - line-height: 38px; + line-height: 36px; letter-spacing: .1rem; text-transform: uppercase; text-decoration: none; white-space: nowrap; background-color: transparent; border-radius: 4px; - border: 1px solid #bbb; + border: 1px solid #43494c; cursor: pointer; box-sizing: border-box; } .button:hover, @@ -197,17 +198,17 @@ button:focus, input[type="submit"]:focus, input[type="reset"]:focus, input[type="button"]:focus { - color: #333; - border-color: #888; + color: #bdc3bc; + border-color: #52585c; outline: 0; } .button.button-primary, button.button-primary, input[type="submit"].button-primary, input[type="reset"].button-primary, input[type="button"].button-primary { - color: #FFF; - background-color: #cc372f; - border-color: #cc372f; } + color: #e8e6e3; + background-color: #a32c26; + border-color: #a32c26; } .button.button-primary:hover, button.button-primary:hover, input[type="submit"].button-primary:hover, @@ -218,9 +219,9 @@ button.button-primary:focus, input[type="submit"].button-primary:focus, input[type="reset"].button-primary:focus, input[type="button"].button-primary:focus { - color: #FFF; - background-color: #dd372f; - border-color: #dd372f; } + color: #e8e6e3; + background-color: #a9221c; + border-color: #a9221c; } /* Forms @@ -236,8 +237,8 @@ textarea, select { height: 38px; padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */ - background-color: #fff; - border: 1px solid #D1D1D1; + background-color: #181a1b; + border: 1px solid #3d4245; border-radius: 4px; box-shadow: none; box-sizing: border-box; } @@ -311,8 +312,8 @@ code { margin: 0 .2rem; font-size: 90%; white-space: nowrap; - background: #F1F1F1; - border: 1px solid #E1E1E1; + background: #202324; + border: 1px solid #383d40; border-radius: 4px; } pre > code { display: block; @@ -377,7 +378,7 @@ hr { margin-top: 3rem; margin-bottom: 3.5rem; border-width: 0; - border-top: 1px solid #E1E1E1; } + border-top: 1px solid #3d4245; } /* Clearing diff --git a/static/guide.htm b/static/guide.htm index cee84e1..339cd86 100644 --- a/static/guide.htm +++ b/static/guide.htm @@ -1,146 +1,182 @@ - - + + +
+
+ +
+
+ diff --git a/static/index.html b/static/index.html index 73148bb..927a5c1 100644 --- a/static/index.html +++ b/static/index.html @@ -4,7 +4,7 @@ - Signature development utillity 2.0 + Signature development utility 2.0 @@ -43,7 +43,7 @@

Signature development utility: 2.0

-
+
+
+
- +
+
-

Standard signatures

+

Standard signatures

Enter your signatures and associated metadata below. Press create signature to save them out to a new signature file! @@ -152,7 +158,9 @@

Standard signatures

- For container signatures only? + + For container signatures? +


@@ -179,7 +187,7 @@

Standard signatures

+ +
+ +
+
-
+
+

+
-
diff --git a/static/js/clone.js b/static/js/clone.js index 21c0fb1..057af1d 100644 --- a/static/js/clone.js +++ b/static/js/clone.js @@ -15,6 +15,7 @@ $(document).ready(function() { cloneAndAddPath(); // Add static components to pages. + cloneAndAddTry() cloneAndAddGuide() cloneAndAddAbout() @@ -71,11 +72,6 @@ function disableRemove() { } } -// _makeElementName returns to us an element name nicely formatted. -function _makeElementName(elemName, idNo) { - return "#".concat(elemName, idNo) -} - // _setIDandName in a single bound. function _setIDAndName(elem, value) { elem.attr("name", value); @@ -86,15 +82,24 @@ function _setIDAndName(elem, value) { // append them to the DOM for submission via the DOM. function cloneAndAdd() { + // Standard form fields that we'll clone and repopulate. + const inputs = "#inputs" const maxInputs = 3; const clonedInput = ".cloned-input" - const cloned = "cloned-input-"; - const sig = "signature-input-"; - const rel = "signature-relativity-"; - const off = "offset-"; - const max = "max-offset-"; + const cloned = "#cloned-input-0"; + const newClonedID = "cloned-input-"; + const sig = "#signature-input-0"; + const rel = "#signature-relativity-0"; + const off = "#offset-0"; + const max = "#max-offset-0"; const disable = "disabled"; + // Element fields that we want to look for. + const idField = "id" + const placeholderField = "placeholder"; + const valueField = "value"; + const selectedAttr = "selected"; + // This loosely represents a PDF signature which means the utility // can also be used for demonstration purposes out of the box. placeholder = [ @@ -114,25 +119,24 @@ function cloneAndAdd() { len = $(clonedInput).length; newIDX = len++; - newID = "".concat(cloned, newIDX); - newSig = "".concat(sig, newIDX); - newRel = "".concat(rel, newIDX); - newOff = "".concat(off, newIDX); - newMax = "".concat(max, newIDX); + newID = "".concat(newClonedID, newIDX); + + clone = $(cloned).clone(); + clone.attr(idField, newID); - clone = $(_makeElementName(cloned, "0")).clone(); - clone.attr("id", newID); + signature = clone.find(sig); + relativity = clone.find(rel); + offset = clone.find(off); + maxOffset = clone.find(max); - signature = clone.find(_makeElementName(sig, "0")); - relativity = clone.find(_makeElementName(rel, "0")); - offset = clone.find(_makeElementName(off, "0")); - maxOffset = clone.find(_makeElementName(max, "0")); + signature.attr(placeholderField, placeholder[newIDX]); + signature.attr(valueField, placeholder[newIDX]); + relativity.val(anchor[newIDX]).attr(selectedAttr, selectedAttr); - signature.attr("placeholder", placeholder[newIDX]); - signature.attr("value", placeholder[newIDX]); - relativity.val(anchor[newIDX]).attr("selected", "selected"); + offset.val("0") + maxOffset.val("0") - clone.appendTo("#inputs"); + clone.appendTo(inputs); // Check whether or not we need to disable the append button. len = $(clonedInput).length; diff --git a/static/js/load-static.js b/static/js/load-static.js index e12219e..f042830 100644 --- a/static/js/load-static.js +++ b/static/js/load-static.js @@ -1,7 +1,19 @@ /* Load static pages dynamically */ +tryContainerSelector = "#try-container" guideContainerSelector = "#guide-container" -aboutContainerSelector = "#about-contaainer" +aboutContainerSelector = "#about-container" + +// cloneAndAddSequence ... +function cloneAndAddTry() { + fetch("./try.htm") + .then(response => { + return response.text() + }) + .then(data => { + $(data).insertAfter(tryContainerSelector) + }); +} // cloneAndAddSequence ... function cloneAndAddGuide() { diff --git a/static/js/variables.js b/static/js/variables.js index 6c9118e..b92db1f 100644 --- a/static/js/variables.js +++ b/static/js/variables.js @@ -87,7 +87,7 @@ var newSignatureSequences = `
+ + + + + + + + + diff --git a/static/wasm/example.js b/static/wasm/example.js new file mode 100644 index 0000000..8e5c898 --- /dev/null +++ b/static/wasm/example.js @@ -0,0 +1,59 @@ +function getArgs() { + const args = []; + var e = document.getElementById("format"); + var val = e.options[e.selectedIndex].value; + args.push(val); + e = document.getElementById("hash"); + val = e.options[e.selectedIndex].value; + if (val != "none") { + args.push(val); + } + val = document.querySelector('input[name="z"]:checked').value; + if (val == "true") { + args.push("z") + } + return args; +} + +window.onload = () => { + document.getElementById('butOpen').addEventListener('click', () => { + window.showOpenFilePicker().then(handles => { + for (const idx in handles) { + const args = getArgs(); + args.unshift(handles[idx]); + identify.apply(null, args).then(result => { + document.getElementById('results').value = result; + }).catch((err) => { + document.getElementById('results').value = err; + }); + }; + } + ); + }); + document.getElementById('butDirectory').addEventListener('click', () => { + window.showDirectoryPicker().then(handle => { + const args = getArgs(); + args.unshift(handle); + identify.apply(null, args).then(result => { + document.getElementById('results').value = result; + }).catch((err) => { + document.getElementById('results').value = err; + }); + } + ); + }); + document.getElementById('butRoy').addEventListener('click', () => { + window.showOpenFilePicker().then(handles => { + for (const idx in handles) { + const args = getArgs(); + args.unshift(handles[idx]); + sigload.apply(null, args).then(result => { + document.getElementById('results').value = result; + }).catch((err) => { + document.getElementById('results').value = err; + }); + }; + } + ); + }); +} diff --git a/static/wasm/sf.wasm b/static/wasm/sf.wasm new file mode 100755 index 0000000..fb4eb4d Binary files /dev/null and b/static/wasm/sf.wasm differ diff --git a/static/wasm/wasm_exec.js b/static/wasm/wasm_exec.js new file mode 100644 index 0000000..bc6f210 --- /dev/null +++ b/static/wasm/wasm_exec.js @@ -0,0 +1,561 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +"use strict"; + +(() => { + const enosys = () => { + const err = new Error("not implemented"); + err.code = "ENOSYS"; + return err; + }; + + if (!globalThis.fs) { + let outputBuf = ""; + globalThis.fs = { + constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused + writeSync(fd, buf) { + outputBuf += decoder.decode(buf); + const nl = outputBuf.lastIndexOf("\n"); + if (nl != -1) { + console.log(outputBuf.substring(0, nl)); + outputBuf = outputBuf.substring(nl + 1); + } + return buf.length; + }, + write(fd, buf, offset, length, position, callback) { + if (offset !== 0 || length !== buf.length || position !== null) { + callback(enosys()); + return; + } + const n = this.writeSync(fd, buf); + callback(null, n); + }, + chmod(path, mode, callback) { callback(enosys()); }, + chown(path, uid, gid, callback) { callback(enosys()); }, + close(fd, callback) { callback(enosys()); }, + fchmod(fd, mode, callback) { callback(enosys()); }, + fchown(fd, uid, gid, callback) { callback(enosys()); }, + fstat(fd, callback) { callback(enosys()); }, + fsync(fd, callback) { callback(null); }, + ftruncate(fd, length, callback) { callback(enosys()); }, + lchown(path, uid, gid, callback) { callback(enosys()); }, + link(path, link, callback) { callback(enosys()); }, + lstat(path, callback) { callback(enosys()); }, + mkdir(path, perm, callback) { callback(enosys()); }, + open(path, flags, mode, callback) { callback(enosys()); }, + read(fd, buffer, offset, length, position, callback) { callback(enosys()); }, + readdir(path, callback) { callback(enosys()); }, + readlink(path, callback) { callback(enosys()); }, + rename(from, to, callback) { callback(enosys()); }, + rmdir(path, callback) { callback(enosys()); }, + stat(path, callback) { callback(enosys()); }, + symlink(path, link, callback) { callback(enosys()); }, + truncate(path, length, callback) { callback(enosys()); }, + unlink(path, callback) { callback(enosys()); }, + utimes(path, atime, mtime, callback) { callback(enosys()); }, + }; + } + + if (!globalThis.process) { + globalThis.process = { + getuid() { return -1; }, + getgid() { return -1; }, + geteuid() { return -1; }, + getegid() { return -1; }, + getgroups() { throw enosys(); }, + pid: -1, + ppid: -1, + umask() { throw enosys(); }, + cwd() { throw enosys(); }, + chdir() { throw enosys(); }, + } + } + + if (!globalThis.crypto) { + throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)"); + } + + if (!globalThis.performance) { + throw new Error("globalThis.performance is not available, polyfill required (performance.now only)"); + } + + if (!globalThis.TextEncoder) { + throw new Error("globalThis.TextEncoder is not available, polyfill required"); + } + + if (!globalThis.TextDecoder) { + throw new Error("globalThis.TextDecoder is not available, polyfill required"); + } + + const encoder = new TextEncoder("utf-8"); + const decoder = new TextDecoder("utf-8"); + + globalThis.Go = class { + constructor() { + this.argv = ["js"]; + this.env = {}; + this.exit = (code) => { + if (code !== 0) { + console.warn("exit code:", code); + } + }; + this._exitPromise = new Promise((resolve) => { + this._resolveExitPromise = resolve; + }); + this._pendingEvent = null; + this._scheduledTimeouts = new Map(); + this._nextCallbackTimeoutID = 1; + + const setInt64 = (addr, v) => { + this.mem.setUint32(addr + 0, v, true); + this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true); + } + + const setInt32 = (addr, v) => { + this.mem.setUint32(addr + 0, v, true); + } + + const getInt64 = (addr) => { + const low = this.mem.getUint32(addr + 0, true); + const high = this.mem.getInt32(addr + 4, true); + return low + high * 4294967296; + } + + const loadValue = (addr) => { + const f = this.mem.getFloat64(addr, true); + if (f === 0) { + return undefined; + } + if (!isNaN(f)) { + return f; + } + + const id = this.mem.getUint32(addr, true); + return this._values[id]; + } + + const storeValue = (addr, v) => { + const nanHead = 0x7FF80000; + + if (typeof v === "number" && v !== 0) { + if (isNaN(v)) { + this.mem.setUint32(addr + 4, nanHead, true); + this.mem.setUint32(addr, 0, true); + return; + } + this.mem.setFloat64(addr, v, true); + return; + } + + if (v === undefined) { + this.mem.setFloat64(addr, 0, true); + return; + } + + let id = this._ids.get(v); + if (id === undefined) { + id = this._idPool.pop(); + if (id === undefined) { + id = this._values.length; + } + this._values[id] = v; + this._goRefCounts[id] = 0; + this._ids.set(v, id); + } + this._goRefCounts[id]++; + let typeFlag = 0; + switch (typeof v) { + case "object": + if (v !== null) { + typeFlag = 1; + } + break; + case "string": + typeFlag = 2; + break; + case "symbol": + typeFlag = 3; + break; + case "function": + typeFlag = 4; + break; + } + this.mem.setUint32(addr + 4, nanHead | typeFlag, true); + this.mem.setUint32(addr, id, true); + } + + const loadSlice = (addr) => { + const array = getInt64(addr + 0); + const len = getInt64(addr + 8); + return new Uint8Array(this._inst.exports.mem.buffer, array, len); + } + + const loadSliceOfValues = (addr) => { + const array = getInt64(addr + 0); + const len = getInt64(addr + 8); + const a = new Array(len); + for (let i = 0; i < len; i++) { + a[i] = loadValue(array + i * 8); + } + return a; + } + + const loadString = (addr) => { + const saddr = getInt64(addr + 0); + const len = getInt64(addr + 8); + return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); + } + + const timeOrigin = Date.now() - performance.now(); + this.importObject = { + _gotest: { + add: (a, b) => a + b, + }, + gojs: { + // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) + // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported + // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). + // This changes the SP, thus we have to update the SP used by the imported function. + + // func wasmExit(code int32) + "runtime.wasmExit": (sp) => { + sp >>>= 0; + const code = this.mem.getInt32(sp + 8, true); + this.exited = true; + delete this._inst; + delete this._values; + delete this._goRefCounts; + delete this._ids; + delete this._idPool; + this.exit(code); + }, + + // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) + "runtime.wasmWrite": (sp) => { + sp >>>= 0; + const fd = getInt64(sp + 8); + const p = getInt64(sp + 16); + const n = this.mem.getInt32(sp + 24, true); + fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); + }, + + // func resetMemoryDataView() + "runtime.resetMemoryDataView": (sp) => { + sp >>>= 0; + this.mem = new DataView(this._inst.exports.mem.buffer); + }, + + // func nanotime1() int64 + "runtime.nanotime1": (sp) => { + sp >>>= 0; + setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); + }, + + // func walltime() (sec int64, nsec int32) + "runtime.walltime": (sp) => { + sp >>>= 0; + const msec = (new Date).getTime(); + setInt64(sp + 8, msec / 1000); + this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); + }, + + // func scheduleTimeoutEvent(delay int64) int32 + "runtime.scheduleTimeoutEvent": (sp) => { + sp >>>= 0; + const id = this._nextCallbackTimeoutID; + this._nextCallbackTimeoutID++; + this._scheduledTimeouts.set(id, setTimeout( + () => { + this._resume(); + while (this._scheduledTimeouts.has(id)) { + // for some reason Go failed to register the timeout event, log and try again + // (temporary workaround for https://github.com/golang/go/issues/28975) + console.warn("scheduleTimeoutEvent: missed timeout event"); + this._resume(); + } + }, + getInt64(sp + 8), + )); + this.mem.setInt32(sp + 16, id, true); + }, + + // func clearTimeoutEvent(id int32) + "runtime.clearTimeoutEvent": (sp) => { + sp >>>= 0; + const id = this.mem.getInt32(sp + 8, true); + clearTimeout(this._scheduledTimeouts.get(id)); + this._scheduledTimeouts.delete(id); + }, + + // func getRandomData(r []byte) + "runtime.getRandomData": (sp) => { + sp >>>= 0; + crypto.getRandomValues(loadSlice(sp + 8)); + }, + + // func finalizeRef(v ref) + "syscall/js.finalizeRef": (sp) => { + sp >>>= 0; + const id = this.mem.getUint32(sp + 8, true); + this._goRefCounts[id]--; + if (this._goRefCounts[id] === 0) { + const v = this._values[id]; + this._values[id] = null; + this._ids.delete(v); + this._idPool.push(id); + } + }, + + // func stringVal(value string) ref + "syscall/js.stringVal": (sp) => { + sp >>>= 0; + storeValue(sp + 24, loadString(sp + 8)); + }, + + // func valueGet(v ref, p string) ref + "syscall/js.valueGet": (sp) => { + sp >>>= 0; + const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 32, result); + }, + + // func valueSet(v ref, p string, x ref) + "syscall/js.valueSet": (sp) => { + sp >>>= 0; + Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); + }, + + // func valueDelete(v ref, p string) + "syscall/js.valueDelete": (sp) => { + sp >>>= 0; + Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)); + }, + + // func valueIndex(v ref, i int) ref + "syscall/js.valueIndex": (sp) => { + sp >>>= 0; + storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); + }, + + // valueSetIndex(v ref, i int, x ref) + "syscall/js.valueSetIndex": (sp) => { + sp >>>= 0; + Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); + }, + + // func valueCall(v ref, m string, args []ref) (ref, bool) + "syscall/js.valueCall": (sp) => { + sp >>>= 0; + try { + const v = loadValue(sp + 8); + const m = Reflect.get(v, loadString(sp + 16)); + const args = loadSliceOfValues(sp + 32); + const result = Reflect.apply(m, v, args); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 56, result); + this.mem.setUint8(sp + 64, 1); + } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 56, err); + this.mem.setUint8(sp + 64, 0); + } + }, + + // func valueInvoke(v ref, args []ref) (ref, bool) + "syscall/js.valueInvoke": (sp) => { + sp >>>= 0; + try { + const v = loadValue(sp + 8); + const args = loadSliceOfValues(sp + 16); + const result = Reflect.apply(v, undefined, args); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, result); + this.mem.setUint8(sp + 48, 1); + } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, err); + this.mem.setUint8(sp + 48, 0); + } + }, + + // func valueNew(v ref, args []ref) (ref, bool) + "syscall/js.valueNew": (sp) => { + sp >>>= 0; + try { + const v = loadValue(sp + 8); + const args = loadSliceOfValues(sp + 16); + const result = Reflect.construct(v, args); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, result); + this.mem.setUint8(sp + 48, 1); + } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, err); + this.mem.setUint8(sp + 48, 0); + } + }, + + // func valueLength(v ref) int + "syscall/js.valueLength": (sp) => { + sp >>>= 0; + setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); + }, + + // valuePrepareString(v ref) (ref, int) + "syscall/js.valuePrepareString": (sp) => { + sp >>>= 0; + const str = encoder.encode(String(loadValue(sp + 8))); + storeValue(sp + 16, str); + setInt64(sp + 24, str.length); + }, + + // valueLoadString(v ref, b []byte) + "syscall/js.valueLoadString": (sp) => { + sp >>>= 0; + const str = loadValue(sp + 8); + loadSlice(sp + 16).set(str); + }, + + // func valueInstanceOf(v ref, t ref) bool + "syscall/js.valueInstanceOf": (sp) => { + sp >>>= 0; + this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0); + }, + + // func copyBytesToGo(dst []byte, src ref) (int, bool) + "syscall/js.copyBytesToGo": (sp) => { + sp >>>= 0; + const dst = loadSlice(sp + 8); + const src = loadValue(sp + 32); + if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { + this.mem.setUint8(sp + 48, 0); + return; + } + const toCopy = src.subarray(0, dst.length); + dst.set(toCopy); + setInt64(sp + 40, toCopy.length); + this.mem.setUint8(sp + 48, 1); + }, + + // func copyBytesToJS(dst ref, src []byte) (int, bool) + "syscall/js.copyBytesToJS": (sp) => { + sp >>>= 0; + const dst = loadValue(sp + 8); + const src = loadSlice(sp + 16); + if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { + this.mem.setUint8(sp + 48, 0); + return; + } + const toCopy = src.subarray(0, dst.length); + dst.set(toCopy); + setInt64(sp + 40, toCopy.length); + this.mem.setUint8(sp + 48, 1); + }, + + "debug": (value) => { + console.log(value); + }, + } + }; + } + + async run(instance) { + if (!(instance instanceof WebAssembly.Instance)) { + throw new Error("Go.run: WebAssembly.Instance expected"); + } + this._inst = instance; + this.mem = new DataView(this._inst.exports.mem.buffer); + this._values = [ // JS values that Go currently has references to, indexed by reference id + NaN, + 0, + null, + true, + false, + globalThis, + this, + ]; + this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id + this._ids = new Map([ // mapping from JS values to reference ids + [0, 1], + [null, 2], + [true, 3], + [false, 4], + [globalThis, 5], + [this, 6], + ]); + this._idPool = []; // unused ids that have been garbage collected + this.exited = false; // whether the Go program has exited + + // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. + let offset = 4096; + + const strPtr = (str) => { + const ptr = offset; + const bytes = encoder.encode(str + "\0"); + new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes); + offset += bytes.length; + if (offset % 8 !== 0) { + offset += 8 - (offset % 8); + } + return ptr; + }; + + const argc = this.argv.length; + + const argvPtrs = []; + this.argv.forEach((arg) => { + argvPtrs.push(strPtr(arg)); + }); + argvPtrs.push(0); + + const keys = Object.keys(this.env).sort(); + keys.forEach((key) => { + argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); + }); + argvPtrs.push(0); + + const argv = offset; + argvPtrs.forEach((ptr) => { + this.mem.setUint32(offset, ptr, true); + this.mem.setUint32(offset + 4, 0, true); + offset += 8; + }); + + // The linker guarantees global data starts from at least wasmMinDataAddr. + // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr. + const wasmMinDataAddr = 4096 + 8192; + if (offset >= wasmMinDataAddr) { + throw new Error("total length of command line and environment variables exceeds limit"); + } + + this._inst.exports.run(argc, argv); + if (this.exited) { + this._resolveExitPromise(); + } + await this._exitPromise; + } + + _resume() { + if (this.exited) { + throw new Error("Go program has already exited"); + } + this._inst.exports.resume(); + if (this.exited) { + this._resolveExitPromise(); + } + } + + _makeFuncWrapper(id) { + const go = this; + return function () { + const event = { id: id, this: this, args: arguments }; + go._pendingEvent = event; + go._resume(); + return event.result; + }; + } + } +})();