summaryrefslogtreecommitdiff
path: root/csprdg.sh
diff options
context:
space:
mode:
Diffstat (limited to 'csprdg.sh')
-rwxr-xr-xcsprdg.sh228
1 files changed, 228 insertions, 0 deletions
diff --git a/csprdg.sh b/csprdg.sh
new file mode 100755
index 0000000..81f880e
--- /dev/null
+++ b/csprdg.sh
@@ -0,0 +1,228 @@
+#!/bin/sh
+#
+# Conjecturally Secure Pseudo-Random Document Generator
+#
+# Copyright (c) 2026 Doan Luu Duc Tai doan@luuduct.ai
+# SPDX-License-Identifier: 0BSD OR Apache-2.0
+#
+# This script is dual-licensed under the Zero-Clause BSD License (0BSD)
+# OR the Apache License, Version 2.0 (Apache-2.0), at your option.
+# See the LICENSE file for the full text of both licenses.
+
+OUTPUT_FILE="document.odt"
+
+SCRIPT_DIR=$(dirname "$0")
+WORDLIST="$SCRIPT_DIR/words.txt"
+
+if [ ! -r "$WORDLIST" ]; then
+ printf "Error: The required word list was not found or is not readable at:\n" >&2
+ printf " %s\n" "$WORDLIST" >&2
+ printf "Please ensure 'words.txt' is placed in the same folder as this script.\n" >&2
+ exit 1
+fi
+
+if ! command -v zip >/dev/null 2>&1; then
+ printf "Error: The 'zip' utility is required but was not found in PATH.\n" >&2
+ exit 1
+fi
+
+TMP_DIR="./odt_tmp_$$"
+mkdir -p "$TMP_DIR"
+
+cleanup() {
+ rm -rf "$TMP_DIR"
+}
+trap cleanup 0 INT TERM
+
+mkdir -p "$TMP_DIR/META-INF"
+
+printf "application/vnd.oasis.opendocument.text" >"$TMP_DIR/mimetype"
+
+cat <<'EOF' >"$TMP_DIR/META-INF/manifest.xml"
+<?xml version="1.0" encoding="UTF-8"?>
+<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" manifest:version="1.2">
+ <manifest:file-entry manifest:full-path="/" manifest:version="1.2" manifest:media-type="application/vnd.oasis.opendocument.text"/>
+ <manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/>
+ <manifest:file-entry manifest:full-path="styles.xml" manifest:media-type="text/xml"/>
+ <manifest:file-entry manifest:full-path="meta.xml" manifest:media-type="text/xml"/>
+ <manifest:file-entry manifest:full-path="settings.xml" manifest:media-type="text/xml"/>
+</manifest:manifest>
+EOF
+
+cat <<'EOF' >"$TMP_DIR/styles.xml"
+<?xml version="1.0" encoding="UTF-8"?>
+<office:document-styles xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" office:version="1.2">
+ <office:styles>
+ <style:default-style style:family="paragraph"/>
+ </office:styles>
+ <office:automatic-styles/>
+ <office:master-styles>
+ <style:master-page style:name="Standard"/>
+ </office:master-styles>
+</office:document-styles>
+EOF
+
+cat <<'EOF' >"$TMP_DIR/meta.xml"
+<?xml version="1.0" encoding="UTF-8"?>
+<office:document-meta xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" office:version="1.2">
+ <office:meta>
+ <meta:generator>POSIX Shell ODT Generator</meta:generator>
+ </office:meta>
+</office:document-meta>
+EOF
+
+cat <<'EOF' >"$TMP_DIR/settings.xml"
+<?xml version="1.0" encoding="UTF-8"?>
+<office:document-settings xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.2">
+ <office:settings/>
+</office:document-settings>
+EOF
+
+SEED=$( (
+ date
+ times 2>/dev/null
+ ls -l /tmp 2>/dev/null
+ env 2>/dev/null
+) | cksum | awk '{print $1}')
+
+case "$SEED" in
+"" | *[!0-9]*) SEED=123456 ;;
+esac
+
+awk -v seed="$SEED" -v stats_file="$TMP_DIR/stats" '
+BEGIN {
+ srand(seed);
+ word_count = 0;
+
+ pages = int(50 + rand() * 31);
+
+ min_words = pages * 200;
+ if (min_words < 10000) {
+ min_words = 10000;
+ }
+ total_words = min_words + int(rand() * 5001);
+}
+
+/^[[:alpha:]]+$/ {
+ if (word_count < 50000) {
+ words[word_count] = $0;
+ word_count++;
+ }
+}
+
+END {
+ if (word_count == 0) {
+ print "ERROR=\"No usable words found in the system wordlist.\"" > stats_file;
+ exit 1;
+ }
+
+ print "PAGES=" pages > stats_file;
+ print "WORDS=" total_words > stats_file;
+
+ print "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
+ print "<office:document-content xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\" xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\" xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\" xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\" office:version=\"1.2\">";
+ print " <office:automatic-styles>";
+ print " <style:style style:name=\"PB\" style:family=\"paragraph\">";
+ print " <style:paragraph-properties fo:break-before=\"page\"/>";
+ print " </style:style>";
+ print " </office:automatic-styles>";
+ print " <office:body>";
+ print " <office:text>";
+
+ w_per_page = int(total_words / pages);
+ words_left = total_words;
+
+ for (p = 1; p <= pages; p++) {
+ if (p == pages) {
+ p_words = words_left;
+ } else {
+ variation = int(w_per_page * 0.2);
+ if (variation < 5) {
+ variation = 5;
+ }
+ p_words = w_per_page + int(rand() * (variation * 2 + 1)) - variation;
+ if (p_words > words_left) {
+ p_words = words_left;
+ }
+ if (p_words < 10) {
+ p_words = 10;
+ }
+ }
+ words_left -= p_words;
+
+ if (p > 1) {
+ is_first_p = 1;
+ } else {
+ is_first_p = 0;
+ }
+ while (p_words > 0) {
+ p_len = int(25 + rand() * 36);
+ if (p_len > p_words) {
+ p_len = p_words;
+ }
+ p_words -= p_len;
+
+ if (is_first_p) {
+ printf " <text:p text:style-name=\"PB\">";
+ is_first_p = 0;
+ } else {
+ printf " <text:p>";
+ }
+
+ for (w = 0; w < p_len; w++) {
+ rnd_idx = int(rand() * word_count);
+ word = words[rnd_idx];
+
+ if (w == 0) {
+ word = toupper(substr(word, 1, 1)) substr(word, 2);
+ } else {
+ word = tolower(word);
+ }
+
+ printf "%s", word;
+ if (w == p_len - 1) {
+ printf(".");
+ } else {
+ printf(" ");
+ }
+ }
+ print "</text:p>";
+ }
+ }
+
+ print " </office:text>";
+ print " </office:body>";
+ print "</office:document-content>";
+}
+' "$WORDLIST" >"$TMP_DIR/content.xml"
+
+if [ -f "$TMP_DIR/stats" ]; then
+ . "$TMP_DIR/stats"
+fi
+
+if [ -n "$ERROR" ]; then
+ printf "Error: %s\n" "$ERROR" >&2
+ exit 1
+fi
+
+case "$OUTPUT_FILE" in
+/*) ABS_OUTPUT="$OUTPUT_FILE" ;;
+*) ABS_OUTPUT="$(pwd)/$OUTPUT_FILE" ;;
+esac
+
+rm -f "$ABS_OUTPUT"
+
+(
+ cd "$TMP_DIR" || exit 1
+ zip -X -r -n mimetype "$ABS_OUTPUT" mimetype META-INF content.xml styles.xml meta.xml settings.xml >/dev/null
+)
+
+if [ $? -eq 0 ]; then
+ printf "Document successfully created: %s\n" "$OUTPUT_FILE"
+ printf "Generated Details:\n"
+ printf " - Pages: %s\n" "$PAGES"
+ printf " - Words: %s\n" "$WORDS"
+else
+ printf "Error: Failed to package ODT using zip.\n" >&2
+ exit 1
+fi