From 55274429cee12e7a0c66f87fffcc98560a8da0e1 Mon Sep 17 00:00:00 2001 From: Mikhail Novosyolov Date: Tue, 18 Dec 2018 19:42:30 +0300 Subject: [PATCH 01/18] Dynamically get path to bash bash is not /bin/bash on some systems, especially BSD OSes --- bashlib.in | 2 +- configure.in | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bashlib.in b/bashlib.in index bcdae1c..74df6d7 100644 --- a/bashlib.in +++ b/bashlib.in @@ -1,4 +1,4 @@ -#!/bin/bash +#!@BASH@ # Author: darren chamberlain # Co-Author: Paul Bournival diff --git a/configure.in b/configure.in index a0ce2f1..4b74b94 100644 --- a/configure.in +++ b/configure.in @@ -28,6 +28,7 @@ bashlib_version=0.5 AC_INIT(bashlib.in) +AC_PATH_PROG(BASH, bash, "") AC_PATH_PROG(AWK, awk, "") AC_PATH_PROG(CAT, cat, "") AC_PATH_PROG(CUT, cut, "") From fdf18135cfcee3de31f289423fff406712899d35 Mon Sep 17 00:00:00 2001 From: Mikhail Novosyolov Date: Wed, 19 Dec 2018 17:22:17 +0300 Subject: [PATCH 02/18] Fix function definitions (did not work with Bash 4) --- bashlib.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bashlib.in b/bashlib.in index 74df6d7..6e2c250 100644 --- a/bashlib.in +++ b/bashlib.in @@ -153,11 +153,11 @@ fi # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- # # Shameless plug, advertises verion. -version { +version() { echo "bashlib, version ${VERSION}" } -version_html { +version_html() { echo -n "bashlib," echo "version ${VERSION}" } From 16ddf20f20df131e12046a443bdea7051ea559b1 Mon Sep 17 00:00:00 2001 From: Mikhail Novosyolov Date: Mon, 29 Apr 2019 00:38:35 +0300 Subject: [PATCH 03/18] Add examples --- examples/bashlib.sourceforge.net_backup.html | 111 +++++++++++++++++++ examples/promo-codes.sh | 52 +++++++++ 2 files changed, 163 insertions(+) create mode 100644 examples/bashlib.sourceforge.net_backup.html create mode 100644 examples/promo-codes.sh diff --git a/examples/bashlib.sourceforge.net_backup.html b/examples/bashlib.sourceforge.net_backup.html new file mode 100644 index 0000000..8642fd7 --- /dev/null +++ b/examples/bashlib.sourceforge.net_backup.html @@ -0,0 +1,111 @@ + + + bashlib - CGI programming with the bash shell + + +
+

bashlib - CGI programming with the bash shell

