Skip to content

Instantly share code, notes, and snippets.

@TylerWhittaker
Last active October 27, 2024 08:21
Show Gist options
  • Save TylerWhittaker/f0e2393b0a911696ca33cce66debcf0e to your computer and use it in GitHub Desktop.
Save TylerWhittaker/f0e2393b0a911696ca33cce66debcf0e to your computer and use it in GitHub Desktop.
Bash HTTP(S) download script (pure bash HTTP download for systems without curl/wget/fetch)
#!/bin/bash
#
# bash_wget.sh
#
# Tyler Whittaker <[email protected]>
#
# BASH HTTP(S) DOWNLOAD SCRIPT
# - downloads a file from HTTP server in almost pure bash (no curl/wget required)
# - requires `openssl` binary for HTTPS download (alternatively, just use curl/wget)
#
# usage: `bash_wget.sh http://www.website.farts/coolfile.zip`
#
# (intended as a fallback for systems without wget/curl/fetch)
# exit immediately on failure
set -e
# enable this to see each command as it executes (i.e. debug mode)
#set -x
# check if URL argument is provided
if [ $# -ne 1 ]; then
echo "error: please provide exactly one URL as an argument"
echo "usage: $0 <url>"
exit 1
fi
url="$1"
# validate URL format (basic check)
if [[ ! $url =~ ^https?:// ]]; then
echo "error: Invalid URL format. URL must start with http:// or https://"
exit 1
fi
# extract filename from URL
filename=$(basename "$url")
# if filename is empty or contains only special characters, use a default name
if [ -z "$filename" ] || [[ "$filename" =~ ^[[:punct:]]+$ ]]; then
filename="downloaded_file"
fi
echo "> downloading $url to $filename..."
# parse URL into components
proto=${url%%://*}
temp=${url#*://}
host=${temp%%/*}
uri=${temp#*/}
# set default port based on protocol
if [ "$proto" = "https" ]; then
port=443
else
port=80
fi
# extract port if specified in URL
if [[ $host =~ : ]]; then
port=${host#*:}
host=${host%:*}
fi
# function to properly format HTTP request with correct line endings
format_request() {
printf "GET /%s HTTP/1.1\r\n" "$uri"
printf "Host: %s\r\n" "$host"
printf "User-Agent: Bash-Download-Script\r\n"
printf "Accept: */*\r\n"
printf "Connection: close\r\n"
printf "\r\n"
}
# function to extract content after headers
extract_content() {
local input_file="$1"
local output_file="$2"
local start_line
# find the empty line that separates headers from content
start_line=$(awk '/^\r$/ {print NR; exit}' "$input_file")
if [ -n "$start_line" ]; then
# add 1 to skip the empty line itself
start_line=$((start_line + 1))
# extract everything after the headers
tail -n +"$start_line" "$input_file" > "$output_file"
return 0
else
return 1
fi
}
# function to handle HTTPS connections
handle_https() {
local temp_file=$(mktemp)
local temp_content=$(mktemp)
# send request and receive response using openssl with proper SSL options
(format_request; sleep 2) | \
openssl s_client -connect "${host}:${port}" \
-quiet \
-no_ssl3 \
-no_tls1 \
-no_tls1_1 \
-ign_eof 2>/dev/null > "$temp_file"
# check if we received any data
if [ ! -s "$temp_file" ]; then
rm -f "$temp_file" "$temp_content"
return 1
fi
# check status code
local status_code
status_code=$(head -n 1 "$temp_file" | grep -o '[0-9]\{3\}')
if [ "$status_code" != "200" ]; then
echo "error: server returned status code $status_code"
rm -f "$temp_file" "$temp_content"
return 1
fi
# extract content after headers
if ! extract_content "$temp_file" "$temp_content"; then
echo "error: failed to extract content from response"
rm -f "$temp_file" "$temp_content"
return 1
fi
# move content to final destination
mv "$temp_content" "$filename"
rm -f "$temp_file"
return 0
}
# function to handle HTTP connections
handle_http() {
local temp_file=$(mktemp)
local temp_content=$(mktemp)
# create TCP connection and send request
exec 3<>/dev/tcp/"$host"/"$port"
format_request >&3
cat <&3 > "$temp_file"
exec 3>&-
exec 3<&-
# check if we received any data
if [ ! -s "$temp_file" ]; then
rm -f "$temp_file" "$temp_content"
return 1
fi
# check status code
local status_code
status_code=$(head -n 1 "$temp_file" | grep -o '[0-9]\{3\}')
if [ "$status_code" != "200" ]; then
echo "error: server returned status code $status_code"
cat "$temp_file" >&2
rm -f "$temp_file" "$temp_content"
return 1
fi
# extract content after headers
if ! extract_content "$temp_file" "$temp_content"; then
echo "error: failed to extract content from response"
rm -f "$temp_file" "$temp_content"
return 1
fi
# move content to final destination
mv "$temp_content" "$filename"
rm -f "$temp_file"
return 0
}
# download file based on protocol
if [ "$proto" = "https" ]; then
if ! command -v openssl >/dev/null; then
echo "error: OpenSSL is required for HTTPS downloads"
exit 1
fi
if ! handle_https; then
echo "error: HTTPS download failed"
exit 1
fi
else
if ! handle_http; then
echo "error: HTTP download failed"
exit 1
fi
fi
# verify download was successful
if [ -s "$filename" ]; then
echo "download completed successfully"
exit 0
else
echo "error: download produced empty file"
rm -f "$filename"
exit 1
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment