Search code examples
linuxbashbinary-data

Corrupt png or jpeg image files when stored in bash variable


I am trying to write an httpserver using netcat. It doesn't work I dont know why ? when I remove ' read -s GET ', it works for plain text but for images it doesn't, the browser displays only the icon for broken images.

nc -l -k -p 80 -q 1 -e serverhttp.sh

serverhttp.sh

#!/bin/bash

index_page=index.html
error_page=Error.html

read -s GET # it doesn't work with this commond to be able to detect path.
resource=$(echo "$input" | grep -P -o '(?<=GET \/).*(?=\ )')
[ -z "$resource" ] && resource=$index_page || resource=$error_page

content_type=$(file -b --mime-type $resource)

case "$(echo "$content_type" | cut -d '/' -f2)" in
html)
    output=$(cat "$resource")
;;

jpeg)
    output=$(cat "$resource")
;;

png)
    output=$(cat "$resource")
;;

*)
    echo 'Unknown type'
esac

content_length=$(stat -c '%s' $resource)
echo -ne "HTTP 200 OK\r\n"
echo -ne "Content-Length: $content_length\r\n"
echo -ne "Content-Type: $content_type\r\n\n$output\n"

Solution

  • The reason it does not work with image files is because these are binary data and the Shell cannot store binary into variables like here:

    output=$(cat "$resource")
    

    Also as stated by Shawn, here there are code pitfalls that would be shown to you with a code check from https://shellcheck.net/

    Here is how you could deal with binary files:

    #!/usr/bin/env bash
    
    # Function to stream a binary file as HTTP body
    send_body () {
      printf '\r\n'
      cat "$1" 2>/dev/null || : # Can stream binary data
      printf '\r\n'
    }
    
    input=$1
    
    index_page=index.html
    error_page=Error.html
    
    # Parses the request string with Bash's built-in Regex engine
    [[ "$input" =~ ^GET[[:space:]]/(.*)/?[[:space:]]HTTP/1\.[01]?$ ]] || :
    resource=${BASH_REMATCH[1]}
    
    # When resource is empty, substitute the value of $index_page
    resource=${resource:-$index_page}
    
    # Capture mime type
    content_type=$(file -b --mime-type "$resource")
    
    # Set http status code depending on if resource file exist
    if [ -f "$resource" ]; then
      http_status='200 OK'
    else
      resource="$error_page"
      content_type='text/html'
      http_status='404 NOT FOUND'
    fi
    
    # Check supported MIME types
    case "${content_type#*/}" in
      html | jpeg | png) ;;
      *)
        printf '%s\n' 'Unknown type' >&2
        resource="$error_page"
        content_type='text/html'
        http_status='415 UNSUPPORTED MEDIA TYPE'
        ;;
    esac
    
    # Capture the content_length (default to 0)
    read -r content_length _ < <(wc -c "$resource" 2>/dev/null)
    content_length=${content_length:-0}
    
    # Send reply
    printf 'HTTP %s\r\n' "$http_status"
    printf 'Content-Length: %d\r\n' "$content_length"
    printf 'Content-Type: %s\r\n' "$content_type"
    send_body "$resource"