+ + [ Project Page + | Why? + | bashlib news + | How it came about + | Using bashlib ]
+ + [ CVs eXtender + | Something you don't like? + | Getting bashlib + | Other Resources + | Comments? ] +
+
+

Why?

+

bashlib is a shell script that makes CGI programming in the bash shell easier, or at least more tolerable. It contains a few functions that get called automatically and place form elements (from POSTs and GETs) and cookies in your environment. It also contains complete documentation on how to use these variables and how to set cookies manually.

+

Recent bashlib News

+

The most recent version of bashlib will always be available from http://bashlib.sourceforge.net/src/bashlib-current.tar.gz and will + be browsable at http://bashlib.sourceforge.net/src/bashlib-current/.

+

Version 0.4 (download or browse) of bashlib, released March 12, 2002, is fully autoconfiscated. To install it, run ./configure as usual. If you check out the CVS version, you'll need autoconf installed to create the configure script from configure.in.

+

I released version 0.3 (download or browse)of bashlib on February 21, 2001. The only difference between 0.3 and 0.2 is that the tarball works. I've been testing it for a while, and it definitely seems stable enough for real use. The interface has changed a little; the sample below reflects these changes.

+

How this came about, and what it is useful for

+

Things such as this are born of necessity. My ISP, while being incredibly useful in most ways, doesn't have a recent version of Perl available for their casual users (e.g., dial-up accounts, with web pages in http://www.isp.net/~mylogin/) -- Perl is a "value-added" service. This was a great source of frustration for me, since I am a Perl programmer by day. So, I began rooting (not literally) around for other methods of writing CGI scripts, and realized that the shell, which I spent most of my time using, was not only a great generaly purpose scripting environment, but also a good one for CGI scripts. This naturally led me to forms and cookies, and here we are today.

+

No, I really don't have this much free time. The current release is the result of about a days worth of work (on and off throughout the day), with some help. Well, that's not quite true -- 9 years of using Unix went into it as well.

+

Using bashlib

+

Using bashlib is pretty straight-forward. More important, however, is knowing what to do with the variables once they come into your script and knowing how to write CGI scripts. (This script is not running here, for obvious reasons.)

+
+#!/bin/bash
+
+# this sources bashlib into your current environment
+. /usr/local/lib/bashlib
+
+echo "Content-type: text/html"
+echo ""
+
+# OK, so we've sent the header... now send some content
+echo "<html><title>Crack This Server</title><body>"
+
+# print a "hello" if the username is filled out
+username=`param username`
+if [ -n "x$username" != "x" ] ; then
+    echo "<h1>Hello, $username</h1>
+fi
+
+echo "<h2>Users on `/bin/hostname`</h2>"
+echo "<ul>"
+
+# for each user in the passwd file, print their login and full name
+# bold them if they are the current user
+for user in $(cat /etc/passwd | awk -F: '{print $1 "\t" $5}') ; do
+    echo "<li>"
+    if [ "$username" = "$user" ] ; then
+        echo "<strong>$user</strong>"
+    else
+        echo "$user"
+    fi
+    echo "</li>"
+done
+echo "</ul>"
+echo "</body></html>"
+  
+ + + +

Other Resoruces

+
    +
  • I recommend checking out bashish if you use bash often; it is dedicated to the configuration of bash. Although it isn't helpful for CGI programming in bash, it makes day to day usage of bash quite nice. To quote:
  • +
    +Bashish is a theme engine for the console.
    +It lets you customize title, prompt, background, foreground, colors, font and a lot of other things.
    +Bashish is also very configurable, you can turn on and off nearly all features.
    +
    +
  • ^txt2regex$ is a Regular Expression "wizard", all written with bash2 builtins, that converts human sentences to RegExs. with a simple interface, you just answer to questions and build your own RegEx for a large variety of programs, like awk, ed, emacs, grep, perl, php, procmail, python, sed and vim. there are more than 20 supported programs. it's bash so download and run, no compilation needed.
  • +
  • For folks who do a lot of command-line-based web work, surfaw might be useful:
  • +
    + Surfraw (Shell Users' Revolutionary Front Rage Against the Web) provides a Unix command line interface to a variety of popular Web search engines and sites, including Google, Altavista, Babelfish, Raging, DejaNews, Research Index, Yahoo!, WeatherNews, Slashdot, freshmeat, and many others. +
    +
  • The BASH Programming - Introduction HOW-TO and Advanced Bash-Scripting HOWTO: A guide to shell scripting, using Bash are very, very useful.
  • +
  • I just came across Prentice Hall's 1996 book Portable Shell Programming, and it is wonderful. All the examples are pure Bourne shell, so they are (naturally) portable, and also very informative.
  • +
  • And, finally, while it's not free, David Tansley's Linux & Unix Shell Programming is a wonderful resource (get it from fatbrain).
  • +
+ + + +

Something you don't like?

+

OK, so there's probably something in here you think should be different, could be better, etc. Well, drop me a line (email me at dlc@users.sourceforge.net) and let me know. You can write it out in words ("bashlib should uuencode GIFs on the fly"), provide a patch (via diff -u), or rewrite the whole thing (try to keep it in shell, though). All reasonable emails will be read, and probably answered as well.

+ + + +

Getting bashlib

+

bashlib is a pretty short script (as libraries go), and all versions can be browsed here. It is available for download from Sourceforge at http.

+

bashlib is also available from anonymous CVS. The CVS repository can be checked out through anonymous (pserver) CVS. When prompted for a password for anonymous, simply press the Enter key.

+
+cvs -d:pserver:anonymous@cvs.bashlib.sourceforge.net:/cvsroot/bashlib login 
+cvs -z3 -d:pserver:anonymous@cvs.bashlib.sourceforge.net:/cvsroot/bashlib co bashlib
+
+
+ SourceForge Logo +
+ diff --git a/examples/promo-codes.sh b/examples/promo-codes.sh new file mode 100644 index 0000000..781c5bd --- /dev/null +++ b/examples/promo-codes.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# CGI script +# Does not work with FastCGI (fcgi) because it requires additional communication, see https://unix.stackexchange.com/a/241694 +set -e +set -f +#set -u # BAH01215: ./bashlib: line 82: value: unbound variable + +# https://stackoverflow.com/questions/3919755/how-to-parse-query-string-from-a-bash-cgi-script + +# source bashlib https://github.com/mikhailnov/bashlib +. ./bashlib 2>/dev/null || . bashlib || ( echo "Failed to source bashlib!" ; exit 1 ) + +# get file with promo codes +for file in './promo_codes_1.txt' '/var/www/domain.tld/promo_codes_1.txt' +do + [ -f "$file" ] && file_codes="$file" && break +done + +do_redirect_back(){ +code_error_type="${code_error_type:-неверный}" +echo -n " + +
+Ошибка: вы ввели ${code_error_type} промо-код! +
+
+
" +} + +do_redirect_forward(){ +redir_URL='https://domain.tld/page2' +echo -n " + + + + + +Вы будете перенаправлены на страницу записи. Если она не открылась, перейдите по ссылке. +" +} + +write_used_code(){ + # used codes will be converted to empty lines + # TODO: check if this file is writable and redirect back in case of error + sed -e "s/${code}//g" -i "$file_codes" +} + +code="$(param code)" +if grep -q "$code" "$file_codes" + then write_used_code && do_redirect_forward # don't redirect if failed to write_used_code + else do_redirect_back +fi From 158390f6eb5355e2a978966623cfbe06c5323d09 Mon Sep 17 00:00:00 2001 From: Mikhail Novosyolov Date: Sat, 27 Jun 2020 13:21:04 +0300 Subject: [PATCH 04/18] Do not confuse emails which names start with the same characters (e.g. email and emails) --- bashlib.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bashlib.in b/bashlib.in index 6e2c250..39fddc0 100644 --- a/bashlib.in +++ b/bashlib.in @@ -20,7 +20,7 @@ PATH=/bin:/usr/bin # # Set version number # -VERSION="0.05" +VERSION="0.06" # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- # Initialization stuff begins here. These things run immediately, and @@ -178,7 +178,7 @@ param() { if [ $# -eq 1 ]; then name=$1 name=$(echo ${name} | @SED@ -e 's/FORM_//') - value=$(@ENV@ | @GREP@ "^FORM_${name}" | @SED@ -e 's/FORM_//' | @CUT@ -d= -f2-) + value=$(@ENV@ | @GREP@ "^FORM_${name}=" | @SED@ -e 's/FORM_//' | @CUT@ -d= -f2-) elif [ $# -gt 1 ]; then name=$1 shift From 582b4171ea7865404115b08d741e0cc8d361d0e5 Mon Sep 17 00:00:00 2001 From: Mikhail Novosyolov Date: Wed, 20 Jan 2021 19:35:35 +0300 Subject: [PATCH 05/18] Do not loose empty space from params --- bashlib.in | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bashlib.in b/bashlib.in index 39fddc0..b0c0ed0 100644 --- a/bashlib.in +++ b/bashlib.in @@ -186,7 +186,9 @@ param() { else value=$(@ENV@ | @GREP@ '^FORM_' | @SED@ -e 's/FORM_//' | @CUT@ -d= -f1) fi - echo ${value} + # "+" is URL-encoded as "%2B", web server replaces spaces with "+", replace back, + # otherwise safe_param() just removes the "+" sign and looses the space. + echo "${value}" | @SED@ -e 's/+/ /' unset name unset value } From 8a5cc81ddc6780363b3fa0c6ecae2119b8555bd2 Mon Sep 17 00:00:00 2001 From: Mikhail Novosyolov Date: Mon, 21 Feb 2022 18:58:35 +0300 Subject: [PATCH 06/18] Bump version, enable integer comparison of versions if needed, some clean up --- bashlib.in | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/bashlib.in b/bashlib.in index b0c0ed0..6ee50e8 100644 --- a/bashlib.in +++ b/bashlib.in @@ -2,16 +2,9 @@ # Author: darren chamberlain # Co-Author: Paul Bournival +# Co-Author: Mikhail Novosyolov # -####### -# Updated Oct 15 2004 by Tony Clayton -# * add safe_param() function with XSS and shell-invocation prevention -# * add extra "| tr -d '$`'" sanity check to name decoding to prevent shell -# invocation of param names. -# * ported function defs to be bash/ash compatible -####### - # bashlib is used by sourcing it at the beginning of scripts that # needs its functionality (by using the . or source commands). @@ -19,8 +12,9 @@ PATH=/bin:/usr/bin # # Set version number +# Must be an integer because bash cannot compare float numbers # -VERSION="0.06" +VERSION="2" # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- # Initialization stuff begins here. These things run immediately, and From fc8ef054d417dece50242dbbb6acd8fca0ffa4f4 Mon Sep 17 00:00:00 2001 From: Mikhail Novosyolov Date: Tue, 16 Apr 2024 16:00:25 +0300 Subject: [PATCH 07/18] upd copyright --- configure.in | 1 + 1 file changed, 1 insertion(+) diff --git a/configure.in b/configure.in index 4b74b94..68c2783 100644 --- a/configure.in +++ b/configure.in @@ -4,6 +4,7 @@ dnl $Id$ dnl ---------------------------------------------------------------------- dnl bashlib dnl Copyright (C) 2002-2005 darren chamberlain +dnl Copyright (C) 2018-2024 Mikhail Novosyolov dnl dnl This program is free software; you can redistribute it and/or modify dnl it under the terms of the GNU General Public License as published by From 9f7c0932d1f5a727003a9876be219c155692ab07 Mon Sep 17 00:00:00 2001 From: Mikhail Novosyolov Date: Tue, 16 Apr 2024 16:00:52 +0300 Subject: [PATCH 08/18] use bash built-in printf and echo to speed up things, no need in external binaries --- bashlib.in | 22 +++++++++++----------- configure.in | 2 -- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/bashlib.in b/bashlib.in index 6ee50e8..3caff41 100644 --- a/bashlib.in +++ b/bashlib.in @@ -36,7 +36,7 @@ fi if [ -n "${QUERY_STRING}" ]; then # name=value params, separated by either '&' or ';' if echo ${QUERY_STRING} | grep '=' >/dev/null ; then - for Q in $(@ECHO@ ${QUERY_STRING} | @TR@ ";&" "\012") ; do + for Q in $(echo ${QUERY_STRING} | @TR@ ";&" "\012") ; do # # Clear our local variables # @@ -48,12 +48,12 @@ if [ -n "${QUERY_STRING}" ]; then # get the name of the key, and decode it # name=${Q%%=*} - name=$(@ECHO@ ${name} | \ + name=$(echo ${name} | \ @SED@ -e 's/%\(\)/\\\x/g' | \ @TR@ "+" " ") - name=$(@ECHO@ ${name} | \ + name=$(echo ${name} | \ @TR@ -d ".-") - name=$(@PRINTF@ ${name} | @TR@ -d '$`') + name=$(printf ${name} | @TR@ -d '$`') # # get the value and decode it. This is tricky... printf chokes on @@ -63,7 +63,7 @@ if [ -n "${QUERY_STRING}" ]; then # printf, and then remove it. # tmpvalue=${Q#*=} - tmpvalue=$(@ECHO@ ${tmpvalue} | \ + tmpvalue=$(echo ${tmpvalue} | \ @SED@ -e 's/%\(..\)/\\\x\1 /g') #echo "Intermediate \$value: ${tmpvalue}" 1>&2 @@ -72,7 +72,7 @@ if [ -n "${QUERY_STRING}" ]; then # value # for i in ${tmpvalue}; do - g=$(@PRINTF@ ${i}) + g=$(printf ${i}) value="${value}${g}" done #value=$(echo ${value}) @@ -110,17 +110,17 @@ if [ -n "${HTTP_COOKIE}" ]; then # get the name of the key, and decode it # name=${Q%%=*} - name=$(@ECHO@ ${name} | \ + name=$(echo ${name} | \ @SED@ -e 's/%\(\)/\\\x/g' | \ @TR@ "+" " ") - name=$(@ECHO@ ${name} | \ + name=$(echo ${name} | \ @TR@ -d ".-") - name=$(@PRINTF@ ${name}) + name=$(printf ${name}) # Decode the cookie value. See the parameter section above for # an explanation of what this is doing. tmpvalue=${Q#*=} - tmpvalue=$(@ECHO@ ${tmpvalue} | \ + tmpvalue=$(echo ${tmpvalue} | \ @SED@ -e 's/%\(..\)/\\\x\1 /g') #echo "Intermediate \$value: ${tmpvalue}" 1>&2 @@ -129,7 +129,7 @@ if [ -n "${HTTP_COOKIE}" ]; then # value # for i in ${tmpvalue}; do - g=$(@PRINTF@ ${i}) + g=$(printf ${i}) value="${value}${g}" done #value=$(echo ${value}) diff --git a/configure.in b/configure.in index 68c2783..4d2bb24 100644 --- a/configure.in +++ b/configure.in @@ -34,13 +34,11 @@ AC_PATH_PROG(AWK, awk, "") AC_PATH_PROG(CAT, cat, "") AC_PATH_PROG(CUT, cut, "") AC_PATH_PROG(CP, cp, "") -AC_PATH_PROG(ECHO, echo, "") AC_PATH_PROG(ENV, env, "") AC_PATH_PROG(GREP, grep, "") AC_PATH_PROG(GZIP, gzip, "") AC_PATH_PROG(INSTALL, install, "") AC_PATH_PROG(MKDIR, mkdir, "") -AC_PATH_PROG(PRINTF, printf, "") AC_PATH_PROG(RM, rm, "") AC_PATH_PROG(SED, sed, "") AC_PATH_PROG(TAR, tar, "") From 4b2328b1a8e2dea8756da8df5c050fb41b388760 Mon Sep 17 00:00:00 2001 From: Mikhail Novosyolov Date: Tue, 16 Apr 2024 16:24:58 +0300 Subject: [PATCH 09/18] add another simple example --- examples/write-to-file.sh | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100755 examples/write-to-file.sh diff --git a/examples/write-to-file.sh b/examples/write-to-file.sh new file mode 100755 index 0000000..d76ebf0 --- /dev/null +++ b/examples/write-to-file.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +# Example request: +# http://localhost/cgi-bin/r8168.cgi?rev=0x00&probe=b0cdd5070e + +# https://github.com/mikhailnov/bashlib, https://abf.io/import/bashlib +. bashlib ||{ echo "Failed to source bashlib!" ; exit 1 ;} + +readonly file="/var/www/r8168.list" +readonly lock="/var/www/r8168.lock" +readonly rev="$(safe_param rev)" +readonly probe="$(safe_param probe)" + +# possible values: 0x15, 0x09 +if ! [[ "$rev" =~ ^0x..$ ]]; then + echo "Status: 400 Bad request" + echo "Content-Type: text/plain; charset=utf-8" + echo "" + echo "Incorrect value of rev" + exit 1 +fi + +if [ ${#probe} -gt 20 ] || ! [[ "$probe" =~ ^[a-zA-Z0-9]+$ ]]; then + echo "Status: 400 Bad request" + echo "Content-Type: text/plain; charset=utf-8" + echo "" + echo "Incorrect value of probe" + exit 1 +fi + +set -e +echo "$rev;$probe" | flock "$lock" tee -a "$file" >/dev/null +echo "Status: 200 OK" +echo "Content-Type: text/plain; charset=utf-8" +echo "" +echo "OK" From b50f6187701ad409996762bb442cefc5acc3cf33 Mon Sep 17 00:00:00 2001 From: Mikhail Novosyolov Date: Sun, 6 Sep 2026 15:51:37 +0300 Subject: [PATCH 10/18] Add a self-contained test suite for bashlib functions run_tests.sh covers GET/POST/cookie parsing done at source time, param(), safe_param(), cookie(), set_cookie(), keywords(), send_redirect(), version(). Each case runs in a pristine env -i with a controlled CGI environment; stdout is compared byte-for-byte. Wired up as "make check". Co-authored-by: Z.AI GLM --- Makefile.in | 2 + run_tests.sh | 202 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+) create mode 100755 run_tests.sh diff --git a/Makefile.in b/Makefile.in index be4c7ac..26a2f32 100644 --- a/Makefile.in +++ b/Makefile.in @@ -28,6 +28,8 @@ all: install: @INSTALL@ bashlib $(PREFIX)/bin +check: + ./run_tests.sh dist: @MKDIR@ bashlib-$(VERSION) diff --git a/run_tests.sh b/run_tests.sh new file mode 100755 index 0000000..91aab78 --- /dev/null +++ b/run_tests.sh @@ -0,0 +1,202 @@ +#!/bin/sh +# +# run_tests.sh -- self-contained test suite for bashlib functions. +# +# bashlib parses QUERY_STRING, HTTP_COOKIE and stdin at source time, so +# every case sources the library in a pristine environment (env -i) with +# a controlled CGI environment. stdout of each case is compared +# byte-for-byte with the expected output (trailing newline included). +# +# Usage: tests/run_tests.sh (or: make check from the top directory) + +PASS=0 +FAIL=0 +TOTAL=0 + +here=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) || exit 1 +LIB=$here/bashlib + +if [ ! -f "$LIB" ]; then + echo "bashlib not found at $LIB -- run ./configure first" >&2 + exit 1 +fi + +BASH_PROG=$(command -v "${BASH:-bash}" 2>/dev/null) || BASH_PROG= +if [ -z "$BASH_PROG" ] || [ ! -x "$BASH_PROG" ]; then + echo "bash interpreter not found" >&2 + exit 1 +fi + +TMP=${TMPDIR:-/tmp}/bashlib-tests.$$ +(umask 077 && mkdir "$TMP") || { echo "cannot create $TMP" >&2; exit 1; } +trap 'rm -rf "$TMP"' EXIT +trap 'rm -rf "$TMP"; exit 1' INT TERM + +# make_case -- write a snippet that sources bashlib then runs +make_case() { + printf '. "%s"\n%s\n' "$LIB" "$1" > "$TMP/case.sh" +} + +# bl [ENV=VAL ...] -- run a case with empty stdin (plain GET request) +bl() { + code=$1; shift + make_case "$code" + env -i "$@" "$BASH_PROG" --norc "$TMP/case.sh" >"$TMP/got" 2>"$TMP/err" [ENV=VAL ...] -- run a case with on stdin (POST) +bl_post() { + data=$1; code=$2; shift 2 + make_case "$code" + printf '%s' "$data" | env -i "$@" "$BASH_PROG" --norc "$TMP/case.sh" >"$TMP/got" 2>"$TMP/err" +} + +# show -- render file bytes with visible line ends +show() { + awk '{ printf "%s\\n", $0 } END { if (NR == 0) print "(empty)" }' "$1" +} + +# ok -- compare stdout of the last bl*() run with +# , a printf %b string ("line1\nline2\n"). +ok() { + desc=$1; exp=$2 + TOTAL=$((TOTAL + 1)) + printf '%b' "$exp" > "$TMP/want" + if cmp -s "$TMP/want" "$TMP/got"; then + PASS=$((PASS + 1)) + printf 'ok %2d - %s\n' "$TOTAL" "$desc" + else + FAIL=$((FAIL + 1)) + printf 'not ok %2d - %s\n' "$TOTAL" "$desc" + printf ' expected: %s\n' "$(show "$TMP/want")" + printf ' actual: %s\n' "$(show "$TMP/got")" + if [ -s "$TMP/err" ]; then + printf ' stderr:\n' + sed 's/^/ /' "$TMP/err" + fi + fi +} + +echo "bashlib test suite" +echo " library: $LIB" +echo + +# --- version ------------------------------------------------------------ + +bl 'version' +ok 'version prints name and release' 'bashlib, version 2\n' + +bl 'version_html' +ok 'version_html prints html link and version' \ + 'bashlib,version 2\n' + +# --- GET parameter parsing ---------------------------------------------- + +bl 'param name' 'QUERY_STRING=name=value' +ok 'GET: single name=value parameter' 'value\n' + +bl 'param a; param b; param c' 'QUERY_STRING=a=1&b=2;c=3' +ok 'GET: parameters separated by & and ;' '1\n2\n3\n' + +bl 'param q' 'QUERY_STRING=q=hello+world' +ok 'GET: + decodes to space' 'hello world\n' + +bl 'param w' 'QUERY_STRING=w=hello%20world' +ok 'GET: %XX hex escapes decode' 'hello world\n' + +bl 'param p' 'QUERY_STRING=p=b%2Bc' +ok 'GET: encoded %2B is returned as space (current behaviour)' 'b c\n' + +bl 'param s' 'QUERY_STRING=s=%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82' +ok 'GET: multi-byte utf-8 %XX sequences decode' 'привет\n' + +bl 'param usernamex' 'QUERY_STRING=user.name-x=1' +ok 'GET: dots and dashes are stripped from names' '1\n' + +bl 'param a' 'QUERY_STRING=a=' +ok 'GET: empty value yields empty string' '\n' + +bl 'param | grep -c .' +ok 'no CGI input: param lists nothing' '0\n' + +# --- param() ------------------------------------------------------------ + +bl 'param | sort' 'QUERY_STRING=b=2&a=1&c=3' +ok 'param: no arguments lists parameter names' 'a\nb\nc\n' + +bl 'param FORM_a' 'QUERY_STRING=a=1' +ok 'param: FORM_ prefix is stripped from the argument' '1\n' + +bl 'param foo bar baz >/dev/null +param foo' +ok 'param: set value and read it back' 'bar baz\n' + +# --- safe_param() ------------------------------------------------------- + +bl 'safe_param s' 'QUERY_STRING=s=%24%60%3C%3E%22%25%3B%29%28%26%2B' +ok 'safe_param: shell/html metacharacters are removed' ' \n' + +bl 'safe_param x' 'QUERY_STRING=x=%3Cscript%3Ealert%281%29%3C%2Fscript%3E' +ok 'safe_param: XSS payload is neutralised' 'scriptalert1/script\n' + +bl 'safe_param msg' 'QUERY_STRING=msg=hello+world' +ok 'safe_param: benign text and spaces survive' 'hello world\n' + +bl 'safe_param msg' 'QUERY_STRING=msg=hello+world+123' +ok 'safe_param: only the first + becomes a space (current behaviour)' \ + 'hello world123\n' + +# --- keywords() --------------------------------------------------------- + +bl 'keywords' 'QUERY_STRING=alpha+beta+gamma' +ok 'keywords: isindex-style query becomes a keyword list' 'alpha beta gamma\n' + +# --- POST via stdin ----------------------------------------------------- + +bl_post 'a=1&b=2' 'param a; param b' +ok 'POST: stdin is parsed as form data' '1\n2\n' + +bl_post 'a=1' 'param a; param b' 'QUERY_STRING=b=2' +ok 'POST: stdin params are merged with QUERY_STRING' '1\n2\n' + +# --- cookie() ----------------------------------------------------------- + +bl 'cookie session; cookie theme' 'HTTP_COOKIE=session=abc123; theme=dark' +ok 'cookies: HTTP_COOKIE is parsed' 'abc123\ndark\n' + +bl 'cookie' 'HTTP_COOKIE=session=abc123; theme=dark' +ok 'cookie: no arguments lists cookie names (space-separated, unlike param)' \ + 'session theme\n' + +bl 'cookie foo bar qux >/dev/null +cookie foo' +ok 'cookie: set value and read it back' 'bar qux\n' + +# --- set_cookie() ------------------------------------------------------- + +bl 'set_cookie theme light >/dev/null +set_cookie lang en >/dev/null +echo "[$bashlib_cookies]" +cookie theme +cookie lang' +ok 'set_cookie: accumulates pairs and exports them (leading space is current behaviour)' \ + '[ theme=light; lang=en]\nlight\nen\n' + +# --- send_redirect() ---------------------------------------------------- + +bl 'send_redirect http://example.org/x' +ok 'send_redirect: emits Location header and a blank line' \ + 'Location: http://example.org/x\n\n' + +bl 'send_redirect' 'SERVER_NAME=www.example.org' 'SCRIPT_NAME=cgi-bin/app.cgi' +ok 'send_redirect: defaults to http://$SERVER_NAME/$SCRIPT_NAME' \ + 'Location: http://www.example.org/cgi-bin/app.cgi\n\n' + +# --- summary ------------------------------------------------------------ + +echo +echo "$PASS of $TOTAL tests passed" +if [ "$FAIL" -ne 0 ]; then + echo "$FAIL test(s) failed" >&2 + exit 1 +fi From 70d6aabf696598c33ef28fe3a87c8d456e5fd3a9 Mon Sep 17 00:00:00 2001 From: Mikhail Novosyolov Date: Sun, 6 Sep 2026 15:54:49 +0300 Subject: [PATCH 11/18] Fix param() losing spaces after the first + sed 's/+/ /' replaced only the first "+" with a space; the remaining ones stayed in the value and were then dropped by safe_param()'s tr -d, e.g. msg=hello+world+123 came back as "hello world123". Use the global substitution so every "+" is restored to a space. Co-authored-by: Z.AI GLM --- bashlib.in | 2 +- run_tests.sh | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/bashlib.in b/bashlib.in index 3caff41..7544419 100644 --- a/bashlib.in +++ b/bashlib.in @@ -182,7 +182,7 @@ param() { fi # "+" is URL-encoded as "%2B", web server replaces spaces with "+", replace back, # otherwise safe_param() just removes the "+" sign and looses the space. - echo "${value}" | @SED@ -e 's/+/ /' + echo "${value}" | @SED@ -e 's/+/ /g' unset name unset value } diff --git a/run_tests.sh b/run_tests.sh index 91aab78..1d2c8f4 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -101,11 +101,17 @@ ok 'GET: parameters separated by & and ;' '1\n2\n3\n' bl 'param q' 'QUERY_STRING=q=hello+world' ok 'GET: + decodes to space' 'hello world\n' +bl 'param q' 'QUERY_STRING=q=one+two+three+four' +ok 'GET: every + decodes to space, not only the first one' 'one two three four\n' + +bl 'param q' 'QUERY_STRING=q=a++b' +ok 'GET: consecutive + decode to consecutive spaces' 'a b\n' + bl 'param w' 'QUERY_STRING=w=hello%20world' ok 'GET: %XX hex escapes decode' 'hello world\n' -bl 'param p' 'QUERY_STRING=p=b%2Bc' -ok 'GET: encoded %2B is returned as space (current behaviour)' 'b c\n' +bl 'param p' 'QUERY_STRING=p=b%2Bc%2Bd' +ok 'GET: every encoded %2B is returned as space' 'b c d\n' bl 'param s' 'QUERY_STRING=s=%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82' ok 'GET: multi-byte utf-8 %XX sequences decode' 'привет\n' @@ -143,8 +149,8 @@ bl 'safe_param msg' 'QUERY_STRING=msg=hello+world' ok 'safe_param: benign text and spaces survive' 'hello world\n' bl 'safe_param msg' 'QUERY_STRING=msg=hello+world+123' -ok 'safe_param: only the first + becomes a space (current behaviour)' \ - 'hello world123\n' +ok 'safe_param: spaces are not lost after the first +' \ + 'hello world 123\n' # --- keywords() --------------------------------------------------------- From 82372dc507bd5f93525c4bd6bfe2182fd4f8716e Mon Sep 17 00:00:00 2001 From: Mikhail Novosyolov Date: Sun, 6 Sep 2026 16:06:16 +0300 Subject: [PATCH 12/18] Fix value corruption when a literal - precedes a %XX escape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sed inserts a separator only after each \xNN token, so a literal "-" directly before an escape produced a word like "-\xD0". The decode loop fed it to printf as a format string, where bash parsed it as an invalid option and output nothing: the "-" and the first byte of the next character were silently lost, and the orphaned UTF-8 continuation byte rendered as U+FFFD, e.g. "АО Альфа-Банк Супер" -> "АО Альфа\xef\xbf\xbdанк Супер". Values starting with "-" were dropped the same way. Pass '%b' as an explicit format so the token is an argument and is never parsed as an option; \xNN expansion behaviour is unchanged. Applied to both the param and the cookie decode loops. Co-authored-by: Z.AI GLM --- bashlib.in | 4 ++-- run_tests.sh | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/bashlib.in b/bashlib.in index 7544419..a31cb41 100644 --- a/bashlib.in +++ b/bashlib.in @@ -72,7 +72,7 @@ if [ -n "${QUERY_STRING}" ]; then # value # for i in ${tmpvalue}; do - g=$(printf ${i}) + g=$(printf '%b' "${i}") value="${value}${g}" done #value=$(echo ${value}) @@ -129,7 +129,7 @@ if [ -n "${HTTP_COOKIE}" ]; then # value # for i in ${tmpvalue}; do - g=$(printf ${i}) + g=$(printf '%b' "${i}") value="${value}${g}" done #value=$(echo ${value}) diff --git a/run_tests.sh b/run_tests.sh index 1d2c8f4..b8b6c6c 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -116,6 +116,18 @@ ok 'GET: every encoded %2B is returned as space' 'b c d\n' bl 'param s' 'QUERY_STRING=s=%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82' ok 'GET: multi-byte utf-8 %XX sequences decode' 'привет\n' +bl 'param org' 'QUERY_STRING=org=%D0%90%D0%9E+%D0%90%D0%BB%D1%8C%D1%84%D0%B0-%D0%91%D0%B0%D0%BD%D0%BA+%D0%A1%D1%83%D0%BF%D0%B5%D1%80' +ok 'GET: literal - before %XX does not break decoding' 'АО Альфа-Банк Супер\n' + +bl 'param q' 'QUERY_STRING=q=-%D0%B0y' +ok 'GET: %-escape glued after a literal - decodes fully' '-аy\n' + +bl 'param q' 'QUERY_STRING=q=-start' +ok 'GET: value starting with - is not eaten' '-start\n' + +bl 'param x' 'QUERY_STRING=x=50%25+off' +ok 'GET: decoded % char survives round trip' '50% off\n' + bl 'param usernamex' 'QUERY_STRING=user.name-x=1' ok 'GET: dots and dashes are stripped from names' '1\n' @@ -178,6 +190,9 @@ bl 'cookie foo bar qux >/dev/null cookie foo' ok 'cookie: set value and read it back' 'bar qux\n' +bl 'cookie org' 'HTTP_COOKIE=org=%D0%90%D0%BB%D1%8C%D1%84%D0%B0-%D0%91%D0%B0%D0%BD%D0%BA' +ok 'cookies: literal - before %XX does not break decoding' 'Альфа-Банк\n' + # --- set_cookie() ------------------------------------------------------- bl 'set_cookie theme light >/dev/null From 6374a963896c510542da270c740cf2bcd2f4ba08 Mon Sep 17 00:00:00 2001 From: Mikhail Novosyolov Date: Sun, 6 Sep 2026 16:11:16 +0300 Subject: [PATCH 13/18] Make param/cookie/set_cookie setters print nothing The assignment branch never set \$value but still fell through to the final echo, so every "param name value..." and "cookie name value..." call emitted a stray blank line (and set_cookie inherited one per call via its inner cookie call). In CGI output that blank line lands before the headers. Return right after the export so setters stay silent. Co-authored-by: Z.AI GLM --- bashlib.in | 2 ++ run_tests.sh | 18 +++++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/bashlib.in b/bashlib.in index a31cb41..1d81930 100644 --- a/bashlib.in +++ b/bashlib.in @@ -177,6 +177,7 @@ param() { name=$1 shift eval "export 'FORM_${name}=$*'" + return else value=$(@ENV@ | @GREP@ '^FORM_' | @SED@ -e 's/FORM_//' | @CUT@ -d= -f1) fi @@ -204,6 +205,7 @@ cookie() { name=$1 shift eval "export 'COOKIE_${name}=$*'" + return else value=$(@ENV@ | @GREP@ '^COOKIE_' | @SED@ -e 's/COOKIE_//' | @CUT@ -d= -f1) fi diff --git a/run_tests.sh b/run_tests.sh index b8b6c6c..3e45e34 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -145,9 +145,9 @@ ok 'param: no arguments lists parameter names' 'a\nb\nc\n' bl 'param FORM_a' 'QUERY_STRING=a=1' ok 'param: FORM_ prefix is stripped from the argument' '1\n' -bl 'param foo bar baz >/dev/null +bl 'param foo bar baz param foo' -ok 'param: set value and read it back' 'bar baz\n' +ok 'param: set value silently and read it back' 'bar baz\n' # --- safe_param() ------------------------------------------------------- @@ -186,21 +186,21 @@ bl 'cookie' 'HTTP_COOKIE=session=abc123; theme=dark' ok 'cookie: no arguments lists cookie names (space-separated, unlike param)' \ 'session theme\n' -bl 'cookie foo bar qux >/dev/null -cookie foo' -ok 'cookie: set value and read it back' 'bar qux\n' - bl 'cookie org' 'HTTP_COOKIE=org=%D0%90%D0%BB%D1%8C%D1%84%D0%B0-%D0%91%D0%B0%D0%BD%D0%BA' ok 'cookies: literal - before %XX does not break decoding' 'Альфа-Банк\n' +bl 'cookie foo bar qux +cookie foo' +ok 'cookie: set value silently and read it back' 'bar qux\n' + # --- set_cookie() ------------------------------------------------------- -bl 'set_cookie theme light >/dev/null -set_cookie lang en >/dev/null +bl 'set_cookie theme light +set_cookie lang en echo "[$bashlib_cookies]" cookie theme cookie lang' -ok 'set_cookie: accumulates pairs and exports them (leading space is current behaviour)' \ +ok 'set_cookie: accumulates pairs silently and exports them (leading space is current behaviour)' \ '[ theme=light; lang=en]\nlight\nen\n' # --- send_redirect() ---------------------------------------------------- From ac7a6297f2667b003f30bd2cd1368072c2ec54fc Mon Sep 17 00:00:00 2001 From: Mikhail Novosyolov Date: Sun, 6 Sep 2026 16:32:43 +0300 Subject: [PATCH 14/18] Fix all shellcheck findings in bashlib - Quote variable expansions that are safe to quote (QUERY_STRING, name, tmpvalue, KEYWORDS, set_cookie's inner cookie call); the word-splitting in the QUERY_STRING/HTTP_COOKIE parse loops stays, it is the parsing mechanism there. - Replace printf-as-format in the name decoders with an explicit %b argument, same as the value decoders; %-escapes in names still decode. - safe_param: pass arguments through as "$@" instead of unquoted $*. - cookie(): print "$value" quoted, so listings are one name per line (like param) and consecutive spaces in values survive the round trip; same for keywords() and set_cookie(). - Drop the unused DEBUG flag. run_tests.sh pins the changed contracts and adds coverage for what was untested: %-escape decoding of names, consecutive spaces in keywords/cookie/set_cookie values, and safe_param not creating params for names with spaces. Two intentional SC2016s in the runner are suppressed with directives. shellcheck is clean on both files; 39/39 tests pass, and the suite fails on the previous library for every changed behaviour. Co-authored-by: Z.AI GLM --- bashlib.in | 37 +++++++++++++++++-------------------- run_tests.sh | 31 ++++++++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/bashlib.in b/bashlib.in index 1d81930..281fef4 100644 --- a/bashlib.in +++ b/bashlib.in @@ -21,9 +21,6 @@ VERSION="2" # do the parameter/cookie parsing. # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- -# Global debug flag. Set to 0 to disable debugging throughout the lib -DEBUG=0 - # capture stdin for POST methods. POST requests don't always come in # with a newline attached, so we use cat to grab stdin and append a newline. # This is a wonderful hack, and thanks to paulb. @@ -35,8 +32,8 @@ fi # Handle GET and POST requests... (the QUERY_STRING will be set) if [ -n "${QUERY_STRING}" ]; then # name=value params, separated by either '&' or ';' - if echo ${QUERY_STRING} | grep '=' >/dev/null ; then - for Q in $(echo ${QUERY_STRING} | @TR@ ";&" "\012") ; do + if echo "${QUERY_STRING}" | grep '=' >/dev/null ; then + for Q in $(echo "${QUERY_STRING}" | @TR@ ";&" "\012") ; do # # Clear our local variables # @@ -48,12 +45,12 @@ if [ -n "${QUERY_STRING}" ]; then # get the name of the key, and decode it # name=${Q%%=*} - name=$(echo ${name} | \ + name=$(echo "${name}" | \ @SED@ -e 's/%\(\)/\\\x/g' | \ @TR@ "+" " ") - name=$(echo ${name} | \ + name=$(echo "${name}" | \ @TR@ -d ".-") - name=$(printf ${name} | @TR@ -d '$`') + name=$(printf '%b' "${name}" | @TR@ -d '$\`') # # get the value and decode it. This is tricky... printf chokes on @@ -63,7 +60,7 @@ if [ -n "${QUERY_STRING}" ]; then # printf, and then remove it. # tmpvalue=${Q#*=} - tmpvalue=$(echo ${tmpvalue} | \ + tmpvalue=$(echo "${tmpvalue}" | \ @SED@ -e 's/%\(..\)/\\\x\1 /g') #echo "Intermediate \$value: ${tmpvalue}" 1>&2 @@ -80,7 +77,7 @@ if [ -n "${QUERY_STRING}" ]; then eval "export FORM_${name}='${value}'" done else # keywords: foo.cgi?a+b+c - Q=$(echo ${QUERY_STRING} | tr '+' ' ') + Q=$(echo "${QUERY_STRING}" | tr '+' ' ') eval "export KEYWORDS='${Q}'" fi fi @@ -110,17 +107,17 @@ if [ -n "${HTTP_COOKIE}" ]; then # get the name of the key, and decode it # name=${Q%%=*} - name=$(echo ${name} | \ + name=$(echo "${name}" | \ @SED@ -e 's/%\(\)/\\\x/g' | \ @TR@ "+" " ") - name=$(echo ${name} | \ + name=$(echo "${name}" | \ @TR@ -d ".-") - name=$(printf ${name}) + name=$(printf '%b' "${name}") # Decode the cookie value. See the parameter section above for # an explanation of what this is doing. tmpvalue=${Q#*=} - tmpvalue=$(echo ${tmpvalue} | \ + tmpvalue=$(echo "${tmpvalue}" | \ @SED@ -e 's/%\(..\)/\\\x\1 /g') #echo "Intermediate \$value: ${tmpvalue}" 1>&2 @@ -171,7 +168,7 @@ param() { local value if [ $# -eq 1 ]; then name=$1 - name=$(echo ${name} | @SED@ -e 's/FORM_//') + name=$(echo "${name}" | @SED@ -e 's/FORM_//') value=$(@ENV@ | @GREP@ "^FORM_${name}=" | @SED@ -e 's/FORM_//' | @CUT@ -d= -f2-) elif [ $# -gt 1 ]; then name=$1 @@ -190,7 +187,7 @@ param() { # shell invocation and X-site scripting prevention safe_param() { - param $* | @TR@ -d '$`<>"%;)(&+'"'" + param "$@" | @TR@ -d '$`<>"%;)(&+'"'" } # cookie function. Same explanation as param @@ -199,7 +196,7 @@ cookie() { local value if [ $# -eq 1 ]; then name=$1 - name=$(echo ${name} | @SED@ -e 's/COOKIE_//') + name=$(echo "${name}" | @SED@ -e 's/COOKIE_//') value=$(@ENV@ | @GREP@ "^COOKIE_${name}" | @SED@ -e 's/COOKIE_//' | @CUT@ -d= -f2-) elif [ $# -gt 1 ]; then name=$1 @@ -209,7 +206,7 @@ cookie() { else value=$(@ENV@ | @GREP@ '^COOKIE_' | @SED@ -e 's/COOKIE_//' | @CUT@ -d= -f1) fi - echo ${value} + echo "${value}" unset name unset value } @@ -217,7 +214,7 @@ cookie() { # keywords returns a list of keywords. This is only set when the script is # called with an ISINDEX form (these are pretty rare nowadays). keywords() { - echo ${KEYWORDS} + echo "${KEYWORDS}" } set_cookie() { @@ -228,7 +225,7 @@ set_cookie() { bashlib_cookies=${bashlib_cookies#;} - cookie $name $value + cookie "$name" "$value" } # diff --git a/run_tests.sh b/run_tests.sh index 3e45e34..33e8895 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -131,6 +131,9 @@ ok 'GET: decoded % char survives round trip' '50% off\n' bl 'param usernamex' 'QUERY_STRING=user.name-x=1' ok 'GET: dots and dashes are stripped from names' '1\n' +bl 'param asb' 'QUERY_STRING=a%73b=1' +ok 'GET: %XX escapes in names decode' '1\n' + bl 'param a' 'QUERY_STRING=a=' ok 'GET: empty value yields empty string' '\n' @@ -164,11 +167,18 @@ bl 'safe_param msg' 'QUERY_STRING=msg=hello+world+123' ok 'safe_param: spaces are not lost after the first +' \ 'hello world 123\n' +bl 'safe_param "foo bar" >/dev/null +param | grep -c .' +ok 'safe_param: a name with spaces does not silently set a param' '0\n' + # --- keywords() --------------------------------------------------------- bl 'keywords' 'QUERY_STRING=alpha+beta+gamma' ok 'keywords: isindex-style query becomes a keyword list' 'alpha beta gamma\n' +bl 'keywords' 'QUERY_STRING=alpha++beta' +ok 'keywords: consecutive + keep consecutive spaces' 'alpha beta\n' + # --- POST via stdin ----------------------------------------------------- bl_post 'a=1&b=2' 'param a; param b' @@ -182,9 +192,8 @@ ok 'POST: stdin params are merged with QUERY_STRING' '1\n2\n' bl 'cookie session; cookie theme' 'HTTP_COOKIE=session=abc123; theme=dark' ok 'cookies: HTTP_COOKIE is parsed' 'abc123\ndark\n' -bl 'cookie' 'HTTP_COOKIE=session=abc123; theme=dark' -ok 'cookie: no arguments lists cookie names (space-separated, unlike param)' \ - 'session theme\n' +bl 'cookie | sort' 'HTTP_COOKIE=session=abc123; theme=dark' +ok 'cookie: no arguments lists cookie names, one per line' 'session\ntheme\n' bl 'cookie org' 'HTTP_COOKIE=org=%D0%90%D0%BB%D1%8C%D1%84%D0%B0-%D0%91%D0%B0%D0%BD%D0%BA' ok 'cookies: literal - before %XX does not break decoding' 'Альфа-Банк\n' @@ -193,8 +202,15 @@ bl 'cookie foo bar qux cookie foo' ok 'cookie: set value silently and read it back' 'bar qux\n' +bl 'cookie foo "a b" +cookie foo' +ok 'cookie: consecutive spaces in a value survive' 'a b\n' + # --- set_cookie() ------------------------------------------------------- +# $bashlib_cookies must stay literal: it is expanded by the inner bash of +# the test case, not by the runner. +# shellcheck disable=SC2016 bl 'set_cookie theme light set_cookie lang en echo "[$bashlib_cookies]" @@ -203,6 +219,13 @@ cookie lang' ok 'set_cookie: accumulates pairs silently and exports them (leading space is current behaviour)' \ '[ theme=light; lang=en]\nlight\nen\n' +# Same as above: expanded inside the case's inner bash. +# shellcheck disable=SC2016 +bl 'set_cookie m "a b" +echo "[$bashlib_cookies]" +cookie m' +ok 'set_cookie: preserves consecutive spaces in values' '[ m=a b]\na b\n' + # --- send_redirect() ---------------------------------------------------- bl 'send_redirect http://example.org/x' @@ -210,6 +233,8 @@ ok 'send_redirect: emits Location header and a blank line' \ 'Location: http://example.org/x\n\n' bl 'send_redirect' 'SERVER_NAME=www.example.org' 'SCRIPT_NAME=cgi-bin/app.cgi' +# Description text; $SERVER_NAME is literal. +# shellcheck disable=SC2016 ok 'send_redirect: defaults to http://$SERVER_NAME/$SCRIPT_NAME' \ 'Location: http://www.example.org/cgi-bin/app.cgi\n\n' From e21a07914048343801c0664f617d21a67791d91b Mon Sep 17 00:00:00 2001 From: Mikhail Novosyolov Date: Sun, 6 Sep 2026 17:34:52 +0300 Subject: [PATCH 15/18] Make every hot path fork-free using bash builtins param()/cookie() used to run env|grep|sed|cut on every call, the URL decoding forked once per %XX escape, and the init section forked a handful of sed/tr/echo pipelines per parameter. Replace all of that with bash-only constructs: - ${!var} indirection reads stored values with zero forks; a helper (_param_value) also feeds safe_param, whose tr(1) pipeline is gone (the strip set is one glob class in SAFE_STRIP) - ${!FORM_@}/${!COOKIE_@} produce the no-argument listings - ${var//pat/rep} plus a single "printf -v %b" decode each name and value in one builtin call (bash reads at most two hex digits per \xHH, so the old per-escape space hack is unnecessary) - "case" replaces the echo|grep probe, read -d replaces $(cat), ${QUERY_STRING//[;&]/ } replaces the tr splitter This makes the library explicitly bash-only, which the %XX decoding already was in practice (POSIX printf has no \xHH escapes). On an Intel N100: param x1000 8.5s -> 0.08s, source x200 14.6s -> 0.13s. Getter lookups are now exact variable matches instead of grep BREs, and invalid variable names yield empty values instead of grep patterns. The full suite (39 tests) passes unchanged, shellcheck is clean. Co-authored-by: Z.AI GLM --- bashlib.in | 169 +++++++++++++++++++++++++++-------------------------- 1 file changed, 87 insertions(+), 82 deletions(-) mode change 100644 => 100755 bashlib.in diff --git a/bashlib.in b/bashlib.in old mode 100644 new mode 100755 index 281fef4..ecd6390 --- a/bashlib.in +++ b/bashlib.in @@ -7,6 +7,12 @@ # bashlib is used by sourcing it at the beginning of scripts that # needs its functionality (by using the . or source commands). +# +# The library targets bash: it relies on bash-only features such as +# ${!var} indirection, printf -v, ${!PREFIX@} listings, ${var//pat/rep} +# substitution and read -d. All hot paths are fork-free (builtins only): +# param()/cookie() used to spawn env|grep|sed|cut on every call, and the +# URL decoding used to fork once per %XX escape. PATH=/bin:/usr/bin @@ -21,65 +27,58 @@ VERSION="2" # do the parameter/cookie parsing. # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +# Characters removed by safe_param(), as one glob character class. +SAFE_STRIP='[$`<>"%;)(&+'"'"']' + # capture stdin for POST methods. POST requests don't always come in -# with a newline attached, so we use cat to grab stdin and append a newline. -# This is a wonderful hack, and thanks to paulb. -STDIN=$(@CAT@) +# with a newline attached, so read everything up to EOF (the empty -d +# delimiter means NUL, which never occurs in CGI input). +STDIN= +IFS= read -r -d '' STDIN || true if [ -n "${STDIN}" ]; then QUERY_STRING="${STDIN}&${QUERY_STRING}" fi # Handle GET and POST requests... (the QUERY_STRING will be set) -if [ -n "${QUERY_STRING}" ]; then +if [ -n "${QUERY_STRING}" ]; then # name=value params, separated by either '&' or ';' - if echo "${QUERY_STRING}" | grep '=' >/dev/null ; then - for Q in $(echo "${QUERY_STRING}" | @TR@ ";&" "\012") ; do + case ${QUERY_STRING} in + *=*) + for Q in ${QUERY_STRING//[;&]/ } ; do # # Clear our local variables # unset name - unset value - unset tmpvalue - + value= + # - # get the name of the key, and decode it + # Decode the name of the key: turn %XX into \xHH for printf, + # decode '+' as a space, then strip '.', '-', '$' and '`' so the + # name can never break the eval below. # name=${Q%%=*} - name=$(echo "${name}" | \ - @SED@ -e 's/%\(\)/\\\x/g' | \ - @TR@ "+" " ") - name=$(echo "${name}" | \ - @TR@ -d ".-") - name=$(printf '%b' "${name}" | @TR@ -d '$\`') - - # - # get the value and decode it. This is tricky... printf chokes on - # hex values in the form \xNN when there is another hex-ish value - # (i.e., a-fA-F) immediately after the first two. My (horrible) - # solution is to put a space aftet the \xNN, give the value to - # printf, and then remove it. - # - tmpvalue=${Q#*=} - tmpvalue=$(echo "${tmpvalue}" | \ - @SED@ -e 's/%\(..\)/\\\x\1 /g') - #echo "Intermediate \$value: ${tmpvalue}" 1>&2 - + name=${name//%/\\x} + name=${name//+/ } + name=${name//[.-]/} + printf -v name '%b' "${name}" + name=${name//$/} + name=${name//\`/} + # - # Iterate through tmpvalue and printf each string, and append it to - # value + # Decode the value: turn %XX into \xHH and let a single printf + # do the whole job. printf reads at most two hex digits per \xHH, + # so no separators between escapes are needed. # - for i in ${tmpvalue}; do - g=$(printf '%b' "${i}") - value="${value}${g}" - done - #value=$(echo ${value}) - + value=${Q#*=} + printf -v value '%b' "${value//%/\\x}" + eval "export FORM_${name}='${value}'" done - else # keywords: foo.cgi?a+b+c - Q=$(echo "${QUERY_STRING}" | tr '+' ' ') - eval "export KEYWORDS='${Q}'" - fi + ;; + *) # keywords: foo.cgi?a+b+c + eval "export KEYWORDS='${QUERY_STRING//+/ }'" + ;; + esac fi # @@ -89,14 +88,13 @@ fi # help distinguish them from the other variables that get set # automatically. # -if [ -n "${HTTP_COOKIE}" ]; then +if [ -n "${HTTP_COOKIE}" ]; then for Q in ${HTTP_COOKIE}; do # # Clear our local variables # name= value= - tmpvalue= # # Strip trailing ; off the value @@ -104,37 +102,23 @@ if [ -n "${HTTP_COOKIE}" ]; then Q=${Q%;} # - # get the name of the key, and decode it + # Decode the name of the key; see the parameter section above. # name=${Q%%=*} - name=$(echo "${name}" | \ - @SED@ -e 's/%\(\)/\\\x/g' | \ - @TR@ "+" " ") - name=$(echo "${name}" | \ - @TR@ -d ".-") - name=$(printf '%b' "${name}") - - # Decode the cookie value. See the parameter section above for - # an explanation of what this is doing. - tmpvalue=${Q#*=} - tmpvalue=$(echo "${tmpvalue}" | \ - @SED@ -e 's/%\(..\)/\\\x\1 /g') - #echo "Intermediate \$value: ${tmpvalue}" 1>&2 + name=${name//%/\\x} + name=${name//+/ } + name=${name//[.-]/} + printf -v name '%b' "${name}" # - # Iterate through tmpvalue and printf each string, and append it to - # value + # Decode the cookie value; see the parameter section above. # - for i in ${tmpvalue}; do - g=$(printf '%b' "${i}") - value="${value}${g}" - done - #value=$(echo ${value}) + value=${Q#*=} + printf -v value '%b' "${value//%/\\x}" # # Export COOKIE_${name} into the environment # - #echo "exporting COOKIE_${name}=${value}" 1>&2 eval "export COOKIE_${name}='${value}'" done fi @@ -153,6 +137,18 @@ version_html() { echo "version ${VERSION}" } +# +# Internal helper: store the raw stored value of parameter $1 in REPLY. +# Anything that is not a valid variable name yields an empty value. +# +_param_value() { + local name="FORM_${1#FORM_}" + case ${name} in + FORM_|FORM_*[!A-Za-z0-9_]*) REPLY= ;; + *) REPLY="${!name}" ;; + esac +} + # # Parameter function. # * When called with no arguments, returns a list of parameters that @@ -164,47 +160,57 @@ version_html() { # that name. # param() { - local name - local value + local name value if [ $# -eq 1 ]; then - name=$1 - name=$(echo "${name}" | @SED@ -e 's/FORM_//') - value=$(@ENV@ | @GREP@ "^FORM_${name}=" | @SED@ -e 's/FORM_//' | @CUT@ -d= -f2-) + _param_value "$1" + value="${REPLY//+/ }" elif [ $# -gt 1 ]; then name=$1 shift eval "export 'FORM_${name}=$*'" return else - value=$(@ENV@ | @GREP@ '^FORM_' | @SED@ -e 's/FORM_//' | @CUT@ -d= -f1) + for name in ${!FORM_@}; do + echo "${name#FORM_}" + done + return fi - # "+" is URL-encoded as "%2B", web server replaces spaces with "+", replace back, - # otherwise safe_param() just removes the "+" sign and looses the space. - echo "${value}" | @SED@ -e 's/+/ /g' + echo "${value}" unset name unset value } # shell invocation and X-site scripting prevention safe_param() { - param "$@" | @TR@ -d '$`<>"%;)(&+'"'" + local value + if [ $# -eq 1 ]; then + _param_value "$1" + value="${REPLY//+/ }" + echo "${value//${SAFE_STRIP}/}" + else + param "$@" + fi } # cookie function. Same explanation as param cookie() { - local name - local value + local name value if [ $# -eq 1 ]; then - name=$1 - name=$(echo "${name}" | @SED@ -e 's/COOKIE_//') - value=$(@ENV@ | @GREP@ "^COOKIE_${name}" | @SED@ -e 's/COOKIE_//' | @CUT@ -d= -f2-) + name="COOKIE_${1#COOKIE_}" + case ${name} in + COOKIE_|COOKIE_*[!A-Za-z0-9_]*) value= ;; + *) value="${!name}" ;; + esac elif [ $# -gt 1 ]; then name=$1 shift eval "export 'COOKIE_${name}=$*'" return else - value=$(@ENV@ | @GREP@ '^COOKIE_' | @SED@ -e 's/COOKIE_//' | @CUT@ -d= -f1) + for name in ${!COOKIE_@}; do + echo "${name#COOKIE_}" + done + return fi echo "${value}" unset name @@ -242,4 +248,3 @@ send_redirect() { echo "Location: ${uri}" echo "" } - From 68e6e29b993df3d3e46289ced83f20b0af1f372d Mon Sep 17 00:00:00 2001 From: Mikhail Novosyolov Date: Sun, 6 Sep 2026 17:40:15 +0300 Subject: [PATCH 16/18] Explain the parameter-expansion constructs with comments above code The fork-free rewrite relies on dense ${var//pat/rep}, ${!var} and ${var#pat} constructs that are hard to read back. Document each transformation with a short comment line above it, in a "input -> output" form, e.g. # every % becomes \x: "user%2Ename" -> "user\x2Ename" name=${name//%/\\x} Also note why SAFE_STRIP is assembled by concatenation, why the '[.-]' deletion spares %2E/%2D, and why set_cookie keeps the space after dropping the leading ';'. Comments only; no code changes (39/39 tests still pass, shellcheck clean). Co-authored-by: Z.AI GLM --- bashlib.in | 53 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/bashlib.in b/bashlib.in index ecd6390..22671e5 100755 --- a/bashlib.in +++ b/bashlib.in @@ -28,11 +28,14 @@ VERSION="2" # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- # Characters removed by safe_param(), as one glob character class. +# The class is built by concatenation because a literal ' cannot +# appear inside a '...' string: '[$`<>"%;)(&+' + "'" + ']'. SAFE_STRIP='[$`<>"%;)(&+'"'"']' # capture stdin for POST methods. POST requests don't always come in # with a newline attached, so read everything up to EOF (the empty -d -# delimiter means NUL, which never occurs in CGI input). +# delimiter means NUL, which never occurs in CGI input). A plain +# $(cat) would fork; read is a builtin. STDIN= IFS= read -r -d '' STDIN || true if [ -n "${STDIN}" ]; then @@ -44,6 +47,9 @@ if [ -n "${QUERY_STRING}" ]; then # name=value params, separated by either '&' or ';' case ${QUERY_STRING} in *=*) + # '&' and ';' become spaces, so the unquoted expansion below splits + # QUERY_STRING into one word per parameter: + # "a=1&b=2;c=3" -> "a=1 b=2 c=3" -> words "a=1" "b=2" "c=3" for Q in ${QUERY_STRING//[;&]/ } ; do # # Clear our local variables @@ -52,30 +58,41 @@ if [ -n "${QUERY_STRING}" ]; then value= # - # Decode the name of the key: turn %XX into \xHH for printf, - # decode '+' as a space, then strip '.', '-', '$' and '`' so the - # name can never break the eval below. + # Decode the name of the key so that it can never break the eval + # below: turn %XX into \xHH for printf, decode '+' as a space, + # then strip '.', '-', '$' and '`'. # + # cut at the first '=': "user%2Ename=admin" -> "user%2Ename" name=${Q%%=*} + # every % becomes \x: "user%2Ename" -> "user\x2Ename" name=${name//%/\\x} + # '+' is an encoded space: "a+b+c" -> "a b c" name=${name//+/ } + # delete '.' and '-' (literal ones only; encoded %2E/%2D survive + # here and decode back below): "user.name-x" -> "usernamex" name=${name//[.-]/} + # decode \xHH hex: "user\x2Enamex" -> "user.namex" printf -v name '%b' "${name}" + # delete every '$' name=${name//$/} + # delete every '`' name=${name//\`/} # # Decode the value: turn %XX into \xHH and let a single printf - # do the whole job. printf reads at most two hex digits per \xHH, - # so no separators between escapes are needed. + # do the whole job. printf reads at most two hex digits per + # \xHH, so no separators between escapes are needed. # + # cut at the first '=': "q=hello%21" -> "hello%21" value=${Q#*=} + # decode %XX: "hello%21" -> "hello!" printf -v value '%b' "${value//%/\\x}" eval "export FORM_${name}='${value}'" done ;; *) # keywords: foo.cgi?a+b+c + # "alpha+beta+gamma" -> "alpha beta gamma" eval "export KEYWORDS='${QUERY_STRING//+/ }'" ;; esac @@ -97,23 +114,30 @@ if [ -n "${HTTP_COOKIE}" ]; then value= # - # Strip trailing ; off the value + # drop one trailing ';': "session=abc123;" -> "session=abc123" # Q=${Q%;} # # Decode the name of the key; see the parameter section above. # + # cut at the first '=': "session=abc123" -> "session" name=${Q%%=*} + # every % becomes \x: "a%73b" -> "a\x73b" name=${name//%/\\x} + # '+' is an encoded space: "a+b" -> "a b" name=${name//+/ } + # delete '.' and '-': "user.name" -> "username" name=${name//[.-]/} + # decode \xHH hex: "a\x73b" -> "asb" printf -v name '%b' "${name}" # # Decode the cookie value; see the parameter section above. # + # cut at the first '=': "q=a%20b" -> "a%20b" value=${Q#*=} + # decode %XX: "a%20b" -> "a b" printf -v value '%b' "${value//%/\\x}" # @@ -142,9 +166,12 @@ version_html() { # Anything that is not a valid variable name yields an empty value. # _param_value() { + # "foo" and "FORM_foo" both give "FORM_foo" local name="FORM_${1#FORM_}" case ${name} in + # empty name or not a valid variable name -> empty value FORM_|FORM_*[!A-Za-z0-9_]*) REPLY= ;; + # ${!name} is the value of the variable whose name is in $name *) REPLY="${!name}" ;; esac } @@ -163,6 +190,7 @@ param() { local name value if [ $# -eq 1 ]; then _param_value "$1" + # '+' is an encoded space: "a+b" -> "a b" value="${REPLY//+/ }" elif [ $# -gt 1 ]; then name=$1 @@ -170,7 +198,9 @@ param() { eval "export 'FORM_${name}=$*'" return else + # ${!FORM_@} expands to every existing variable named FORM_* for name in ${!FORM_@}; do + # "FORM_foo" -> "foo" echo "${name#FORM_}" done return @@ -185,7 +215,9 @@ safe_param() { local value if [ $# -eq 1 ]; then _param_value "$1" + # '+' is an encoded space: "a+b" -> "a b" value="${REPLY//+/ }" + # delete the SAFE_STRIP characters: 'a$b"c' -> 'abc' echo "${value//${SAFE_STRIP}/}" else param "$@" @@ -196,9 +228,12 @@ safe_param() { cookie() { local name value if [ $# -eq 1 ]; then + # "foo" and "COOKIE_foo" both give "COOKIE_foo" name="COOKIE_${1#COOKIE_}" case ${name} in + # empty name or not a valid variable name -> empty value COOKIE_|COOKIE_*[!A-Za-z0-9_]*) value= ;; + # ${!name} is the value of the variable whose name is in $name *) value="${!name}" ;; esac elif [ $# -gt 1 ]; then @@ -207,7 +242,9 @@ cookie() { eval "export 'COOKIE_${name}=$*'" return else + # ${!COOKIE_@} expands to every existing variable named COOKIE_* for name in ${!COOKIE_@}; do + # "COOKIE_foo" -> "foo" echo "${name#COOKIE_}" done return @@ -227,8 +264,10 @@ set_cookie() { local name=$1 shift local value=$* + # "" -> "; a=1" -> "; a=1; b=2" bashlib_cookies="${bashlib_cookies}; ${name}=${value}" + # drop the leading ';' only (the space after it stays): "; a=1" -> " a=1" bashlib_cookies=${bashlib_cookies#;} cookie "$name" "$value" From 3f5a7960035871b1051623b5c141a8890a1cb901 Mon Sep 17 00:00:00 2001 From: Mikhail Novosyolov Date: Sun, 6 Sep 2026 17:50:00 +0300 Subject: [PATCH 17/18] Drop configure: the library needs no generation The fork-free rewrite removed every external tool call, so configure had exactly one substitution left, @BASH@ in the shebang of a file that is sourced, not executed. Meanwhile the template machinery kept costing real effort: stale generated artifacts drifted from the template twice in one session (a 67-month-old bashlib with VERSION 0.06, and the shebang silently reverting to /bin/sh because bash-as-sh exports BASH, which autoconf's user-override probe picks up), and a fresh clone could not produce a working library without autoconf+configure first. bashlib.in is now the tracked bashlib itself, with a plain "#!/bin/bash" shebang (no env(1) exec; anyone on an unusual layout can adjust the one line). A static Makefile (PREFIX ?=, DESTDIR for packagers) replaces the generated one; configure.in, Makefile.in and the generated artifacts are gone. Also stop clobbering PATH of the sourcing script: the /bin:/usr/bin assignment predates the fork-free rewrite and nothing external is called anymore. Fresh clones now work with ". ./bashlib" directly; make check and shellcheck pass. Co-authored-by: Z.AI GLM --- .gitignore | 7 +----- INSTALL | 19 +++++++++++------ Makefile.in => Makefile | 44 +++++++++++++++----------------------- bashlib.in => bashlib | 4 +--- configure.in | 47 ----------------------------------------- run_tests.sh | 2 +- 6 files changed, 33 insertions(+), 90 deletions(-) rename Makefile.in => Makefile (57%) rename bashlib.in => bashlib (99%) delete mode 100644 configure.in diff --git a/.gitignore b/.gitignore index 16a6e5f..420398a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1 @@ -Makefile -autom4te.cache -bashlib -config.log -config.status -configure +bashlib-*.tar.gz diff --git a/INSTALL b/INSTALL index 403bece..9ff5cba 100644 --- a/INSTALL +++ b/INSTALL @@ -1,11 +1,19 @@ -bashlib 0.5 +bashlib 2 -Installation of bashlib is relatively straightforward: +bashlib is a bash library: there is nothing to configure and nothing +to build, the checked-out file is the finished library (it calls no +external tools). To run the test suite: + + # make check + +To install (the default prefix is /usr/local): - # gunzip -c bashlib-0.5.tar.gz | tar xf - - # cd bashlib-0.5 - # ./configure # make install + # make install PREFIX=/usr # or wherever + +Packagers can use the usual DESTDIR redirection: + + # make install DESTDIR=$RPM_BUILD_ROOT Using bashlib in your CGI scripts is as simple as beginning your scripts with: @@ -38,7 +46,6 @@ needs to be done. Patches and feature requests are accepted. TODO * Better documentation, probably a man page - * Autoconf-based install script * More functions * Functions to generate HTML * Functions to set cookies diff --git a/Makefile.in b/Makefile similarity index 57% rename from Makefile.in rename to Makefile index 26a2f32..5524b9e 100644 --- a/Makefile.in +++ b/Makefile @@ -1,9 +1,6 @@ # Makefile for bashlib # ---------------------------------------------------------------------- -# $Id$ -# @configure_input@ -# ---------------------------------------------------------------------- -# bashlib +# bashlib # Copyright (C) 2002-2005 darren chamberlain # # This program is free software; you can redistribute it and/or modify @@ -20,37 +17,30 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # ---------------------------------------------------------------------- -PREFIX = @prefix@ -VERSION = @bashlib_version@ +# There is nothing to configure or build: bashlib calls no external +# tools, so the checked-out file is the finished library. +PREFIX ?= /usr/local +DESTDIR ?= +VERSION = 2 all: - ./configure + @echo "nothing to build; try 'make check' or 'make install'" install: - @INSTALL@ bashlib $(PREFIX)/bin + mkdir -p $(DESTDIR)$(PREFIX)/bin + install -m 755 bashlib $(DESTDIR)$(PREFIX)/bin + check: ./run_tests.sh dist: - @MKDIR@ bashlib-$(VERSION) - @CP@ configure bashlib-$(VERSION)/ - @CP@ bashlib.in bashlib-$(VERSION)/ - @CP@ Makefile.in bashlib-$(VERSION)/ - @CP@ INSTALL bashlib-$(VERSION)/ - @CP@ COPYING bashlib-$(VERSION)/ + mkdir bashlib-$(VERSION) + cp bashlib Makefile INSTALL COPYING run_tests.sh bashlib-$(VERSION)/ + cp -r examples bashlib-$(VERSION)/ cd bashlib-$(VERSION); ln -s INSTALL README - @TAR@ cf bashlib-$(VERSION).tar bashlib-$(VERSION) - @GZIP@ --best bashlib-$(VERSION).tar - @RM@ -rf bashlib-$(VERSION) + tar cf bashlib-$(VERSION).tar bashlib-$(VERSION) + gzip --best bashlib-$(VERSION).tar + rm -rf bashlib-$(VERSION) clean: - @RM@ -f Makefile bashlib config.cache config.log config.status - @RM@ -fr ./autom4te.cache/ - -cvs-clean: - $(MAKE) clean - @RM@ -f configure - @RM@ -f bashlib-$(VERSION).tar.gz - -distclean: - $(MAKE) cvs-clean + rm -f bashlib-$(VERSION).tar bashlib-$(VERSION).tar.gz diff --git a/bashlib.in b/bashlib similarity index 99% rename from bashlib.in rename to bashlib index 22671e5..80fd56d 100755 --- a/bashlib.in +++ b/bashlib @@ -1,4 +1,4 @@ -#!@BASH@ +#!/bin/bash # Author: darren chamberlain # Co-Author: Paul Bournival @@ -14,8 +14,6 @@ # param()/cookie() used to spawn env|grep|sed|cut on every call, and the # URL decoding used to fork once per %XX escape. -PATH=/bin:/usr/bin - # # Set version number # Must be an integer because bash cannot compare float numbers diff --git a/configure.in b/configure.in deleted file mode 100644 index 4d2bb24..0000000 --- a/configure.in +++ /dev/null @@ -1,47 +0,0 @@ -dnl configure.in for bashlib -dnl ---------------------------------------------------------------------- -dnl $Id$ -dnl ---------------------------------------------------------------------- -dnl bashlib -dnl Copyright (C) 2002-2005 darren chamberlain -dnl Copyright (C) 2018-2024 Mikhail Novosyolov -dnl -dnl This program is free software; you can redistribute it and/or modify -dnl it under the terms of the GNU General Public License as published by -dnl the Free Software Foundation; version 2. -dnl -dnl This program is distributed in the hope that it will be useful, but -dnl WITHOUT ANY WARRANTY; without even the implied warranty of -dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -dnl General Public License for more details. -dnl -dnl You should have received a copy of the GNU General Public License -dnl along with this program; if not, write to the Free Software -dnl Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -dnl USA -dnl ---------------------------------------------------------------------- - -AC_REVISION($Revision$) - -dnl bashlib version -AC_SUBST(bashlib_version)dnl -bashlib_version=0.5 - -AC_INIT(bashlib.in) - -AC_PATH_PROG(BASH, bash, "") -AC_PATH_PROG(AWK, awk, "") -AC_PATH_PROG(CAT, cat, "") -AC_PATH_PROG(CUT, cut, "") -AC_PATH_PROG(CP, cp, "") -AC_PATH_PROG(ENV, env, "") -AC_PATH_PROG(GREP, grep, "") -AC_PATH_PROG(GZIP, gzip, "") -AC_PATH_PROG(INSTALL, install, "") -AC_PATH_PROG(MKDIR, mkdir, "") -AC_PATH_PROG(RM, rm, "") -AC_PATH_PROG(SED, sed, "") -AC_PATH_PROG(TAR, tar, "") -AC_PATH_PROG(TR, tr, "") - -AC_OUTPUT(Makefile bashlib) diff --git a/run_tests.sh b/run_tests.sh index 33e8895..684796b 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -17,7 +17,7 @@ here=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) || exit 1 LIB=$here/bashlib if [ ! -f "$LIB" ]; then - echo "bashlib not found at $LIB -- run ./configure first" >&2 + echo "bashlib not found at $LIB -- broken checkout?" >&2 exit 1 fi From e2fbaca5f1db0dc403887094562cc96d7c6992fd Mon Sep 17 00:00:00 2001 From: Mikhail Novosyolov Date: Sun, 6 Sep 2026 18:02:20 +0300 Subject: [PATCH 18/18] Release version 4 Co-authored-by: Z.AI GLM --- INSTALL | 2 +- Makefile | 2 +- bashlib | 2 +- run_tests.sh | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/INSTALL b/INSTALL index 9ff5cba..6ea8e81 100644 --- a/INSTALL +++ b/INSTALL @@ -1,4 +1,4 @@ -bashlib 2 +bashlib 4 bashlib is a bash library: there is nothing to configure and nothing to build, the checked-out file is the finished library (it calls no diff --git a/Makefile b/Makefile index 5524b9e..d1063b9 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ # tools, so the checked-out file is the finished library. PREFIX ?= /usr/local DESTDIR ?= -VERSION = 2 +VERSION = 4 all: @echo "nothing to build; try 'make check' or 'make install'" diff --git a/bashlib b/bashlib index 80fd56d..ce700b7 100755 --- a/bashlib +++ b/bashlib @@ -18,7 +18,7 @@ # Set version number # Must be an integer because bash cannot compare float numbers # -VERSION="2" +VERSION="4" # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- # Initialization stuff begins here. These things run immediately, and diff --git a/run_tests.sh b/run_tests.sh index 684796b..5f52d09 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -84,11 +84,11 @@ echo # --- version ------------------------------------------------------------ bl 'version' -ok 'version prints name and release' 'bashlib, version 2\n' +ok 'version prints name and release' 'bashlib, version 4\n' bl 'version_html' ok 'version_html prints html link and version' \ - 'bashlib,version 2\n' + 'bashlib,version 4\n' # --- GET parameter parsing ----------------------------------------------