Skip to content

Instantly share code, notes, and snippets.

@stephancasas
Last active September 29, 2022 19:51
Show Gist options
  • Save stephancasas/692949c76fcd425514fd4b3e20694016 to your computer and use it in GitHub Desktop.
Save stephancasas/692949c76fcd425514fd4b3e20694016 to your computer and use it in GitHub Desktop.
straycat.sh — a minimal HTTP server script for netcat.
#!/bin/sh
# ------------------------------------------------------------------------------
# Straycat.sh HTTP Server (straycat)
# ------------------------------------------------------------------------------
#
# Author:
# Stephan Casas <[email protected]>
#
# Description:
# Straycat.sh (straycat) is designed to receive HTTP payloads over netcat TCP
# sessions — removing the need to install a dedicated HTTP server like Apache
# or Lighttpd, where such an installation would be unfeasible, or excessive
# to the objective or available resources.
#
# Received payloads are digested into their respective fundamental units and
# exposed as script variables which may then be piped or handled according to
# user-defined server logic.
#
# The set of commands used by straycat is minimal, and should be supported by
# most, if not all, Docker images — including base Alpine Linux.
#
# Use with netcat:
# nc -lk -p 4444 -e /opt/straycat.sh # listen for HTTP on port 4444
#
# Server Logic Example:
# echo $__METHOD >&2 # echo the HTTP method to the netcat console
# echo $__PATH >&2 # echo the request path to the netcat console
# echo $__BODY >&2 # echo the request body to the netcat console
#
# Tips:
# * Define your server logic on line 144 -- after all variables are hydrated.
# * Variables are prefixed with a double underscore (e.g. $__BODY, $__PATH).
# * Use stderr (see examples above) to log to console. Printing to stdout will
# send a response to the HTTP client.
# * Your response is sent and the HTTP connection ends once you `echo` or
# `printf` on stdout to the client.
# * Always send a valid HTTP response (with headers) to avoid client errors.
# ---------------------------------------------------------------------------- #
# --- Request Timeout Config ------------------------------------------------- #
_TIMEOUT=1 # how long should the server wait for new bytes to buffer?
# --- Request Header Digest -------------------------------------------------- #
_BUF=""
_LAST=""
_SET="METHOD"
_SUBSET=""
while read -r -t $_TIMEOUT -n1 CHAR; do
_CLEAR=false
STASH=""
_CODE=$(printf %d \'$CHAR)
if [ "$_CODE" = "0" ]; then
if [ "$_LAST" != "13" ]; then
if [ "$_SET" = "METHOD" ]; then
__METHOD="$_BUF"
_SET="PATH"
_CLEAR=true
fi
if [ "$_SET" = "PATH" ] && ! $_CLEAR; then
__PATH="$_BUF"
_SET="PROTOCOL"
_CLEAR=true
fi
if [ "$_SET" = "HEADER" ] && [ -z "$_BUF" ] && ! $_CLEAR; then
# ignore the first space after a header definition
STASH=" "
else
_BUF="$_BUF "
fi
fi
else
if [ "$_CODE" = "13" ]; then
if [ "$_SET" = "PROTOCOL" ] && ! $_CLEAR; then
__PROTOCOL="$_BUF"
_SET="HEADER"
_CLEAR=true
fi
if [ "$_SET" = "HEADER" ] && ! $_CLEAR; then
if [ -z "$_SUBSET" ]; then
_SET="BODY"
_CLEAR=true
# skip next empty line
read
else
_SUBSET=$(echo "$_SUBSET" |
awk '{gsub(/-/, "_")}{print toupper($0)}')
eval "__HEADER_$_SUBSET"="\"$_BUF\""
_SET="HEADER"
_SUBSET=""
_CLEAR=true
fi
fi
_BUF="$_BUF$(printf '\n\r')"
else
if [ "$_CODE" = "58" ]; then
if [ "$_SET" = "HEADER" ]; then
if [ -z "$_SUBSET" ]; then
_SUBSET=$_BUF
_CLEAR=true
fi
fi
fi
_BUF="$_BUF$CHAR"
fi
fi
__REQUEST="$__REQUEST$STASH"
$_CLEAR && __REQUEST="$__REQUEST$_BUF"
$_CLEAR && _BUF=""
_LAST="$_CODE"
[ "$_SET" = "BODY" ] && break
done
# --- Request Body Digest ---------------------------------------------------- #
__BODY=""
_OFFSET=0
while [ ${#__BODY} -lt $__HEADER_CONTENT_LENGTH ]; do
[ ! -z "$__BODY" ] && __BODY="$__BODY$(printf '\n\r')"
eval "read -r -n$((($__HEADER_CONTENT_LENGTH + $_OFFSET) - ${#__BODY})) \
-t $_TIMEOUT _CHARS"
_OFFSET=$(($_OFFSET + 1))
__BODY="$__BODY$_CHARS"
done
__REQUEST="$__REQUEST$__BODY"
# --- Server Logic ----------------------------------------------------------- #
echo "$__BODY" >&2 # example -- echo request body to console
# --- Response --------------------------------------------------------------- #
echo "HTTP/1.1 202 Accepted
Content-Length: 2
Content-Type: text/plain
Server: straycat
OK"
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment