Last active
October 27, 2024 08:21
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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