diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b855143 --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +CC= gcc +CFLAGS= -g -Wall -Werror -std=gnu99 -Iinclude +LD= gcc +LDFLAGS= -Llib +AR= ar +ARFLAGS= rcs +TARGETS= bin/spidey + +all: $(TARGETS) + +clean: + @echo Cleaning... + @rm -f $(TARGETS) lib/*.a src/*.o *.log *.input + +.PHONY: all test clean + +# TODO: Add rules for bin/spidey, lib/libspidey.a, and any intermediate objects +# +src/%.o: src/%.c + $(CC) $(CFLAGS) -c -o $@ $^ + +bin/spidey: src/spidey.o lib/libspidey.a + $(LD) $(LDFLAGS) -o $@ $^ + +lib/libspidey.a: src/forking.o src/handler.o src/request.o src/single.o src/socket.o src/utils.o + $(AR) $(ARFLAGS) $@ $^ diff --git a/README.md b/README.md index d96d5eb..7d9e332 100644 --- a/README.md +++ b/README.md @@ -1 +1,27 @@ -# httpserver-systems \ No newline at end of file +# Project - README + +This is the [Final Project] for [CSE 20289 Systems Programming (Spring 2020)]. + +## Members + +- Flahavan Abbott (fabbott@nd.edu) + +## Demonstration + +- [Link to Demonstration Video](https://drive.google.com/open?id=1vE4CxTGxSZD_WydFzxRkI1AHwhYR10RP) + +## Errata + +Everything works right now. I had the hardest times writing handle browse request +and all of the functions in request but once I got those the rest flowed easily. +I currently think the handle_browse_request() function looks messy right now because +of all of the checks I must perform in there for the thumbnails guru point. Sorry +for the long video I tried to cut it down. + + +## Contributions + +I did everything because I worked by myself. I completed the thumbnails guru point and the vps guru point as shown.. + +[Final Project]: https://www3.nd.edu/~pbui/teaching/cse.20289.sp20/project.html +[CSE 20289 Systems Programming (Spring 2020)]: https://www3.nd.edu/~pbui/teaching/cse.20289.sp20/ diff --git a/bin/spidey b/bin/spidey new file mode 100755 index 0000000..d0a266b Binary files /dev/null and b/bin/spidey differ diff --git a/bin/test_spidey.sh b/bin/test_spidey.sh new file mode 100755 index 0000000..780ede2 --- /dev/null +++ b/bin/test_spidey.sh @@ -0,0 +1,402 @@ +#!/bin/bash + +PROGRAM=spidey +WORKSPACE=/tmp/$PROGRAM.$(id -u) +FAILURES=0 + +# Functions + +error() { + echo "$@" + [ -r $WORKSPACE/test ] && (echo; cat $WORKSPACE/test; echo) + FAILURES=$((FAILURES + 1)) +} + +cleanup() { + STATUS=${1:-$FAILURES} + rm -fr $WORKSPACE + exit $STATUS +} + +check_status() { + if [ $1 -ne $2 ]; then + echo "FAILURE: exit status $1 != $2" > $WORKSPACE/test + return 1; + fi + + return 0; +} + +check_md5sum() { + cksum=$(md5sum $WORKSPACE/test | awk '{print $1}') + if [ $cksum != $1 ]; then + echo "FAILURE: md5sum $cksum != $1" > $WORKSPACE/test + return 1; + fi +} + +check_header() { + status=$(head -n 1 $WORKSPACE/header | tr -d '\r\n') + content=$(awk '/Content/ { print $2 }' $WORKSPACE/header | tr -d '\r\n') + if [ "$status" != "$1" ]; then + echo "FAILURE: $status != $1" > $WORKSPACE/test + return 1; + fi + if [ "$content" != "$2" ]; then + echo "FAILURE: content-type: $content != $2" > $WORKSPACE/test + return 1; + fi +} + +grep_all() { + for pattern in $1; do + if ! grep -q -E "$pattern" $2; then + echo "FAILURE: Missing '$pattern' in '$2'" > $WORKSPACE/test + return 1; + fi + done + return 0; +} + +grep_count() { + if [ $(grep -i -c $1 $WORKSPACE/test) -ne $2 ]; then + echo "FAILURE: $1 count != $2" > $WORKSPACE/test + return 1; + fi + return 0; +} + +check_hrefs() { + if [ "$(sed -En 's/.*a href="([^"]+)".*/\1/p' $WORKSPACE/test | sort | paste -s -d ,)" != $1 ]; then + echo "FAILURE: hrefs != $1" > $WORKSPACE/test + return 1; + fi +} + +# Setup + +mkdir $WORKSPACE + +trap "cleanup" EXIT +trap "cleanup 1" INT TERM + +# Testing + +# ------------------------------------------------------------------------------ + +echo +cowsay -W 72 < $WORKSPACE/test +if ! check_status $? 0 || ! grep_all ".. html scripts text" $WORKSPACE/test || ! check_hrefs $HREFS || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi + +sleep 1 + +printf " %-60s ... " "/html" +HREFS="/html/..,/html/index.html" +curl -s -D $WORKSPACE/header $HOST:$PORT/html > $WORKSPACE/test +if ! check_status $? 0 || ! grep_all ".. index.html" $WORKSPACE/test || ! check_hrefs $HREFS || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi + +sleep 1 + +printf " %-60s ... " "/images" +HREFS="/images/..,/images/a.png,/images/b.jpg,/images/c.jpg,/images/d.png" +curl -s -D $WORKSPACE/header $HOST:$PORT/images > $WORKSPACE/test +if ! check_status $? 0 || ! grep_all ".. a.png b.jpg c.jpg d.png" $WORKSPACE/test || ! check_hrefs $HREFS || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi + +sleep 1 + +printf " %-60s ... " "/scripts" +HREFS="/scripts/..,/scripts/cowsay.sh,/scripts/env.sh,/scripts/hello.py" +curl -s -D $WORKSPACE/header $HOST:$PORT/scripts > $WORKSPACE/test +if ! check_status $? 0 || ! grep_all ".. cowsay.sh env.sh" $WORKSPACE/test || ! check_hrefs $HREFS || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi + +sleep 1 + +printf " %-60s ... " "/text" +HREFS="/text/..,/text/hackers.txt,/text/lyrics.txt,/text/pass" +curl -s -D $WORKSPACE/header $HOST:$PORT/text > $WORKSPACE/test +if ! check_status $? 0 || ! grep_all ".. hackers.txt lyrics.txt" $WORKSPACE/test || ! check_hrefs $HREFS || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi + +printf " %-60s ... " "/text/pass" +HREFS="/text/pass/..,/text/pass/fail" +curl -s -D $WORKSPACE/header $HOST:$PORT/text/pass > $WORKSPACE/test +if ! check_status $? 0 || ! grep_all ".. fail" $WORKSPACE/test || ! check_hrefs $HREFS || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi + +# ------------------------------------------------------------------------------ + +printf "\n %-64s ... \n" "Handle File Requests" + +printf " %-60s ... " "/html/index.html" +MD5SUM=36fcc1da4afe58242350ee3940bb4220 +STATUS="HTTP/1.0 200 OK" +CONTENT="text/html" +curl -s -D $WORKSPACE/header $HOST:$PORT/html/index.html > $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "Spidey html thumbnail" $WORKSPACE/test || ! check_md5sum $MD5SUM || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi + +sleep 1 + +printf " %-60s ... " "/images/a.png" +MD5SUM=648cb635b64492a5d78041a8094a9df0 +CONTENT="image/png" +curl -s -D $WORKSPACE/header $HOST:$PORT/images/a.png > $WORKSPACE/test +if ! check_status $? 0 || ! check_md5sum $MD5SUM || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi + +sleep 1 + +printf " %-60s ... " "/images/b.jpg" +MD5SUM=7552baf02d08fb11a5f76677a9de6bb1 +CONTENT="image/jpeg" +curl -s -D $WORKSPACE/header $HOST:$PORT/images/b.jpg > $WORKSPACE/test +if ! check_status $? 0 || ! check_md5sum $MD5SUM || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi + +sleep 1 + +printf " %-60s ... " "/images/c.jpg" +MD5SUM=e165e1eb8fd07260db2c9bdb963cc91d +CONTENT="image/jpeg" +curl -s -D $WORKSPACE/header $HOST:$PORT/images/c.jpg > $WORKSPACE/test +if ! check_status $? 0 || ! check_md5sum $MD5SUM || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi + +sleep 1 + +printf " %-60s ... " "/images/d.png" +MD5SUM=575bfc9fec29c2d867a094da9eb929dc +CONTENT="image/png" +curl -s -D $WORKSPACE/header $HOST:$PORT/images/d.png > $WORKSPACE/test +if ! check_status $? 0 || ! check_md5sum $MD5SUM || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi + +sleep 1 + +printf " %-60s ... " "/text/hackers.txt" +MD5SUM=c77059544e187022e19b940d0c55f408 +CONTENT="text/plain" +curl -s -D $WORKSPACE/header $HOST:$PORT/text/hackers.txt > $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "criminal Damn kids beauty Mentor" $WORKSPACE/test || ! check_md5sum $MD5SUM || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi + +sleep 1 + +printf " %-60s ... " "/text/lyrics.txt" +MD5SUM=083de1aef4143f2ec2ef7269700a6f07 +curl -s -D $WORKSPACE/header $HOST:$PORT/text/lyrics.txt > $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "love me close eyes" $WORKSPACE/test || ! check_md5sum $MD5SUM || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi + +sleep 1 + +printf " %-60s ... " "/text/pass/fail" +MD5SUM=7fbdff94348a88b08e989f9ac66879ae +curl -s -D $WORKSPACE/header $HOST:$PORT/text/pass/fail > $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "justice" $WORKSPACE/test || ! check_md5sum $MD5SUM || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi + +sleep 1 + +printf " %-60s ... " "/song.txt" +MD5SUM=d073749ecc174b560cded952656a4f57 +curl -s -D $WORKSPACE/header $HOST:$PORT/song.txt > $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "Right void where" $WORKSPACE/test || ! check_md5sum $MD5SUM || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi + +sleep 1 + +# ------------------------------------------------------------------------------ + +printf "\n %-64s ... \n" "Handle CGI Requests" + +printf " %-60s ... " "/scripts/env.sh" +CONTENT="text/plain" +HEADERS="DOCUMENT_ROOT QUERY_STRING REMOTE_ADDR REMOTE_PORT REQUEST_METHOD REQUEST_URI SCRIPT_FILENAME SERVER_PORT HTTP_HOST HTTP_USER_AGENT" +curl -s -D $WORKSPACE/header $HOST:$PORT/scripts/env.sh > $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$HEADERS" $WORKSPACE/test || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi + +sleep 1 + +printf " %-60s ... " "/scripts/cowsay.sh" +MD5SUM=ddc37544d37e4ff1ca8c43eae6ff0f9d +CONTENT="text/html" +curl -s -D $WORKSPACE/header $HOST:$PORT/scripts/cowsay.sh > $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "Cowsay surgery daemon cheese sheep" $WORKSPACE/test || ! check_md5sum $MD5SUM || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi + +sleep 1 + +printf " %-60s ... " "/scripts/cowsay.sh?message=hi" +MD5SUM=4b88cc20abfb62fe435c55e98f23ff43 +CONTENT="text/html" +curl -s -D $WORKSPACE/header $HOST:$PORT/scripts/cowsay.sh?message=hi > $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "Cowsay surgery daemon cheese sheep" $WORKSPACE/test || ! check_md5sum $MD5SUM || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi + +sleep 1 + +printf " %-60s ... " "/scripts/cowsay.sh?message=hi&template=vader" +MD5SUM=91bd83301e691e52406f9bf8722ae5fc +CONTENT="text/html" +curl -s -D $WORKSPACE/header "$HOST:$PORT/scripts/cowsay.sh?message=hi&template=vader" > $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "Cowsay surgery daemon cheese sheep" $WORKSPACE/test || ! check_md5sum $MD5SUM || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi + +sleep 1 + +printf " %-60s ... " "/scripts/hello.py" +MD5SUM=f42061ae140a94c559a9d21bd48b9753 +CONTENT="text/html" +curl -s -D $WORKSPACE/header $HOST:$PORT/scripts/hello.py > $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "form input user" $WORKSPACE/test || ! check_md5sum $MD5SUM || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi + +sleep 1 + +printf " %-60s ... " "/scripts/hello.py?user=pparker" +MD5SUM=c8b21ed36d22e523d25715b62170a783 +CONTENT="text/html" +curl -s -D $WORKSPACE/header "$HOST:$PORT/scripts/hello.py?user=pparker" > $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "form input user Hello" $WORKSPACE/test || ! check_md5sum $MD5SUM || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi + +sleep 1 + +# ------------------------------------------------------------------------------ + +printf "\n %-64s ... \n" "Handle Errors" + +printf " %-60s ... " "/asdf" +STATUS="HTTP/1.0 404 Not Found" +CONTENT="text/html" +curl -s -D $WORKSPACE/header $HOST:$PORT/asdf > $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "404" $WORKSPACE/test || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi + +sleep 1 + +printf " %-60s ... " "Bad Request" +STATUS="HTTP/1.0 400 Bad Request" +CONTENT="text/html" +nc $HOST $PORT <<<"DERP" |& tee $WORKSPACE/test $WORKSPACE/header > /dev/null +if ! check_status $? 0 || ! grep_all "400" $WORKSPACE/test || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi + +sleep 1 + +printf " %-60s ... " "Bad Headers" +STATUS="HTTP/1.0 400 Bad Request" +CONTENT="text/html" +printf "GET / HTTP/1.0\r\nHost\r\n" | nc $HOST $PORT |& tee $WORKSPACE/test $WORKSPACE/header > /dev/null +if ! check_status $? 0 || ! grep_all "400" $WORKSPACE/test || ! check_header "$STATUS" "$CONTENT"; then + error "Failure" +else + echo "Success" +fi diff --git a/bin/test_thor.sh b/bin/test_thor.sh new file mode 100755 index 0000000..92c473d --- /dev/null +++ b/bin/test_thor.sh @@ -0,0 +1,346 @@ +#!/bin/bash + +PROGRAM=bin/thor.py +WORKSPACE=/tmp/$(basename $PROGRAM).$(id -u) +FAILURES=0 + +# Functions + +error() { + echo "$@" + [ -r $WORKSPACE/test ] && (echo; cat $WORKSPACE/test; echo) + FAILURES=$((FAILURES + 1)) +} + +cleanup() { + STATUS=${1:-$FAILURES} + rm -fr $WORKSPACE + exit $STATUS +} + +check_status() { + if [ $1 -ne $2 ]; then + echo "FAILURE: exit status $1 != $2" > $WORKSPACE/test + return 1; + fi + + return 0; +} + +check_md5sum() { + cksum=$(grep -E -v '^(Hammer|TOTAL)' $WORKSPACE/test | sed -E '/^\s*$/d' | md5sum | awk '{print $1}') + if [ $cksum != $1 ]; then + echo "FAILURE: md5sum $cksum != $1" > $WORKSPACE/test + return 1; + fi +} + +grep_all() { + for pattern in $1; do + if ! grep -q -E "$pattern" $2; then + echo "FAILURE: Missing '$pattern' in '$2'" > $WORKSPACE/test + return 1; + fi + done + return 0; +} + +grep_count() { + if [ $(grep -i -c $1 $WORKSPACE/test) -ne $2 ]; then + echo "FAILURE: $1 count != $2" > $WORKSPACE/test + return 1; + fi + return 0; +} + +# Setup + +mkdir $WORKSPACE + +trap "cleanup" EXIT +trap "cleanup 1" INT TERM + +# Testing + +echo "Testing $PROGRAM..." + +# ------------------------------------------------------------------------------ + +printf " %-64s ... " "Functions" +if ! grep_all "ProcessPoolExecutor requests.get map time.time" $PROGRAM; then + error "Failure" +else + echo "Success" +fi + +# ------------------------------------------------------------------------------ + +printf "\n %-64s\n" "Usage" + +printf " %-60s ... " "no arguments" +./$PROGRAM &> $WORKSPACE/test +if ! check_status $? 1 || ! grep_all "Usage" $WORKSPACE/test; then + error "Failure" +else + echo "Success" +fi + +printf " %-60s ... " "bad arguments" +./$PROGRAM -b -a -d &> $WORKSPACE/test +if ! check_status $? 1 || ! grep_all "Usage" $WORKSPACE/test; then + error "Failure" +else + echo "Success" +fi + +# ------------------------------------------------------------------------------ + +PATTERNS="Hammer Throw Elapsed Time TOTAL AVERAGE" + +printf "\n %-64s\n" "Single Hammer" + +DOMAIN=https://example.com +MD5SUM=c5953ba10795984694b107c66e922d51 +printf " %-60s ... " "$DOMAIN" +./$PROGRAM $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test; then + error "Failure" +else + echo "Success" +fi + +printf " %-60s ... " "$DOMAIN (-v)" +./$PROGRAM -v $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test || ! check_md5sum $MD5SUM; then + error "Failure" +else + echo "Success" +fi + +DOMAIN=https://yld.me +MD5SUM=7e198de49c3d9da2572573bbd115a15b +printf " %-60s ... " "$DOMAIN" +./$PROGRAM $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test; then + error "Failure" +else + echo "Success" +fi + +printf " %-60s ... " "$DOMAIN (-v)" +./$PROGRAM -v $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test || ! check_md5sum $MD5SUM; then + error "Failure" +else + echo "Success" +fi + +DOMAIN=https://yld.me/izE?raw=1 +MD5SUM=1877fcda6f85fa183220fdb47ecdbb9d +printf " %-60s ... " "$DOMAIN" +./$PROGRAM $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test; then + error "Failure" +else + echo "Success" +fi + +printf " %-60s ... " "$DOMAIN (-v)" +./$PROGRAM -v $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test || ! check_md5sum $MD5SUM; then + error "Failure" +else + echo "Success" +fi + +# ------------------------------------------------------------------------------ + +printf "\n %-64s\n" "Single Hammer, Multiple Throws" + +DOMAIN=https://example.com +MD5SUM=0d994deecb09db39fcc67c597e1ed920 +printf " %-60s ... " "$DOMAIN (-t 4)" +./$PROGRAM -t 4 $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test || \ + ! grep_count Hammer 5 || ! grep_count Throw 4; then + error "Failure" +else + echo "Success" +fi + +printf " %-60s ... " "$DOMAIN (-t 4 -v)" +./$PROGRAM -t 4 -v $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test || ! check_md5sum $MD5SUM || \ + ! grep_count Hammer 5 || ! grep_count Throw 4; then + error "Failure" +else + echo "Success" +fi + +DOMAIN=https://yld.me +MD5SUM=fcd87a5d4bc539b052fe86fdaf769b59 +printf " %-60s ... " "$DOMAIN (-t 4)" +./$PROGRAM -t 4 $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test || \ + ! grep_count Hammer 5 || ! grep_count Throw 4; then + error "Failure" +else + echo "Success" +fi + +printf " %-60s ... " "$DOMAIN (-t 4 -v)" +./$PROGRAM -t 4 -v $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test || ! check_md5sum $MD5SUM || \ + ! grep_count Hammer 5 || ! grep_count Throw 4; then + error "Failure" +else + echo "Success" +fi + +DOMAIN=https://yld.me/izE?raw=1 +MD5SUM=a44c9b1df78e3656398db9ba92548a56 +printf " %-60s ... " "$DOMAIN (-t 4)" +./$PROGRAM -t 4 $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test || \ + ! grep_count Hammer 5 || ! grep_count Throw 4; then + error "Failure" +else + echo "Success" +fi + +printf " %-60s ... " "$DOMAIN (-t 4 -v)" +./$PROGRAM -t 4 -v $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test || ! check_md5sum $MD5SUM || \ + ! grep_count Hammer 5 || ! grep_count Throw 4; then + error "Failure" +else + echo "Success" +fi + +# ------------------------------------------------------------------------------ + +printf "\n %-64s\n" "Multiple Hammers" + +DOMAIN=https://example.com +MD5SUM=354446ae72c1a63f18abf3ca9ddffb70 +printf " %-60s ... " "$DOMAIN (-h 2)" +./$PROGRAM -h 2 $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test || \ + ! grep_count Hammer 4 || ! grep_count Throw 2; then + error "Failure" +else + echo "Success" +fi + +printf " %-60s ... " "$DOMAIN (-h 2 -v)" +./$PROGRAM -h 2 -v $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test || ! check_md5sum $MD5SUM || \ + ! grep_count Hammer 4 || ! grep_count Throw 2; then + error "Failure" +else + echo "Success" +fi + +DOMAIN=https://yld.me +MD5SUM=285a6a2a8365ebae8681967d0b7b9083 +printf " %-60s ... " "$DOMAIN (-h 2)" +./$PROGRAM -h 2 $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test || \ + ! grep_count Hammer 4 || ! grep_count Throw 2; then + error "Failure" +else + echo "Success" +fi + +printf " %-60s ... " "$DOMAIN (-h 2 -v)" +./$PROGRAM -h 2 -v $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test || ! check_md5sum $MD5SUM || \ + ! grep_count Hammer 4 || ! grep_count Throw 2; then + error "Failure" +else + echo "Success" +fi + +DOMAIN=https://yld.me/izE?raw=1 +MD5SUM=9df2453cf04874e6ca1124f8e49f2051 +printf " %-60s ... " "$DOMAIN (-h 2)" +./$PROGRAM -h 2 $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test || \ + ! grep_count Hammer 4 || ! grep_count Throw 2; then + error "Failure" +else + echo "Success" +fi + +printf " %-60s ... " "$DOMAIN (-p 2 -v)" +./$PROGRAM -h 2 -v $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test || ! check_md5sum $MD5SUM || \ + ! grep_count Hammer 4 || ! grep_count Throw 2; then + error "Failure" +else + echo "Success" +fi + +# ------------------------------------------------------------------------------ + +printf "\n %-64s\n" "Multiple Hammers, Multiple Throws" + +DOMAIN=https://example.com +MD5SUM=7043520023714e026f42e5dc97997770 +printf " %-60s ... " "$DOMAIN (-h 2 -t 4)" +./$PROGRAM -h 2 -t 4 $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test || \ + ! grep_count Hammer 10 || ! grep_count Throw 8; then + error "Failure" +else + echo "Success" +fi + +printf " %-60s ... " "$DOMAIN (-h 2 -t 4 -v)" +./$PROGRAM -h 2 -t 4 -v $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test || ! check_md5sum $MD5SUM || \ + ! grep_count Hammer 10 || ! grep_count Throw 8; then + error "Failure" +else + echo "Success" +fi + +DOMAIN=https://yld.me +MD5SUM=6aeb58b41d0bb41abedbe7f6649f39d9 +printf " %-60s ... " "$DOMAIN (-h 2 -t 4)" +./$PROGRAM -h 2 -t 4 $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test || \ + ! grep_count Hammer 10 || ! grep_count Throw 8; then + error "Failure" +else + echo "Success" +fi + +printf " %-60s ... " "$DOMAIN (-h 2 -t 4 -v)" +./$PROGRAM -h 2 -t 4 -v $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test || ! check_md5sum $MD5SUM || \ + ! grep_count Hammer 10 || ! grep_count Throw 8; then + error "Failure" +else + echo "Success" +fi + +DOMAIN=https://yld.me/izE?raw=1 +MD5SUM=ce4d6bbaca157f968e6e57aa084e6ec1 +printf " %-60s ... " "$DOMAIN (-h 2 -t 4)" +./$PROGRAM -h 2 -t 4 $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test || \ + ! grep_count Hammer 10 || ! grep_count Throw 8; then + error "Failure" +else + echo "Success" +fi + +printf " %-60s ... " "$DOMAIN (-h 2 -t 4 -v)" +./$PROGRAM -h 2 -t 4 -v $DOMAIN &> $WORKSPACE/test +if ! check_status $? 0 || ! grep_all "$PATTERNS" $WORKSPACE/test || ! check_md5sum $MD5SUM || \ + ! grep_count Hammer 10 || ! grep_count Throw 8; then + error "Failure" +else + echo "Success" +fi diff --git a/bin/thor.py b/bin/thor.py new file mode 100755 index 0000000..15bf749 --- /dev/null +++ b/bin/thor.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 + +import concurrent.futures +import os +import requests +import sys +import time + +# Functions + +def usage(status=0): + progname = os.path.basename(sys.argv[0]) + print(f'''Usage: {progname} [-h HAMMERS -t THROWS] URL + -h HAMMERS Number of hammers to utilize (1) + -t THROWS Number of throws per hammer (1) + -v Display verbose output + ''') + sys.exit(status) + +def hammer(url, throws, verbose, hid): + ''' Hammer specified url by making multiple throws (ie. HTTP requests). + + - url: URL to request + - throws: How many times to make the request + - verbose: Whether or not to display the text of the response + - hid: Unique hammer identifier + + Return the average elapsed time of all the throws. + ''' + throw = 0 + avgTime = 0 + while throw < throws: + start = time.time() + response = requests.get(url) + if response and verbose: + print(f'{response.text}') + elif not response: + print('Request failed') + + singleTime = time.time() - start + avgTime = avgTime + singleTime + print(f'Hammer: {hid}, Throw: {throw}, Elapsed Time: {round(singleTime, 2)}') + + throw = throw + 1 + + avgTime = avgTime / throws + + print(f'Hammer: {hid}, AVERAGE , Elapsed Time: {round(avgTime,2)}') + + + return avgTime + +def do_hammer(args): + ''' Use args tuple to call `hammer` ''' + return hammer(*args) + +def main(): + hammers = 1 + throws = 1 + verbose = False + flags = {'-v', '-t', '-h'} + url = None + + # Parse command line arguments + if len(sys.argv)>1: + arguments = sys.argv[1:] + else: + usage(1) + + arg = 0 + while arg < len(arguments): + if arguments[arg] in flags: + if(arguments[arg] == '-v'): + verbose = True + elif(arguments[arg] == '-t'): + throws = int(arguments[arg+1]) + arg+=1 + elif(arguments[arg] == '-h'): + hammers = int(arguments[arg+1]) + arg+=1 + else: + if(arguments[arg][0] == '-'): + usage(1) + else: + url = arguments[arg] + arg+=1 + + if(hammers > 1): + args = ((url, throws, verbose, hid) for hid in range(hammers)) + + # Create pool of workers and perform throws + with concurrent.futures.ProcessPoolExecutor(hammers) as executor: + results = executor.map(do_hammer, args) + + avgTime = 0 + for result in results: + avgTime = avgTime + result + avgTime = avgTime / hammers + else: + avgTime = hammer(url, throws, verbose, 0) + + print(f'TOTAL AVERAGE ELAPSED TIME: {round(avgTime,2)}') +# Main execution + +if __name__ == '__main__': + main() + +# vim: set sts=4 sw=4 ts=8 expandtab ft=python: diff --git a/include/spidey.h b/include/spidey.h new file mode 100644 index 0000000..f9f7215 --- /dev/null +++ b/include/spidey.h @@ -0,0 +1,102 @@ +/* spidey.h: Spidey HTTP Server */ + +#pragma once + +#include +#include +#include + + +#include +#include + +/* Constants */ + +#define WHITESPACE " \t\n" + +/** + * Concurrency modes + */ +typedef enum { + SINGLE, /**< Single connection */ + FORKING, /**< Process per connection */ + UNKNOWN +} ServerMode; + +/* Global Variables */ + +extern char *Port; /**< Port number */ +extern char *MimeTypesPath; /**< Path to mime.types file */ +extern char *DefaultMimeType; /**< Default file mimetype */ +extern char *RootPath; /**< Path to root directory */ + +/* Logging Macros */ + +#ifdef NDEBUG +#define debug(M, ...) +#else +#define debug(M, ...) fprintf(stderr, "[%5d] DEBUG %10s:%-4d " M "\n", getpid(), __FILE__, __LINE__, ##__VA_ARGS__) +#endif + +#define fatal(M, ...) fprintf(stderr, "[%5d] FATAL %10s:%-4d " M "\n", getpid(), __FILE__, __LINE__, ##__VA_ARGS__); exit(EXIT_FAILURE) +#define log(M, ...) fprintf(stderr, "[%5d] LOG %10s:%-4d " M "\n", getpid(), __FILE__, __LINE__, ##__VA_ARGS__) + +/* HTTP Request */ + +typedef struct header Header; +struct header { + char *name; /*< Name of header entry */ + char *data; /*< Data of header entry */ + Header *next; /*< Next header entry */ +}; + +typedef struct { + int fd; /*< Client socket file descripter */ + FILE *stream; /*< Client socket file stream */ + char *method; /*< HTTP method */ + char *uri; /*< HTTP uniform resource identifier */ + char *path; /*< Real path corrsponding to URI and RootPath */ + char *query; /*< HTTP query string */ + + char host[NI_MAXHOST]; /*< Host name of client */ + char port[NI_MAXSERV]; /*< Port number of client */ + + Header *headers; /*< List of name, data Header pairs */ +} Request; + +Request * accept_request(int sfd); +void free_request(Request *request); +int parse_request(Request *request); + +/* HTTP Request Handlers */ + +typedef enum { + HTTP_STATUS_OK = 0, /* 200 OK */ + HTTP_STATUS_BAD_REQUEST, /* 400 Bad Request */ + HTTP_STATUS_NOT_FOUND, /* 404 Not Found */ + HTTP_STATUS_INTERNAL_SERVER_ERROR, /* 500 Internal Server Error */ +} Status; + +Status handle_request(Request *request); + +/* HTTP Server */ + +int single_server(int sfd); +int forking_server(int sfd); + +/* Socket */ + +int socket_listen(const char *port); + +/* Utilities */ + +#define chomp(s) (s)[strlen(s) - 1] = '\0' +#define streq(a, b) (strcmp((a), (b)) == 0) + +char * determine_mimetype(const char *path); +char * determine_request_path(const char *uri); +const char *http_status_string(Status status); +char * skip_nonwhitespace(char *s); +char * skip_whitespace(char *s); + +/* vim: set expandtab sts=4 sw=4 ts=8 ft=c: */ diff --git a/lib/.gitkeep b/lib/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/libspidey.a b/lib/libspidey.a new file mode 100644 index 0000000..b130f8b Binary files /dev/null and b/lib/libspidey.a differ diff --git a/src/forking.c b/src/forking.c new file mode 100644 index 0000000..6aca73c --- /dev/null +++ b/src/forking.c @@ -0,0 +1,50 @@ +/* forking.c: Forking HTTP Server */ + +#include "spidey.h" + +#include +#include +#include + +#include + +/** + * Fork incoming HTTP requests to handle the concurrently. + * + * @param sfd Server socket file descriptor. + * @return Exit status of server (EXIT_SUCCESS). + * + * The parent should accept a request and then fork off and let the child + * handle the request. + **/ +int forking_server(int sfd) { + /* Accept and handle HTTP request */ + while (true) { + /* Accept request */ + Request *request = accept_request(sfd); + if(!request){ + log("Unable to accept request: %s", strerror(errno)); + continue; + } + + /* Ignore children */ + signal(SIGCHLD, SIG_IGN); + /* Fork off child process to handle request */ + pid_t pid = fork(); + if(pid==0){ + handle_request(request); + free_request(request); + exit(EXIT_SUCCESS); + }else{ + free_request(request); + } + + + } + + /* Close server socket */ + close(sfd); + return EXIT_SUCCESS; +} + +/* vim: set expandtab sts=4 sw=4 ts=8 ft=c: */ diff --git a/src/forking.o b/src/forking.o new file mode 100644 index 0000000..2d624fa Binary files /dev/null and b/src/forking.o differ diff --git a/src/handler.c b/src/handler.c new file mode 100644 index 0000000..7c5899f --- /dev/null +++ b/src/handler.c @@ -0,0 +1,279 @@ +/* handler.c: HTTP Request Handlers */ + +#include "spidey.h" + +#include +#include +#include + +#include +#include +#include + +/* Internal Declarations */ +Status handle_browse_request(Request *request); +Status handle_file_request(Request *request); +Status handle_cgi_request(Request *request); +Status handle_error(Request *request, Status status); + +/** + * Handle HTTP Request. + * + * @param r HTTP Request structure + * @return Status of the HTTP request. + * + * This parses a request, determines the request path, determines the request + * type, and then dispatches to the appropriate handler type. + * + * On error, handle_error should be used with an appropriate HTTP status code. + **/ +Status handle_request(Request *r) { + Status result; + + /* Parse request */ + if(parse_request(r) != 0){ + return handle_error(r, HTTP_STATUS_BAD_REQUEST); + } + + /* Determine request path */ + r->path = determine_request_path(r->uri); + debug("HTTP REQUEST PATH: %s", r->path); + + /* Dispatch to appropriate request handler type based on file type */ + + struct stat s; + if(r->path && stat(r->path, &s) == 0){ + if(S_ISDIR(s.st_mode)){ + result = handle_browse_request(r); + }else if(S_ISREG(s.st_mode)){ + if(access(r->path, X_OK)==0){ + + result = handle_cgi_request(r); + }else{ + result = handle_file_request(r); + } + } + }else{ + result = handle_error(r, HTTP_STATUS_NOT_FOUND); + } + + + log("HTTP REQUEST STATUS: %s", http_status_string(result)); + + return result; +} + +/** + * Handle browse request. + * + * @param r HTTP Request structure. + * @return Status of the HTTP browse request. + * + * This lists the contents of a directory in HTML. + * + * If the path cannot be opened or scanned as a directory, then handle error + * with HTTP_STATUS_NOT_FOUND. + **/ +Status handle_browse_request(Request *r) { + char *mimetype = "\0"; + struct dirent **entries; + int n; + + /* Open a directory for reading or scanning */ + n = scandir(r->path, &entries, 0, alphasort); + if(n<0){ + return handle_error(r,HTTP_STATUS_NOT_FOUND); + } + + /* Write HTTP Header with OK Status and text/html Content-Type */ + fprintf(r->stream, "HTTP/1.0 200 OK\r\n"); + fprintf(r->stream, "Content-Type: text/html\r\n"); + fprintf(r->stream, "\r\n"); + + /* For each entry in directory, emit HTML list item */ + fprintf(r->stream, "
    \n"); + for(int i = 0; i < n; i++){ + if(streq(entries[i]->d_name, ".")){ + free(entries[i]); + i++; + } + if(streq(entries[i]->d_name, "..")){ + if(strchr(r->uri, '/') == r->uri+strlen(r->uri)-1){ + fprintf(r->stream, "
  • %s
  • \n", r->uri, entries[i]->d_name, entries[i]->d_name); + + }else{ + fprintf(r->stream, "
  • %s
  • \n", r->uri, entries[i]->d_name, entries[i]->d_name); + } + + free(entries[i]); + i++; + } + if(access(entries[i]->d_name, X_OK)==0){ + mimetype = "\0"; + }else{ + mimetype = determine_mimetype(entries[i]->d_name); + } + if(strchr(r->uri, '/') == r->uri+strlen(r->uri)-1){ + if(streq(mimetype, "image/png") || streq(mimetype, "image/jpeg")){ + fprintf(r->stream, "
  • %s
  • \n", r->uri, entries[i]->d_name, r->uri, entries[i]->d_name, entries[i]->d_name); + + }else{ + fprintf(r->stream, "
  • %s
  • \n", r->uri, entries[i]->d_name, entries[i]->d_name); + } + }else{ + if(streq(mimetype, "image/png") || streq(mimetype, "image/jpeg")){ + fprintf(r->stream, "
  • %s
  • \n", r->uri, entries[i]->d_name, r->uri, entries[i]->d_name, entries[i]->d_name); + + }else{ + fprintf(r->stream, "
  • %s
  • \n", r->uri, entries[i]->d_name, entries[i]->d_name); + } + + } + + free(mimetype); + free(entries[i]); + } + free(entries); + fprintf(r->stream,"
\n"); + + + /* Return OK */ + return HTTP_STATUS_OK; +} + +/** + * Handle file request. + * + * @param r HTTP Request structure. + * @return Status of the HTTP file request. + * + * This opens and streams the contents of the specified file to the socket. + * + * If the path cannot be opened for reading, then handle error with + * HTTP_STATUS_NOT_FOUND. + **/ +Status handle_file_request(Request *r) { + FILE *fs; + char buffer[BUFSIZ]; + char *mimetype = "\0"; + size_t nread; + Status status = HTTP_STATUS_OK; + + /* Open file for reading */ + fs = fopen(r->path, "r"); + if(!fs){ + status = HTTP_STATUS_NOT_FOUND; + goto fail; + } + /* Determine mimetype */ + + mimetype = determine_mimetype(r->uri); + + /* Write HTTP Headers with OK status and determined Content-Type */ + + fprintf(r->stream, "HTTP/1.0 200 OK\r\n"); + fprintf(r->stream, "Content-Type: %s\r\n", mimetype); + fprintf(r->stream, "\r\n"); + /* Read from file and write to socket in chunks */ + nread = fread(buffer, 1, BUFSIZ, fs); + while(nread > 0){ + fwrite(buffer, 1, nread, r->stream); + nread = fread(buffer, 1, BUFSIZ, fs); + } + /* Close file, deallocate mimetype, return OK */ + fclose(fs); + free(mimetype); + return status; + +fail: + /* Close file, free mimetype, return INTERNAL_SERVER_ERROR */ + free(mimetype); + fclose(fs); + return handle_error(r, status); +} + +/** + * Handle CGI request + * + * @param r HTTP Request structure. + * @return Status of the HTTP file request. + * + * This popens and streams the results of the specified executables to the + * socket. + * + * If the path cannot be popened, then handle error with + * HTTP_STATUS_INTERNAL_SERVER_ERROR. + **/ +Status handle_cgi_request(Request *r) { + FILE *pfs; + char buffer[BUFSIZ]; + + /* Export CGI environment variables from request: + * http://en.wikipedia.org/wiki/Common_Gateway_Interface */ + setenv("QUERY_STRING", r->query, 1); + setenv("DOCUMENT_ROOT", RootPath, 1); + setenv("REMOTE_ADDR", r->host, 1); + setenv("REMOTE_PORT", r->port, 1); + setenv("REQUEST_METHOD", r->method, 1); + setenv("REQUEST_URI", r->uri, 1); + setenv("SCRIPT_FILENAME", r->path, 1); + setenv("SERVER_PORT", Port, 1); + + + /* Export CGI environment variables from request headers */ + for(Header *header = r->headers; header; header = header->next){ + if(streq(header->name, "Host")){ + setenv("HTTP_HOST", header->data, 1); + }else if(streq(header->name, "User-Agent")){ + setenv("HTTP_USER_AGENT", header->data, 1); + } + + } + + /* POpen CGI Script */ + + pfs = popen(r->path, "r"); + if(!pfs){ + return handle_error(r, HTTP_STATUS_INTERNAL_SERVER_ERROR); + } + + /* Copy data from popen to socket */ + size_t nread = fread(buffer, 1, BUFSIZ, pfs); + while(nread > 0){ + fwrite(buffer, 1, nread, r->stream); + nread = fread(buffer, 1, BUFSIZ, pfs); + } + + + /* Close popen, return OK */ + pclose(pfs); + return HTTP_STATUS_OK; +} + +/** + * Handle displaying error page + * + * @param r HTTP Request structure. + * @return Status of the HTTP error request. + * + * This writes an HTTP status error code and then generates an HTML message to + * notify the user of the error. + **/ +Status handle_error(Request *r, Status status) { + const char *status_string = http_status_string(status); + + /* Write HTTP Header */ + fprintf(r->stream, "HTTP/1.0 %s\r\n", status_string); + fprintf(r->stream, "Content-Type: text/html\r\n"); + fprintf(r->stream, "\r\n"); + + /* Write HTML Description of Error*/ + fprintf(r->stream, "

%s

\n", status_string); + fprintf(r->stream, "
\n"); + + /* Return specified status */ + + return status; +} + +/* vim: set expandtab sts=4 sw=4 ts=8 ft=c: */ diff --git a/src/handler.o b/src/handler.o new file mode 100644 index 0000000..ab82d23 Binary files /dev/null and b/src/handler.o differ diff --git a/src/request.c b/src/request.c new file mode 100644 index 0000000..5e2b590 --- /dev/null +++ b/src/request.c @@ -0,0 +1,294 @@ +/* request.c: HTTP Request Functions */ + +#include "spidey.h" + +#include +#include + +#include + +int parse_request_method(Request *r); +int parse_request_headers(Request *r); + +/** + * Accept request from server socket. + * + * @param sfd Server socket file descriptor. + * @return Newly allocated Request structure. + * + * This function does the following: + * + * 1. Allocates a request struct initialized to 0. + * 2. Initializes the headers list in the request struct. + * 3. Accepts a client connection from the server socket. + * 4. Looks up the client information and stores it in the request struct. + * 5. Opens the client socket stream for the request struct. + * 6. Returns the request struct. + * + * The returned request struct must be deallocated using free_request. + **/ +Request * accept_request(int sfd) { + + /* Allocate request struct (zeroed) */ + Request *r = calloc(1, sizeof(Request)); + if(!r){ + debug("Unable to allocate request: %s", strerror(errno)); + return NULL; + } + + /* Accept a client */ + struct sockaddr raddr; + socklen_t rlen = sizeof(struct sockaddr); + + r->fd = accept(sfd, &raddr, &rlen); + if(r->fd < 0){ + debug("Unable to accept: %s", strerror(errno)); + goto fail; + } + + /* Lookup client information */ + int status = getnameinfo(&raddr, rlen, r->host, sizeof(r->host), r->port, sizeof(r->port), NI_NUMERICHOST | NI_NUMERICSERV); + if(status < 0){ + debug("Unable to getnameinfo: %s", gai_strerror(status)); + goto fail; + } + + /* Open socket stream */ + r->stream = fdopen(r->fd, "w+"); + if(!r->stream){ + debug("Unable to fdopen: %s", strerror(errno)); + goto fail; + } + + log("Accepted request from %s:%s", r->host, r->port); + return r; + +fail: + /* Deallocate request struct */ + free_request(r); + return NULL; +} + +/** + * Deallocate request struct. + * + * @param r Request structure. + * + * This function does the following: + * + * 1. Closes the request socket stream or file descriptor. + * 2. Frees all allocated strings in request struct. + * 3. Frees all of the headers (including any allocated fields). + * 4. Frees request struct. + **/ +void free_request(Request *r) { + if (!r) { + return; + } + /* Close socket or fd */ + if(r->stream){ + fclose(r->stream); + }else{ + close(r->fd); + } + + /* Free allocated strings */ + if(r->method) + free(r->method); + if(r->uri) + free(r->uri); + if(r->path) + free(r->path); + if(r->query) + free(r->query); + + + /* Free Headers */ + if(r->headers){ + + for(Header *header = r->headers; header; header = header->next){ + free(header->name); + free(header->data); + } + + Header *curr = r->headers; + Header *next = r->headers->next; + while(next){ + free(curr); + curr = next; + next = next->next; + } + free(curr); + } + + /* Free request */ + free(r); +} + +/** + * Parse HTTP Request. + * + * @param r Request structure. + * @return -1 on error and 0 on success. + * + * This function first parses the request method, any query, and then the + * headers, returning 0 on success, and -1 on error. + **/ +int parse_request(Request *r) { + /* Parse HTTP Request Method */ + if(parse_request_method(r) != 0){ + return -1; + } + /* Parse HTTP Requet Headers*/ + if(parse_request_headers(r) != 0){ + return -1; + } + + return 0; +} + +/** + * Parse HTTP Request Method and URI. + * + * @param r Request structure. + * @return -1 on error and 0 on success. + * + * HTTP Requests come in the form + * + * [QUERY] HTTP/ + * + * Examples: + * + * GET / HTTP/1.1 + * GET /cgi.script?q=foo HTTP/1.0 + * + * This function extracts the method, uri, and query (if it exists). + **/ +int parse_request_method(Request *r) { + char buffer[BUFSIZ]; + char *method; + char *uri; + char *query; + + /* Read line from socket */ + if(!fgets(buffer, BUFSIZ, r->stream)){ + goto fail; + } + + /* Parse method and uri */ + method = strtok(buffer, WHITESPACE); + + uri = strtok(NULL, WHITESPACE); + if(!method || !uri){ + goto fail; + } + + /* Parse query from uri */ + query = strchr(uri, '?'); + + if(query){ + *query++ = '\0'; + }else{ + query = ""; + } + /* Record method, uri, and query in request struct */ + r->method = strdup(method); + r->uri = strdup(uri); + r->query = strdup(query); + + + debug("HTTP METHOD: %s", r->method); + debug("HTTP URI: %s", r->uri); + debug("HTTP QUERY: %s", r->query); + + return 0; + +fail: + return -1; +} + +/** + * Parse HTTP Request Headers. + * + * @param r Request structure. + * @return -1 on error and 0 on success. + * + * HTTP Headers come in the form: + * + * : + * + * Example: + * + * Host: localhost:8888 + * User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:29.0) Gecko/20100101 Firefox/29.0 + * Accept: text/html,application/xhtml+xml + * Accept-Language: en-US,en;q=0.5 + * Accept-Encoding: gzip, deflate + * Connection: keep-alive + * + * This function parses the stream from the request socket using the following + * pseudo-code: + * + * while (buffer = read_from_socket() and buffer is not empty): + * name, data = buffer.split(':') + * header = new Header(name, data) + * headers.append(header) + **/ +int parse_request_headers(Request *r) { + Header *curr = calloc(1,sizeof(Header)); + if(!curr){ + return -1; + } + char buffer[BUFSIZ]; + char *name = "\0"; + char *data = "\0"; + + /* Parse headers from socket */ + if(!(fgets(buffer, BUFSIZ, r->stream) && strlen(buffer) > 2)){ + goto fail; + }else{ + + name = strtok(buffer, ":"); + if(!name) + goto fail; + data = skip_whitespace(strtok(NULL, "\n")); + if(!data) + goto fail; + curr->name = strdup(name); + curr->data = strdup(data); + + r->headers = curr; + curr = r->headers; + while(fgets(buffer, BUFSIZ, r->stream) && strlen(buffer) > 2){ + curr->next = calloc(1, sizeof(Header)); + if(!curr->next){ + return -1; + } + curr = curr->next; + name = strtok(buffer, ":"); + if(!name) + goto fail; + data = skip_whitespace(strtok(NULL, "\n")); + if(!data) + goto fail; + + curr->name = strdup(name); + curr->data = strdup(data); + } + } + + + +#ifndef NDEBUG + for (Header *header = r->headers; header; header = header->next) { + debug("HTTP HEADER %s = %s", header->name, header->data); + } +#endif + return 0; + +fail: + free(curr); + return -1; +} + +/* vim: set expandtab sts=4 sw=4 ts=8 ft=c: */ diff --git a/src/request.o b/src/request.o new file mode 100644 index 0000000..def4070 Binary files /dev/null and b/src/request.o differ diff --git a/src/single.c b/src/single.c new file mode 100644 index 0000000..5f74776 --- /dev/null +++ b/src/single.c @@ -0,0 +1,37 @@ +/* single.c: Single User HTTP Server */ + +#include "spidey.h" + +#include +#include + +#include + +/** + * Handle one HTTP request at a time. + * + * @param sfd Server socket file descriptor. + * @return Exit status of server (EXIT_SUCCESS). + **/ +int single_server(int sfd) { + /* Accept and handle HTTP request */ + while (true) { + /* Accept request */ + Request *request = accept_request(sfd); + if(!request){ + log("Unable to accept request: %s", strerror(errno)); + continue; + } + + /* Handle request */ + handle_request(request); + /* Free request */ + free_request(request); + } + + /* Close server socket */ + close(sfd); + return EXIT_SUCCESS; +} + +/* vim: set expandtab sts=4 sw=4 ts=8 ft=c: */ diff --git a/src/single.o b/src/single.o new file mode 100644 index 0000000..cd05e04 Binary files /dev/null and b/src/single.o differ diff --git a/src/socket.c b/src/socket.c new file mode 100644 index 0000000..e9fcc84 --- /dev/null +++ b/src/socket.c @@ -0,0 +1,65 @@ +/* socket.c: Simple Socket Functions */ + +#include "spidey.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +/** + * Allocate socket, bind it, and listen to specified port. + * + * @param port Port number to bind to and listen on. + * @return Allocated server socket file descriptor. + **/ +int socket_listen(const char *port) { + /* Lookup server address information */ + struct addrinfo *results; + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_flags = AI_PASSIVE + }; + + int status = getaddrinfo(NULL, port, &hints, &results); + if(status != 0){ + fprintf(stderr, "getaddrinfo failed: %s\n", gai_strerror(status)); + return -1; + } + + /* For each server entry, allocate socket and try to connect */ + int socket_fd = -1; + for (struct addrinfo *p = results; p != NULL && socket_fd < 0; p = p->ai_next) { + /* Allocate socket */ + socket_fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if(socket_fd < 0){ + fprintf(stderr, "Unable to make socket: %s\n", strerror(errno)); + continue; + } + + /* Bind socket */ + if(bind(socket_fd, p->ai_addr, p->ai_addrlen)<0){ + close(socket_fd); + socket_fd = -1; + continue; + } + + /* Listen to socket */ + if(listen(socket_fd, SOMAXCONN) < 0){ + close(socket_fd); + socket_fd = -1; + continue; + } + } + + freeaddrinfo(results); + return socket_fd; +} + +/* vim: set expandtab sts=4 sw=4 ts=8 ft=c: */ diff --git a/src/socket.o b/src/socket.o new file mode 100644 index 0000000..def7506 Binary files /dev/null and b/src/socket.o differ diff --git a/src/spidey.c b/src/spidey.c new file mode 100644 index 0000000..7e11bb3 --- /dev/null +++ b/src/spidey.c @@ -0,0 +1,124 @@ +/* spidey: Simple HTTP Server */ + +#include "spidey.h" + +#include +#include +#include + +#include + +/* Global Variables */ +char *Port = "9898"; +char *MimeTypesPath = "/etc/mime.types"; +char *DefaultMimeType = "text/plain"; +char *RootPath = "www"; + +/** + * Display usage message and exit with specified status code. + * + * @param progname Program Name + * @param status Exit status. + */ +void usage(const char *progname, int status) { + fprintf(stderr, "Usage: %s [hcmMpr]\n", progname); + fprintf(stderr, "Options:\n"); + fprintf(stderr, " -h Display help message\n"); + fprintf(stderr, " -c mode Single or Forking mode\n"); + fprintf(stderr, " -m path Path to mimetypes file\n"); + fprintf(stderr, " -M mimetype Default mimetype\n"); + fprintf(stderr, " -p port Port to listen on\n"); + fprintf(stderr, " -r path Root directory\n"); + exit(status); +} + +/** + * Parse command-line options. + * + * @param argc Number of arguments. + * @param argv Array of argument strings. + * @param mode Pointer to ServerMode variable. + * @return true if parsing was successful, false if there was an error. + * + * This should set the mode, MimeTypesPath, DefaultMimeType, Port, and RootPath + * if specified. + */ +bool parse_options(int argc, char *argv[], ServerMode *mode) { + int argind = 1; + while (argind < argc && strlen(argv[argind]) > 1 && argv[argind][0] == '-') { + char *arg = argv[argind++]; + switch (arg[1]) { + case 'c': + if (streq(argv[argind], "single")) { + *mode = SINGLE; + } else if (streq(argv[argind], "forking")) { + *mode = FORKING; + } else { + return false; + } + argind++; + break; + case 'h': + usage(argv[0], EXIT_SUCCESS); + break; + case 'm': + MimeTypesPath = argv[argind++]; + break; + case 'M': + DefaultMimeType = argv[argind++]; + break; + case 'p': + Port = argv[argind++]; + break; + case 'r': + RootPath = argv[argind++]; + break; + default: + return false; + break; + } + } + + return true; +} + +/** + * Parses command line options and starts appropriate server + **/ +int main(int argc, char *argv[]) { + ServerMode mode; + + /* Parse command line options */ + if(!parse_options(argc, argv, &mode)){ + usage(argv[0], 0); + } + + /* Listen to server socket */ + int server_fd = socket_listen(Port); + if(server_fd < 0){ + return EXIT_FAILURE; + } + + /* Determine real RootPath */ + char *path = realpath(RootPath, NULL); + if(!path){ + debug("ERROR: Could not find real rootpath"); + } + RootPath = path; + log("Listening on port %s", Port); + debug("RootPath = %s", RootPath); + debug("MimeTypesPath = %s", MimeTypesPath); + debug("DefaultMimeType = %s", DefaultMimeType); + debug("ConcurrencyMode = %s", mode == SINGLE ? "Single" : "Forking"); + + /* Start either forking or single HTTP server */ + if(mode == SINGLE){ + single_server(server_fd); + }else{ + forking_server(server_fd); + } + free(RootPath); + return 0; +} + +/* vim: set expandtab sts=4 sw=4 ts=8 ft=c: */ diff --git a/src/spidey.o b/src/spidey.o new file mode 100644 index 0000000..5ae1002 Binary files /dev/null and b/src/spidey.o differ diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..eefdf4a --- /dev/null +++ b/src/utils.c @@ -0,0 +1,169 @@ +/* utils.c: spidey utilities */ + +#include "spidey.h" + +#include +#include +#include + +#include +#include + +/** + * Determine mime-type from file extension. + * + * @param path Path to file. + * @return An allocated string containing the mime-type of the specified file. + * + * This function first finds the file's extension and then scans the contents + * of the MimeTypesPath file to determine which mimetype the file has. + * + * The MimeTypesPath file (typically /etc/mime.types) consists of rules in the + * following format: + * + * ... + * + * This function simply checks the file extension version each extension for + * each mimetype and returns the mimetype on the first match. + * + * If no extension exists or no matching mimetype is found, then return + * DefaultMimeType. + * + * This function returns an allocated string that must be free'd. + **/ +char * determine_mimetype(const char *path) { + char *ext; + char *mimetype; + char *temp; + char *token; + char buffer[BUFSIZ]; + FILE *fs = NULL; + + /* Find file extension */ + ext = strchr(path, '.'); + if(!ext){ + return strdup(DefaultMimeType); + }else{ + ext++; + } + + /* Open MimeTypesPath file */ + fs = fopen("/etc/mime.types", "r"); + if(!fs){ + return strdup(DefaultMimeType); + } + + /* Scan file for matching file extensions */ + int n = 1; + while(fgets(buffer, BUFSIZ, fs)){ + if(n<14){ + n++; + }else{ + temp = strtok(buffer, WHITESPACE); + if(!temp){ + continue; + } + mimetype = strdup(temp); + + token = strtok(NULL, WHITESPACE); + while(token){ + token = skip_whitespace(token); + if(streq(token, ext)){ + fclose(fs); + return mimetype; + } + token = strtok(NULL, WHITESPACE); + } + free(mimetype); + } + } + fclose(fs); + return strdup(DefaultMimeType); +} + +/** + * Determine actual filesystem path based on RootPath and URI. + * + * @param uri Resource path of URI. + * @return An allocated string containing the full path of the resource on the + * local filesystem. + * + * This function uses realpath(3) to generate the realpath of the + * file requested in the URI. + * + * As a security check, if the real path does not begin with the RootPath, then + * return NULL. + * + * Otherwise, return a newly allocated string containing the real path. This + * string must later be free'd. + **/ +char * determine_request_path(const char *uri) { + char buffer[BUFSIZ]; + sprintf(buffer, "%s%s", RootPath, uri); + char *path = realpath(buffer, NULL); + if(!path){ + return NULL; + } + size_t size = strlen(RootPath); + if(strncmp(path, RootPath, size) != 0){ + return NULL; + } + return path; +} + +/** + * Return static string corresponding to HTTP Status code. + * + * @param status HTTP Status. + * @return Corresponding HTTP Status string (or NULL if not present). + * + * http://en.wikipedia.org/wiki/List_of_HTTP_status_codes + **/ +const char * http_status_string(Status status) { + static char *StatusStrings[] = { + "200 OK", + "400 Bad Request", + "404 Not Found", + "500 Internal Server Error", + "418 I'm A Teapot", + }; + if(status < (sizeof(StatusStrings)/sizeof(char *))){ + return StatusStrings[status]; + }else{ + return NULL; + } +} + +/** + * Advance string pointer pass all nonwhitespace characters + * + * @param s String. + * @return Point to first whitespace character in s. + **/ +char * skip_nonwhitespace(char *s) { + if(!s){ + return NULL; + } + while(!isspace((int)(*s))){ + s++; + } + return s; +} + +/** + * Advance string pointer pass all whitespace characters + * + * @param s String. + * @return Point to first non-whitespace character in s. + **/ +char * skip_whitespace(char *s) { + if(!s){ + return NULL; + } + while(isspace((int)(*s))){ + s++; + } + return s; +} + +/* vim: set expandtab sts=4 sw=4 ts=8 ft=c: */ diff --git a/src/utils.o b/src/utils.o new file mode 100644 index 0000000..97d7030 Binary files /dev/null and b/src/utils.o differ diff --git a/test_latency.sh b/test_latency.sh new file mode 100755 index 0000000..ed8450b --- /dev/null +++ b/test_latency.sh @@ -0,0 +1,49 @@ +#!/bin/sh + +if [ "$#" -lt 1 ]; then + echo "Usage: test_latency.sh http://{HOST}:{PORT}" + exit 1 +fi + +echo "AVERAGE LATENCY TEST: TIME(SEC)" +echo "" + +PART=`./bin/thor.py $1 -t 5 -h 5 | grep "TIME" | sed -En "s/^.*([0-9]+\.[0-9]+)$/\1/p"` +echo "Average Latency for DIRECTORY LISTING /WWW: $PART" + +PART=`./bin/thor.py $1/html -t 5 -h 5 | grep "TIME" | sed -En "s/^.*([0-9]+\.[0-9]+)$/\1/p"` +echo "Average Latency for DIRECTORY LISTING /WWW/HTML: $PART" + +PART=`./bin/thor.py $1/images -t 5 -h 5 | grep "TIME" | sed -En "s/^.*([0-9]+\.[0-9]+)$/\1/p"` +echo "Average Latency for DIRECTORY LISTING /WWW/IMAGES: $PART" + +PART=`./bin/thor.py $1/scripts -t 5 -h 5 | grep "TIME" | sed -En "s/^.*([0-9]+\.[0-9]+)$/\1/p"` +echo "Average Latency for DIRECTORY LISTING /WWW/SCRIPTS: $PART" + +echo "" + +PART=`./bin/thor.py $1/html/index.html -t 5 -h 5 | grep "TIME" | sed -En "s/^.*([0-9]+\.[0-9]+)$/\1/p"` +echo "Average Latency for STATIC FILES /WWW/HTML/index.html: $PART" + +PART=`./bin/thor.py $1/images/a.png -t 5 -h 5 | grep "TIME" | sed -En "s/^.*([0-9]+\.[0-9]+)$/\1/p"` +echo "Average Latency for STATIC FILES /WWW/IMAGES/a.png: $PART" + +PART=`./bin/thor.py $1/images/b.png -t 5 -h 5 | grep "TIME" | sed -En "s/^.*([0-9]+\.[0-9]+)$/\1/p"` +echo "Average Latency for STATIC FILES /WWW/IMAGES/b.png: $PART" + +PART=`./bin/thor.py $1/song.txt -t 5 -h 5 | grep "TIME" | sed -En "s/^.*([0-9]+\.[0-9]+)$/\1/p"` +echo "Average Latency for STATIC FILES /WWW/song.txt: $PART" + +echo"" + + +PART=`./bin/thor.py $1/scripts/hello.py -t 5 -h 5 | grep "TIME" | sed -En "s/^.*([0-9]+\.[0-9]+)$/\1/p"` +echo "Average Latency for CGI SCRIPTS /WWW/SCRIPTS/hello.py: $PART" + +PART=`./bin/thor.py $1/scripts/cowsay.sh -t 5 -h 5 | grep "TIME" | sed -En "s/^.*([0-9]+\.[0-9]+)$/\1/p"` +echo "Average Latency for CGI SCRIPTS /WWW/SCRIPTS/cowsay.sh: $PART" + +PART=`./bin/thor.py $1/scripts/env.sh -t 5 -h 5 | grep "TIME" | sed -En "s/^.*([0-9]+\.[0-9]+)$/\1/p"` +echo "Average Latency for CGI SCRIPTS /WWW/SCRIPTS/env.sh: $PART" + +exit 0 diff --git a/test_throughput.sh b/test_throughput.sh new file mode 100755 index 0000000..dd85af3 --- /dev/null +++ b/test_throughput.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +if [ "$#" -lt 1 ]; then + echo "Usage: test_throughput.sh http://{HOST}:{PORT}" + exit 1 +fi + +echo "AVERAGE THROUGHPUT TEST: KB/SEC" +echo "" + +PART=`./bin/thor.py $1/images/a.png -t 5 -h 5 | grep "TIME" | sed -En "s/^.*([0-9]+\.[0-9]+)$/\1/p"` +SIZE=`du -sk www/images/a.png | cut -f 1` +RESULT=`bc -l <<<"${SIZE}/${PART}"` +echo "Average Throughput for MEDIUM FILE /WWW/IMAGES/a.png: `echo "scale=2; $RESULT / 1" | bc`" +echo "SIZE: $SIZE kb" +echo "TIME: $PART sec" + +PART=`./bin/thor.py $1/images/b.png -t 5 -h 5 | grep "TIME" | sed -En "s/^.*([0-9]+\.[0-9]+)$/\1/p"` +SIZE=`du -sk www/images/b.jpg | cut -f 1` +RESULT=`bc -l <<<"${SIZE}/${PART}"` +echo "Average Throughput for MEDIUM FILE /WWW/IMAGES/b.jpg: `echo "scale=2; $RESULT / 1" | bc`" +echo "SIZE: $SIZE kb" +echo "TIME: $PART sec" + +PART=`./bin/thor.py $1/song.txt -t 5 -h 5 | grep "TIME" | sed -En "s/^.*([0-9]+\.[0-9]+)$/\1/p"` +SIZE=`du -sk www/song.txt | cut -f 1` +RESULT=`bc -l <<<"${SIZE}/${PART}"` +echo "Average Throughput for SMALL FILE /WWW/song.txt: `echo "scale=2; $RESULT / 1" | bc`" +echo "SIZE: $SIZE kb" +echo "TIME: $PART sec" + +PART=`./bin/thor.py $1/text/lyrics.txt -t 5 -h 5 | grep "TIME" | sed -En "s/^.*([0-9]+\.[0-9]+)$/\1/p"` +SIZE=`du -sk www/text/lyrics.txt | cut -f 1` +RESULT=`bc -l <<<"${SIZE}/${PART}"` +echo "Average Throughput for SMALL FILE /WWW/TEXT/lyrics.txt: `echo "scale=2; $RESULT / 1" | bc`" +echo "SIZE: $SIZE kb" +echo "TIME: $PART sec" + +exit 0 diff --git a/www/html/index.html b/www/html/index.html new file mode 100644 index 0000000..07b0ecb --- /dev/null +++ b/www/html/index.html @@ -0,0 +1,24 @@ + + + + + + + + + + Spidey + + + + +
+ +
+ +
+
+ + diff --git a/www/images/a.png b/www/images/a.png new file mode 100644 index 0000000..1dcb7ed Binary files /dev/null and b/www/images/a.png differ diff --git a/www/images/b.jpg b/www/images/b.jpg new file mode 100644 index 0000000..5e9cd29 Binary files /dev/null and b/www/images/b.jpg differ diff --git a/www/images/c.jpg b/www/images/c.jpg new file mode 100644 index 0000000..435fe24 Binary files /dev/null and b/www/images/c.jpg differ diff --git a/www/images/d.png b/www/images/d.png new file mode 100644 index 0000000..2350e0b Binary files /dev/null and b/www/images/d.png differ diff --git a/www/scripts/cowsay.sh b/www/scripts/cowsay.sh new file mode 100755 index 0000000..84d1b8a --- /dev/null +++ b/www/scripts/cowsay.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +if ! command -v cowsay > /dev/null; then + export PATH="~pbui/pub/pkgsrc/bin:$PATH" +fi + +echo "HTTP/1.0 200 OK" +echo "Content-type: text/html" +echo + +MESSAGE=$(echo $QUERY_STRING | sed -En 's|.*message=([^&]*).*|\1|p' | sed 's/+/ /g') +TEMPLATE=$(echo $QUERY_STRING | sed -En 's|.*template=([^&]*).*|\1|p' | sed 's/+/ /g') + +if [ -z "$TEMPLATE" ]; then + TEMPLATE=default +fi + +cat <Cowsay +
+
+ + +
+
+ +EOF + +#echo 1>&2 $QUERY_STRING +#echo 1>&2 $MESSAGE +#echo 1>&2 $TEMPLATE + +if [ -n "$MESSAGE" ]; then + cat < +$(cowsay -f "$TEMPLATE" "$MESSAGE") + +EOF + +fi diff --git a/www/scripts/env.sh b/www/scripts/env.sh new file mode 100755 index 0000000..ad1595f --- /dev/null +++ b/www/scripts/env.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +echo "HTTP/1.0 200 OK" +echo "Content-type: text/plain" +echo + +env | sort diff --git a/www/scripts/hello.py b/www/scripts/hello.py new file mode 100755 index 0000000..7ff31fc --- /dev/null +++ b/www/scripts/hello.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +import cgi + +print('HTTP/1.0 200 OK') +print('Content-Type: text/html') +print() + +form = cgi.FieldStorage() + +if 'user' in form: + print('

Hello, {}

'.format(form['user'].value)) + +print(''' +
+ + +
+''') diff --git a/www/scripts/test.sh b/www/scripts/test.sh new file mode 100755 index 0000000..c944f78 --- /dev/null +++ b/www/scripts/test.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +echo "HTTP/1.0 200 OK" +echo "Content-type: text/plain" +echo + +echo "HI" +echo $(./weather_2.sh) + diff --git a/www/scripts/weather.sh b/www/scripts/weather.sh new file mode 100755 index 0000000..1c23715 --- /dev/null +++ b/www/scripts/weather.sh @@ -0,0 +1,61 @@ +#!/bin/sh + +#Globals + +URL="https://forecast.weather.gov/zipcity.php" +ZIPCODE=46556 +FORECAST=0 +CELSIUS=0 + +# Functions + +usage() { + cat 1>&2 <' -f 2 | sed 's/&.*//' + else + weather_information | grep 'myforecast-current-lrg' | cut -d '>' -f 2 | sed 's/&.*//' + fi +} + +forecast() { + # Extract forecast information from weather source + weather_information | grep -E 'myforecast-current[^-]' | cut -d '>' -f 2 | sed 's/<.*//' +} + +# Parse Command Line Options + +while [ $# -gt 0 ]; do + case $1 in + -h) usage 0;; + -c) CELSIUS=1;; + -f) FORECAST=1;; + *) ZIPCODE=$1;; + esac + shift + done + +# Display Information + +if [ $FORECAST -eq 1 ]; then + echo "Forecast: $(forecast)" +fi +echo "Temperature: $(temperature) degrees" + diff --git a/www/scripts/weather/weather.sh b/www/scripts/weather/weather.sh new file mode 100755 index 0000000..1c23715 --- /dev/null +++ b/www/scripts/weather/weather.sh @@ -0,0 +1,61 @@ +#!/bin/sh + +#Globals + +URL="https://forecast.weather.gov/zipcity.php" +ZIPCODE=46556 +FORECAST=0 +CELSIUS=0 + +# Functions + +usage() { + cat 1>&2 <' -f 2 | sed 's/&.*//' + else + weather_information | grep 'myforecast-current-lrg' | cut -d '>' -f 2 | sed 's/&.*//' + fi +} + +forecast() { + # Extract forecast information from weather source + weather_information | grep -E 'myforecast-current[^-]' | cut -d '>' -f 2 | sed 's/<.*//' +} + +# Parse Command Line Options + +while [ $# -gt 0 ]; do + case $1 in + -h) usage 0;; + -c) CELSIUS=1;; + -f) FORECAST=1;; + *) ZIPCODE=$1;; + esac + shift + done + +# Display Information + +if [ $FORECAST -eq 1 ]; then + echo "Forecast: $(forecast)" +fi +echo "Temperature: $(temperature) degrees" + diff --git a/www/scripts/weather/weather_2.sh b/www/scripts/weather/weather_2.sh new file mode 100755 index 0000000..dc2a366 --- /dev/null +++ b/www/scripts/weather/weather_2.sh @@ -0,0 +1,56 @@ +#!/bin/sh + +# Globals +CELSIUS=0 +FORECAST=0 +CITY="Petersburg" +STATE="Alaska" + +# Functions + +usage() { + cat 1>&2 <&2 < +

Weather

+
+
+ + + +
+

The weather in $CITY is $WEATHER

+
+ +EOF + + +# echo "

$(./weather_2.sh -l "$CITY" "$STATE")

" + diff --git a/www/scripts/weather_2.sh b/www/scripts/weather_2.sh new file mode 100755 index 0000000..dc2a366 --- /dev/null +++ b/www/scripts/weather_2.sh @@ -0,0 +1,56 @@ +#!/bin/sh + +# Globals +CELSIUS=0 +FORECAST=0 +CITY="Petersburg" +STATE="Alaska" + +# Functions + +usage() { + cat 1>&2 <&2 <