From db0190b0ff7ebc1a88f0a9edd72e91a7f63b5dea Mon Sep 17 00:00:00 2001 From: sose Date: Wed, 16 Oct 2019 22:17:39 -0700 Subject: [PATCH] shi 2 --- .shi2.sh.swo | Bin 0 -> 20480 bytes .shi2.sh.swp | Bin 0 -> 24576 bytes README.md | 140 ++++++++++-- console.sh | 43 ++++ file_db_backend.sh | 157 +++++++++++++ globals.sh | 8 + http_backend.sh | 147 ++++++++++++ main.css | 108 +++++++++ post.sh | 3 + problems.txt | 11 + reset.sh | 5 + shi/README.md | 33 +++ shi.sh => shi/shi.sh | 75 +++--- shi2.sh | 364 ++++++++++++++++++++++++++++++ tar_backend.sh | 99 ++++++++ templates/index.html.template | 12 +- templates/main.css.template | 6 + templates/oppost-noimage.template | 2 +- templates/oppost.template | 6 +- templates/reply.template | 4 +- util.sh | 29 +++ 21 files changed, 1186 insertions(+), 66 deletions(-) create mode 100644 .shi2.sh.swo create mode 100644 .shi2.sh.swp create mode 100755 console.sh create mode 100755 file_db_backend.sh create mode 100755 globals.sh create mode 100755 http_backend.sh create mode 100755 main.css create mode 100755 post.sh create mode 100644 problems.txt create mode 100644 shi/README.md rename shi.sh => shi/shi.sh (84%) create mode 100755 shi2.sh create mode 100755 tar_backend.sh create mode 100755 util.sh diff --git a/.shi2.sh.swo b/.shi2.sh.swo new file mode 100644 index 0000000000000000000000000000000000000000..2e6ed7e6d55942eeb1e9173049324d7fcc65c410 GIT binary patch literal 20480 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 NE?NGnsSXX!zW@tb(ggqj literal 0 HcmV?d00001 diff --git a/.shi2.sh.swp b/.shi2.sh.swp new file mode 100644 index 0000000000000000000000000000000000000000..113fe301149b5ca322252fdd1137e1a229f31216 GIT binary patch literal 24576 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 G6ZGEzG{=(w literal 0 HcmV?d00001 diff --git a/README.md b/README.md index f9aadb5..6a9aaf8 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,131 @@ -#shi +# shi **SH**ell **I**mageboard -no php, no js, just POSIX shell, gnu coreutils, nc, html and css +the programming equivalent of a shitpost +(second edition) ### features +- its all POSIX sh + - seriously - posting - replies - images - timestamps - more -### to use it -- `git clone ` -- `cd ` -- `touch log.txt` -- `cp templates/mainindex.html ./index.html` -- point your web server to ./index.html -- to run shi - - `./shi.sh &` - - `tail -f log.txt` -- to make a board: - - `mkdir -p boards/` - - `cp templates/index.html.template boards//index.html` - - `cp templates/main.css.template boards//main.css` -- to stop shi - - `killall shi.sh` -- to post to shi - - copy the script at the top of the board into your terminal - - hit enter, follow the prompts +### how to set up +- clone this repo +- cd to this repo +- `. ./console.sh` to start the shi console + - `start_listener` to start lisenting for posts +- you might want to do `tail -f log.txt` in another terminal to see whats going on +### how backends work +- shi has a builtin framework for swappable backends both for post recieving and storage, +currently there is: + - tar backend + - like the old version of shi + - recives tar files through nc and generates posts + - bad + - dont use it + - http backend + - listens for an http POST request with form data + - allows user-friendly html post form + - cool + - good + - file db backend + - stores posts as a series of text files in folders + - unchanged from old shi + - ok +- **how backends work** + - **listening** + - a backend is a shell script with a `listen` function + - this function is expected to coninuosly output post data it recives like so: + ``` + New post + board: + parent: + user: + title: + content:<content> + image_name:<name of image> + image content:<base64 encoded image> + End new post + ``` + - as long as the backend has a listen function that outputs data to stdout like this, + shi doesn't care where it comes from. + - **db** + - a db backend is simply a shell script provinding the following funtions: + - `path_to_post <post id>` + - `create post <board> <parent> <user> <title> <content> <image_name> <image_content> <post_id> <post_type>` + - `get_post_data <post_data_path> <item> + - note that since these functions are both in the same file, `post_data_path` can be in any format you desire + - again, shi doesn't care how this is done, only that it follows this format + +### anatomy of a post +- user + - name of user who posted the post +- title + - title of post + - this will be ignored for replies +- content + - the text content of a post +- thumb_path + - optional, filesystem path to the thumbnail of the post's image +- image_path + - optional, filesystem path to the image +- html_path + - path to where the html of the post is stored +- post_id + - unique numerical id of the post +- post_type + - type of post, specifies what template to use + - currently there is 'oppost' and 'reply' supported +- replies + - newline separated list of post_id's for all the replies to the op post + - a trailing /. is added to prevent `find` from seeing `post_data_path` itself + +### defining new post templates +- edit your template of choice in the 'templates' folder, +- {{{ TRIPLE BRACES }}} will be replaced with their respective variables + +### style notes +- if it can be done in a single line, you can set a variable to it, +otherwise declare the variable as empty and then set it to your operation +- follow shellcheck + +# files n folders +- boards + - where all the html for the boards is stored +- console.sh + - souce this to allow you to control shi +- globals.sh + - global variables (really just settings) +- inbox + - where posts data is cached before being processed +- latest + - the id of the latest post + - very important!! +- LICENSE + - free software, free society +- log.txt + - log +- problems.txt + - current issues with shi +- README.md + - this file +- reset.sh + - debug script that nukes all boards and completly resets shi + - don't use this unless you've really screwed up +- shi.sh + - memories +- templates + - templates +- util.sh + - utility functions employed by console.sh -i know the file permissions are weird i'll fix them at some point + + +this whole rewrite started because of a feature request by desvox. Unfortunately, due to the complexity of responding to http requests with sh, this feature was not added. + +Sorry desvox :( diff --git a/console.sh b/console.sh new file mode 100755 index 0000000..5ad49d9 --- /dev/null +++ b/console.sh @@ -0,0 +1,43 @@ +#!/bin/sh +. ./globals.sh +. ./util.sh +. ./shi2.sh + +alias log="tail -f $log_file" + +echo "Your shell is now a shi console!" +oldps1="$PS1" +PS1="shi>" +listener_pid="" + + +start_shi() { + start_listener & + listener_pid="$!" + echo "Started shi" +} + +stop_shi() { + kill -- -"$listener_pid" +# kill -- -$(ps -o pgid= $listener_pid | grep -o [0-9]*) + echo "Stopped shi" +} + +restart(){ + stop_shi + . ./globals.sh + . ./util.sh + . ./shi2.sh + echo "Reloaded source files" + start_shi +} + +#tail -f "$log_file" & # uncomment this if you want constant log output to console + + +cleanup() { + PS1="$oldps1" + alias log="log" +} + +trap cleanup EXIT INT TERM diff --git a/file_db_backend.sh b/file_db_backend.sh new file mode 100755 index 0000000..27f6b84 --- /dev/null +++ b/file_db_backend.sh @@ -0,0 +1,157 @@ +#!/bin/sh +# the file/folder db backend +# since the backend provides both the add_post and fetch_post +# functions the data path can be in whatever format you like +# TODO: Post deletion, both on front and backend + +# NOTE: A post db path should always begin at $board_dir + +. ./globals.sh + + +path_to_post() { + post_id="$1" + find "$board_dir" -maxdepth 4 -name "$post_id" -not -path '*/\.*' +} + +create_post() { + # This function takes strings correspoding to post + # data and stores them in the boards folder in the + # shi folder/file format + + + local board="$1" + local parent="$2" + local user="$3" + local title="$4" + local content="$(echo "$5" | tr '\a' '\n')" # change the BEL characters back to newlines + local image_name="$6" + local image_content="$7" + local post_id="$8" + local post_type="$9" + + echo "Adding data for post with id $post_id" >> "$log_file" + + local post_directory="$board_dir/$board/$parent/$post_id" # where the post data will be stored + post_directory="$(realpath "$post_directory")" + + echo "[file_db_backend.sh] board: $board" >> "$log_file" + echo "[file_db_backend.sh] parent: $parent" >> "$log_file" + echo "[file_db_backend.sh] user: $user" >> "$log_file" + echo "[file_db_backend.sh] title: $title" >> "$log_file" + echo "[file_db_backend.sh] content: $content" >> "$log_file" + echo "[file_db_backend.sh] image_name: $image_name" >> "$log_file" + # uncomment this if you want base64 spam vvv +# echo "image_content: $image_content" >> "$log_file" +# echo "image_content: $image_content" | head -c 50 >> "$log_file" + echo "[file_db_backend.sh] post id: $post_id" >> "$log_file" + echo "[file_db_backend.sh] post type: $post_type" >> "$log_file" + + if [ -z "$user" ]; then echo "[file_db_backend.sh] ERROR: no user" >> "$log_file"; return 1; fi + if [ -z "$content" ]; then echo "[file_db_backend.sh] ERROR: no content"; return 1; fi + if ! (mkdir -p "$post_directory") + then + echo "[file_db_backend.sh] ERROR: could not create post dir" >> "$log_file" + return 1 + fi + + local image_path="$post_directory/$image_name" + + echo "$user" > "$post_directory/user" + echo "$title" > "$post_directory/title" + echo "$content" > "$post_directory/content" + echo "$post_type" > "$post_directory/type" + if [ -n "$image_name" ] && [ -n "$image_content" ] + then + echo "$image_content" | base64 -d > "$image_path" + echo "$image_name" > "$post_directory/image_name" + fi + + if command -v convert > /dev/null && [ -n "$image_name" ] + then + local thumb_path="$post_directory/thumb_$image_name.jpg" + echo "[file_db_backend.sh] 'convert' found, generating thumbnail" >> "$log_file" + # convert the image to a jpeg + if echo "$image_path" | grep '.gif' + then + convert "$image_path"'[0]' "$thumb_path" + else + convert "$image_path" "$thumb_path" + echo "[file_db_backend.sh] converted image to jpeg" >> "$log_file" + fi + # re-compress the jpeg to be under 100kb + convert "$thumb_path" -define jpeg:extent=100kb "$thumb_path" + echo "[file_db_backend.sh] re compressed image at $thumb_path" >> "$log_file" + fi + + echo "[file_db_backend.sh] Finished adding post $post_id on $(date)" >> "$log_file" + echo "[file_db_backend.sh] post dir: $post_directory" >> "$log_file" + + echo "$post_directory" + + return 0 +} + +get_post_data() { + # this function is given a post id and a data item to be + # retrieved. It returns the value of the requested item + + # TODO: file path caching + + local post_data_path="$1" + local item="$2" + local image_path="" + + echo "[file_db_backend.sh] Got request for $item from post at $post_data_path" >> "$log_file" + + if ! [ -d "$post_data_path" ]; then return 1; fi + + case "$item" in "parent") + basename $(realpath "$post_data_path/..") + ;; + "user") + cat "$post_data_path/user" + ;; + "title") + cat "$post_data_path/title" + ;; + "content") + cat "$post_data_path/content" + ;; + "thumb_path") # where the post's image is stored on the filesystem + echo "thumb_$(cat "$post_data_path/$image_name").jpg" + ;; + "image_path") # where the post's image is stored on the filesystem + image_path="$post_data_path/$(cat "$post_data_path/image_name")" + if [ -f "$image_path" ] + then + echo "$image_path" + else + echo "" + fi + ;; + "html_path") # where the post's html is on the filesystem + echo "$post_data_path/index.html" + ;; + "post_id") + basename "$post_data_path" + ;; + "post_type") + cat "$post_data_path/type" + ;; + "replies") + # Newline separated list of post_id for all replies to the + # specified post + # A trailing /. is added to prevent find from seeing + # post_data_path itself + + find "$post_data_path" \ + -mindepth 1 \ + -type d \ + -iname '[0-9]*' \ + -printf '%p\n' | sort + ;; + esac +} + +# no paths, only use the post id diff --git a/globals.sh b/globals.sh new file mode 100755 index 0000000..b4eabff --- /dev/null +++ b/globals.sh @@ -0,0 +1,8 @@ +port="7070" +log_file="./log.txt" +board_dir="./boards" +template_dir="./templates" +latest_file="./latest" +inbox="./inbox" +image_types="(\.png|\.jpg|\.gif)" +shi_sub_url="/shi" # the sub url shi is being run from (eg. http://domain.tld/[shi/]) diff --git a/http_backend.sh b/http_backend.sh new file mode 100755 index 0000000..bf29691 --- /dev/null +++ b/http_backend.sh @@ -0,0 +1,147 @@ +#!/bin/sh +. ./globals.sh + + +echo "Using http backend." >> "$log_file" + +gen_post_data_from_file() { +# TODO: if variables are unset by the end, set them to "error" + local file="$1" + local board="" + local parent="" + local user="" + local title="" + local content="" + local image_name="" + local image_content="" + + # find the form boundary + local boundary="$(grep -am1 \ + "Content-Type: multipart/form-data; boundary=" \ + $file | cut -f 2- -d "=")" + + local in_boundary="0" + local data_type="" + local file_upload_start="" # the line at which binary file data starts (if any) + local iterator="0" + echo "New post" + while read -r line + do + iterator="$(( iterator + 1 ))" + # dont read the binary data contained in files, + # as it will break the script + if echo "$line" | grep -q 'Content-Disposition: form-data; name="fileupload";' + then + file_upload_start="$iterator" + break + fi + + # if a boundary is seen + if echo "$line" | grep -q -- "$boundary" + then + # and we are not already in a boundary + if [ "$in_boundary" -eq 0 ] + then + # then we are now in a boundary + in_boundary="1" + else + # otherwise, we are in a new boundary, + # so reset data_type + data_type="" + fi + fi + + # if we are in a boundary, the beginning of form data is seen, + # and we are not already reading form data + if [ "$in_boundary" -eq "1" ] \ + && echo "$line" \ + | grep -q "Content-Disposition: form-data; name=" \ + && [ -z "$data_type" ] + then + + data_type=$(echo "$line" | cut -f 2 -d '"') + fi + + case "$data_type" in "post_to") + # remove LFs from line + line="$(echo "$line" | tr -d '\r')" + + # if our post is a reply + if echo "$line" | grep -qE "[0-9]$" + then + # set parent to the post it is replying to + parent="$(find $board_dir \ + -maxdepth 3 \ + -name "$line" \ + -not -path '*/\.*')" + # make sure the parent post exists + if [ -z "$parent" ]; then return 1; fi + # and set the board value to the board it is to be posted to + board="$(basename `dirname "$parent"`)" + # then change the parent value from adirectory path to a plain string + parent="$(basename `realpath "$parent"`)" + else # otherwise the post is a oppost + # set the board value appropriately + board="$line" + fi + ;; + "name") + user="$(echo "$line" | tr -d '\r')" + ;; + "title") + title="$(echo "$line" | tr -d '\r')" + ;; + "content") + # carrige returns need to be removed from multiline content + content="$content$(printf "\n%s\n" "$line" | tr -d '\r')" + ;; + "fileupload") + echo "read line of fileupload" + ;; + esac + done < "$file" + + # remove the first line from content, which is html form junk + content="$(echo "$content" | sed -e '1,2d' | tr '\n' '\a')" + image_name="$(sed -n "$file_upload_start"p $file \ + | grep -oE 'filename=".*' \ + | cut -f 2 -d '"' \ + | tr -d '"' )" + image_content="$(sed -e "1,$(( file_upload_start + 2 ))d" "$file" | base64 -w 0)" + + echo "board:$board" + echo "parent:$parent" + echo "user:$user" + echo "title:$title" + echo "content:$content" + echo "image_name:$image_name" + echo "image_content:$image_content" + echo "End new post" + rm "$file" +} + +get_request() { + submission_id="$(date '+%s%N')" # unique id of the submission + printf "HTTP/1.1 200 OK\r +Date: %s\r +Server: BSD Netcat/lmao\r +Connection: close\r +Content-Type: text/html\r\n\r\n +<script>window.close();</script>\n" "$(date -Ru | sed -e 's/\+0000/GMT/')" \ + | nc -lp "$port" -w 3 | head -c 1000000000 > "$inbox/$submission_id" + + mv "$inbox/$submission_id" "$inbox/$submission_id.submission" +} + +listen() { + while true + do + # TODO: scale to allow for posting in rapid succession + get_request + for post in $(find "$inbox" -iname "*.submission") + do + gen_post_data_from_file "$post" + done + done +} +trap break EXIT INT TERM diff --git a/main.css b/main.css new file mode 100755 index 0000000..3d0097a --- /dev/null +++ b/main.css @@ -0,0 +1,108 @@ +html * +{ + color: #fff !important; + font-family: monospace !important; +} + +div.postContainer { + border-style: solid; + padding-left: 5px; + margin: 0px; + margin-top: 5px; + width: 75%; + float: left; + border-width: 1px; + /*display: inline;*/ +} + +div.opContainer { + border-style: solid; + border-width: 1px; + padding: 0px 5px; + max-width: 1000px; + margin: 0px; + overflow: hidden; +} + +div.opContainer + div.opContainer { + margin-top: 10px; +} + +div.postHeader { + padding: 0px; +} +div.opContent { + /*display: inline;*/ +} + +div.postContent { + width: 100%; + height: 100%; +} + +p.opText { + /*font-family: sans;*/ + padding-bottom: 2px; + margin: 0px; + margin-top: 2px; + padding-bottom: 10px; + font-size: 12px; + height: 12px; + display: inline; +} + +p.postText { + /*font-family: sans;*/ + word-wrap: break-word; + padding-left: 5px; + padding-bottom: 2px; + margin: 0px; + margin-top: 2px; + font-size: 12px; +} +p.imageName { + margin: 0; + margin-top: -5px; + font-size: 10px; +} + +div.imageContainer { + height: 100%; + width: 20%; + padding-right: 5px; + float: left; + display: block; +} +img.postImage { + max-width: 100%; + height: auto; + width: auto; +} +div.reply { + border-style: double; +} +p.box { + border-style: double; +} +p.postTitle { + margin: 0px; + font-size: 14px; + display: inline-block; + padding-left: 9%; +} +a.postHeaderText { + margin: 1px; + font-size: 10px; + display: inline-block; +} + +p.replyHeaderText { + margin: 1px; + font-size: 10px; + display: inline-block; +} + +body { + padding: 20px; + background-color: black; +} diff --git a/post.sh b/post.sh new file mode 100755 index 0000000..96c9699 --- /dev/null +++ b/post.sh @@ -0,0 +1,3 @@ +#!/bin/sh +readdat () { read -r var; echo "$var" | grep -qE "[a-Z].*" || var=$2; echo "$var" > "$1"; };echo "Post to:"; read -r parent; echo "$parent" | grep -qE "([a-Z]|[1-9]).*" || exit;postdir=$(echo "$parent"-"$RANDOM");mkdir ./"$postdir" && cd ./"$postdir" || exit; echo "Name (blank for anonymous):";readdat "user" "Anonymous";echo "Title (blank for none):";readdat "title" "";echo "Absolute path to image (blank for none):";read -r imagepath; echo "$imagepath" | grep -qE "[a-Z].*" && if test -f +"$imagepath"; then cp "$imagepath" .;else echo "could not find $imagepath";fi;echo "Post text (Ctrl-D to finish):"; cat > content; cd ..; tar -cf "$postdir".tar ./"$postdir"; diff --git a/problems.txt b/problems.txt new file mode 100644 index 0000000..497daa8 --- /dev/null +++ b/problems.txt @@ -0,0 +1,11 @@ +incredibly slow to post images + +not entirely synchronus, a post is lost if it is received at the same time as another + +tar backend has no clean way to exit, leaves an orphan nc process behind + +css in post page +test rapid succession posting + - I think it works? +test replies in post html + diff --git a/reset.sh b/reset.sh index ee6fbed..e07d72a 100755 --- a/reset.sh +++ b/reset.sh @@ -1,6 +1,10 @@ #!/bin/sh +# DEBUG PURPOSES ONLY +# USERS SHOULD USE THE FUNCTION PROVIDED IN util.sh! + echo '0' > latest echo "" > log.txt +rm -rf ./inbox/* for board in boards/* do echo reseting board "$board" @@ -15,3 +19,4 @@ do done done +pkill -f nc diff --git a/shi/README.md b/shi/README.md new file mode 100644 index 0000000..f9aadb5 --- /dev/null +++ b/shi/README.md @@ -0,0 +1,33 @@ +#shi +**SH**ell **I**mageboard + +no php, no js, just POSIX shell, gnu coreutils, nc, html and css + +### features +- posting +- replies +- images +- timestamps +- more + +### to use it +- `git clone <this repo>` +- `cd <this repos folder>` +- `touch log.txt` +- `cp templates/mainindex.html ./index.html` +- point your web server to ./index.html +- to run shi + - `./shi.sh &` + - `tail -f log.txt` +- to make a board: + - `mkdir -p boards/<boardname>` + - `cp templates/index.html.template boards/<boardname>/index.html` + - `cp templates/main.css.template boards/<boardname>/main.css` +- to stop shi + - `killall shi.sh` +- to post to shi + - copy the script at the top of the board into your terminal + - hit enter, follow the prompts + + +i know the file permissions are weird i'll fix them at some point diff --git a/shi.sh b/shi/shi.sh similarity index 84% rename from shi.sh rename to shi/shi.sh index 018e084..0b3a42a 100755 --- a/shi.sh +++ b/shi/shi.sh @@ -27,7 +27,7 @@ IFS='' LOG=log.txt TEMPLATEDIR="templates" - +alias echo='echo [shi.sh]:' newPost() { POSTTYPE=$2 POSTNUM=$(basename "$1") @@ -150,45 +150,48 @@ addPostToBoard() { cat "$TEMPFILE" > "$BOARDDIR"/index.html } - +addNewPost() { + post="$1" + if basename "$post" | grep -qE "[0-9]$" && [ "$(basename "$post")" -gt "$2" ] + then + TEMPFILE=$(mktemp) + if dirname "$post" | grep -qE "[0-9]*+/[0-9]*$" # If reply generate a reply + then + echo "Post type: reply" > $LOG + PARENT=$(dirname "$post" | sed 's/^.*\/\([0-9]\+\)/\1/') + while read -r line + do + if echo "$line" | grep -q "<!--END REPLIES $PARENT-->" + then + newPost "$post" "reply" + printf "\t\t\t\t\t\t<!--END REPLIES %s-->\n" "$PARENT" + else + echo "$line" + fi + done < "$post/../index.html" > "$TEMPFILE" + cat "$TEMPFILE" > "$post/../index.html" + addPostToBoard "$(dirname "$post")" + else # Else generate an op post + echo "Post type: OP post" >> $LOG + while read -r line + do + echo "$line" + if echo "$line" | grep -q "<!--BEGIN POSTS-->" + then + newPost "$post" "oppost" + fi + done < "$(dirname "$post")/index.html.template" > "$TEMPFILE" + cat "$TEMPFILE" > "$post"/index.html + rm "$TEMPFILE" + addPostToBoard "$post" + fi + fi +} update() { echo "Posting started at $(date -u)" >> $LOG find "$1" | while read -r post do - if basename "$post" | grep -qE "[0-9]$" && [ "$(basename "$post")" -gt "$2" ] - then - TEMPFILE=$(mktemp) - if dirname "$post" | grep -qE "[0-9]*+/[0-9]*$" # If reply generate a reply - then - echo "Post type: reply" > $LOG - PARENT=$(dirname "$post" | sed 's/^.*\/\([0-9]\+\)/\1/') - while read -r line - do - if echo "$line" | grep -q "<!--END REPLIES $PARENT-->" - then - newPost "$post" "reply" - printf "\t\t\t\t\t\t<!--END REPLIES %s-->\n" "$PARENT" - else - echo "$line" - fi - done < "$post/../index.html" > "$TEMPFILE" - cat "$TEMPFILE" > "$post/../index.html" - addPostToBoard "$(dirname "$post")" - else # Else generate an op post - echo "Post type: OP post" >> $LOG - while read -r line - do - echo "$line" - if echo "$line" | grep -q "<!--BEGIN POSTS-->" - then - newPost "$post" "oppost" - fi - done < "$(dirname "$post")/index.html.template" > "$TEMPFILE" - cat "$TEMPFILE" > "$post"/index.html - rm "$TEMPFILE" - addPostToBoard "$post" - fi - fi + addNewPost "$post" done echo $(( $2 + 1 )) > ./latest echo "Posting finished at $(date -u)" >> $LOG diff --git a/shi2.sh b/shi2.sh new file mode 100755 index 0000000..c3a7d8a --- /dev/null +++ b/shi2.sh @@ -0,0 +1,364 @@ +#!/bin/sh +# TODO: gif support and thumbnails if Imagemagick is installed +# TODO: More comments +# TODO: discard reply if parent doesnt exist, right now shi hangs + +backend="./http_backend.sh" +db_backend="./file_db_backend.sh" + +. ./globals.sh +. ./util.sh +. "$backend" +. "$db_backend" + + +sanitize_text() { + # Sanitizes text, the first argument is the text to be + # sanitized, the rest represent what sanitations should be applied + # (html, sed, etc.) + + # sed sanitation will prepend a leading backslash to sed characters + # html sanitation will escape '&', '<', and '>' and replace newlines + # with '<br>' tags + + # NOTE: 'sed' must be placed before 'html" in the options if they + # are both specified (This should be fixed later) + + local text="$1" # text to be sanitized + + for option in "$@" # parse options to find specified sanitations + do + case "$option" in "$text") + ;; # if it is the first option, ignore it + "sed") + echo "sanitizing text $text for $option" >> $log_file + text="$(echo "$text" \ + | sed -e 's/&/&/g' \ + -e 's/</\</g' \ + -e 's/>/\>/g' \ + -e 's/\([-$^*()+{\[\.?\/]\)/\\\1/g')" + ;; + "html") + echo "sanitizing text $text for $option" >> $log_file + text="$(echo "$text" \ + | sed -e ':a;N;$!ba;s/\n/<br>/g')" + ;; + esac + done + + echo "$text" +} + +get_op_post_html() { + # Get the html of an op post if it has already been generated + local temp_file="$(mktemp)" + local post_db_path="$1" + local post_html_path="$(get_post_data "$post_db_path" "html_path")" + local post_id="$(get_post_data "$post_db_path" "post_id")" + local trim="$2" # how many replies to get + local post_replies="$(get_post_data "$post_db_path" "replies")" + + # id of the most recent reply to be included on the board page + local reply_lim="$(echo "$post_replies" | sed -n "$trim"p | tail -c 1)" + + echo "REPLIES $post_replies" >> "$log_file" + cat "$post_html_path" \ + | sed -e ':a' -e 'N' -e '$!ba' \ + -e "s/.*\(<!--BEGIN OP POST $post_id-->.*<!--END OP POST $post_id-->\).*/\1/g" \ + > "$temp_file" + + sed -i -e ':a' -e 'N' -e '$!ba' \ + -e "s/<!--BEGIN REPLY $(( post_id - trim ))-->.*<!--END OP POST $post_id-->//g" \ + "$temp_file" + + cat "$temp_file" + +# printf "<!--END REPLIES %s-->\n</div>\n</div>\n<!--END OP POST %s-->" \ +# "$post_id" "$post_id" + + rm "$temp_file" +} + +gen_post_html() { + # Takes a path to a database entry and generates html + # based on it's content + + # TODO: implement thumbnails + # TODO: separate html frontend to enable creation of other frontends + # eval + + local post_db_path="$1" # db path to post + local template_path="" # filesystem path to the post template to be used + + echo "[Genereate post html]" >> "$log_file" + + echo "Generating post html for $post_db_path" >> "$log_file" + local date="$(date -u)" + local user="$(get_post_data "$post_db_path" "user")" + local title="$(get_post_data "$post_db_path" "title")" + local content="$(get_post_data "$post_db_path" "content")" + local image_path="$(get_post_data "$post_db_path" "image_path")" + local image_size="" + local image_name="" + local post_id="$(get_post_data "$post_db_path" "post_id")" + local post_type="$(get_post_data "$post_db_path" "post_type")" + + + # http path to post directory (generated from html path) + local post_url_path="$(get_post_data "$post_db_path" "html_path")" + post_url_path="$(dirname "$post_url_path")" + + # http path to image + local image_url_path="" + + # http path to thumbail + local thumb_url_path="" + + # truncate the url path to start at $board_dir + case "$post_type" in "oppost") + post_url_path="$(echo "$post_url_path" \ + | rev \ + | cut -f -4 -d '/' \ + | rev)" + ;; + "reply") + post_url_path="$(echo "$post_url_path" \ + | rev \ + | cut -f -5 -d '/' \ + | rev)" + ;; + esac + + post_url_path="$shi_sub_url/$post_url_path" + + if [ -z "$image_path" ] # if no image is found + then + post_type="$post_type"-noimage # specify the post type as imageless + echo "Image path is null, -noimage post" >> "$log_file" + else + image_size="$(du -h "$image_path" | cut -f 1)" + image_name="$(basename "$image_path")" + image_url_path="$post_url_path/$image_name" + thumb_url_path="$post_url_path/thumb_$image_name.jpg" + fi + + template_path="$template_dir/$post_type".template + + content="$(sanitize_text "$content" sed html)" # sanitize content + title="$(sanitize_text "$title" sed)" # sanitize title for sed + post_url_path="$(sanitize_text "$post_url_path" sed)" # sanitize post url for sed + image_url_path="$(sanitize_text "$image_url_path" sed)" # sanitize image path for sed + thumb_url_path="$(sanitize_text "$thumb_url_path" sed)" # sanitize image path for sed + + # read through the appropriate template and replace + # placeholders with their actual values + while read -r line + do + echo "$line" \ + | sed -e "s/{{{ POSTNUM }}}/$post_id/g" \ + -e "s/{{{ POSTURL }}}/$post_url_path/g" \ + -e "s/{{{ DATE }}}/$date/g" \ + -e "s/{{{ USER }}}/$user/g" \ + -e "s/{{{ POSTTITLE }}}/$title/g" \ + -e "s/{{{ POSTTEXT }}}/$content/g" \ + -e "s/{{{ IMAGEPATH }}}/$image_url_path/g" \ + -e "s/{{{ THUMBPATH }}}/$thumb_url_path/g" \ + -e "s/{{{ IMAGENAME }}}/$image_name/g" \ + -e "s/{{{ IMAGESIZE }}}/$image_size/g" + done < "$template_path" +} + +insert_post() { + # TODO: cache (?) the position of <!--BEGIN POSTS--> in the html + # currently a large and exponential slowdown + + # read from a cache file, if it doesn't exist, make it + + local post_db_path="$1" # path to the post data + local target_html="$2" # the html to be modified + local trim="3" # how many replies to display on the main board page + + echo "[Insert post into html]" >> "$log_file" + echo "Inserting post $post_db_path into $target_html" >> "$log_file" + + local post_id="$(get_post_data "$post_db_path" "post_id")" + local post_type="$(get_post_data "$post_db_path" "post_type")" + local post_replies="" + local parent="" + local temp_file="$(mktemp)" + + local replace="" # regex to find the exitsing post's html + local insert_at="" # html will be inserted after the line containing this string + + case "$post_type" in "oppost") + replace="<!--BEGIN OP POST $post_id-->.*<!--END OP POST $post_id-->" + insert_at="<!--BEGIN POSTS-->" + ;; + "reply") + # In the future, we will want to display replies in reverse chronological order + parent="$(get_post_data "$post_db_path" "parent")" + replace="<!--BEGIN REPLY $post_id-->.*<!--END REPLY $post_id-->\n" + insert_at="<!--BEGIN REPLIES $parent-->" + ;; + esac + + # remove any existing instances of the post in the html + sed -i \ + -e ':a' \ + -e 'N' \ + -e '$!ba' \ + -e "s/$replace//g" "$target_html" + + + while read -r line + do + echo "$line" + if echo "$line" | grep -qE "$insert_at" + then + echo "Inserting html at line $line matching $insert_at" >> "$log_file" + if [ "$post_type" = "oppost" ] + then + post_replies="$(get_post_data "$post_db_path" "replies")" + if [ -n "$post_replies" ] + then + get_op_post_html "$post_db_path" "$trim" + else + gen_post_html "$post_db_path" + fi + else + gen_post_html "$post_db_path" + fi + fi + + done < "$target_html" > "$temp_file" + cat "$temp_file" > "$target_html" + + +# NOTE: This section only applies when regenerating an entire board +# If the post is an oppost, we will need to also generate the replies +# if [ "$post_type" = "oppost" ] +# then +# # NOTE: Very redundant, find a way to process both files at once +# post_replies=$(get_post_data "$post_db_path" "replies" | tac) +# for reply_db_path in $post_replies +# do +# echo "Inserting "$reply_db_path" into post html" >> "$log_file" +# insert_post "$reply_db_path" "$(get_post_data "$post_db_path" "html_path")" +# done +# +# # NOTE: This will break if the board directory has spaces +# for reply_db_path in $(echo $post_replies | cut -f "$trim"- -d ' ') +# do +# echo "Inserting "$reply_db_path" into board html" >> "$log_file" +# insert_post "$reply_db_path" "$target_html" +# done +# fi +} + +start_listener() { + # This function reads a continuous stream of data + # (provided by the `listen` function from a user + # specified backend) and generates posts from the + # data it receives. + + local status=0 # 0 is normal, 1 is receiving post data + + local board="" + local parent="" + local user="" + local title="" + local content="" + local image_name="" + local image_content="" + local post_id="" + local post_type="" + local post_html_path="" + + local latest="" + + listen | while read -r line + do + + if [ "$line" = "End new post" ] + then + echo [New post] >> "$log_file" + status=0 + latest="$(cat "$latest_file")" + post_id="$(( latest + 1 ))" + post_db_path="$(create_post \ + "$board" \ + "$parent" \ + "$user" \ + "$title" \ + "$content" \ + "$image_name" \ + "$image_content" \ + "$post_id" \ + "$post_type")" + + post_html_path="$(get_post_data "$post_db_path" "html_path")" + parent_html_path="" + parent_db_path="" + + + if [ "$post_type" == "oppost" ] + then + cp "$board_dir/$board/index.html.template" "$post_html_path" + insert_post "$post_db_path" "$post_html_path" + insert_post "$post_db_path" "$board_dir/$board/index.html" + else + parent_html_path="$board_dir/$board/$parent/index.html" + parent_db_path="$board_dir/$board/$parent" + insert_post "$post_db_path" "$parent_html_path" + insert_post "$parent_db_path" "$board_dir/$board/index.html" + fi + + + echo "$post_id" > "$latest_file" # update the latest post file + echo "Finished adding post $post_id at $(date)" + fi + + if [ "$status" -eq 1 ] + then + key="$(echo "$line" | cut -f 1 -d :)" value="$(echo "$line" | cut -f 2- -d :)" + + echo "Reading key $key from backend" >> "$log_file" + case "$key" in "board") + board="$value" + ;; + "parent") + parent="$value" + if [ -z "$parent" ] + then + post_type="oppost" + else + post_type="reply" + fi + ;; + "user") + user="$value" + ;; + "title") + title="$value" + ;; + "content") + content="$value" + ;; + "image_name") + image_name="$value" + ;; + "image_content") + image_content="$value" + ;; + esac + fi + + if [ "$line" = "New post" ] + then + echo "Processing new post" >> "$log_file" + status=1 + fi + done +} + +trap break EXIT INT TERM diff --git a/tar_backend.sh b/tar_backend.sh new file mode 100755 index 0000000..85710f1 --- /dev/null +++ b/tar_backend.sh @@ -0,0 +1,99 @@ +#!/bin/sh +# Tar listening backend. +# This backend looks for tarfiles in the folder specified by $inbox. +# It then un-tars them and and gets the username, title and post content +# from their respecively named files. It will use the first image matching +# one of $image_types types as the image for the post. + +# TODO: Trap the exit signal to kill nc +# or maybe use telnet + +# TODO: make this file backend agnostic + +. ./globals.sh + +echo "Using tar backend." >> "$log_file" +gen_post_from_dir() { + local dir="$1" + local post_to="$(basename "$dir" | sed 's/-.*//g')" # where the post wants to go + + local board="" + local parent="" + local user="" + local title="" + local content="" + local image_name="" + local image_content="" + + local image_path="" + + + echo "New post" + if echo "$post_to" | grep -qE "[0-9]$" # if our post is a reply + then + # set the parent to the post it is replying to + parent="$(find $board_dir -maxdepth 3 -name "$post_to" -not -path '*/\.*')" + # make sure the parent post exists + if [ -z "$parent" ]; then echo "Parent post does not exist" && return 1; fi + + # and set the board value to the board it is to be posted to + board="$(basename `dirname "$parent"`)" + # then change the parent value from a directory path to a plain string + parent="$(basename `realpath "$parent"`)" + else # otherwise, the post is an OP post. + # set the board value to the board it is to be posted to + board="$post_to" + fi + + image_path="$(find "$dir" | grep -Em1 "$image_types")" + if ! [ -z "$image_path" ] + then + image_name="$(basename "$image_path")" + image_content="$(cat "$image_path" | base64 -w 0)" + fi + + user="$(cat "$dir/user")" + content="$(tr '\n' '\a' < "$dir/content")" # change newline characters to BEL + title="$(cat "$dir/title")" + + echo "board:$board" + echo "parent:$parent" + echo "user:$user" + echo "title:$title" + echo "content:$content" + echo "image_name:$image_name" + echo "image_content:$image_content" + echo "End new post" + + return 0 +} + +listen() { + # NOTE: Tar extraction takes a nontrivial amount of time, which + # means if two connections were made in close succecion + # there is a possibility that the second connection would + # not receive the "busy" signal, and send it's data anyways + # + # additionally, there should be a way to prevent the sending of + # malicious data to interfere with normal post processing + # + # to prevent zip bombs, break the tar -x -C operation up and use + # `zcat $file | head -c $file_limit` + + blocker_pid="" # the pid of the loop that sends the busy signal + while nc -lp "$port" | tar -x -C "$inbox" + do + (while true + do + echo "Busy" | nc -lp 7070 + done)>/dev/null & blocker_pid="$!" + + for dir in $inbox/* + do + echo "Got post" >> "$log_file" + gen_post_from_dir "$dir" + rm -rf "$dir" + done + kill "$blocker_pid" >/dev/null + done +} diff --git a/templates/index.html.template b/templates/index.html.template index 31637b6..64d6c34 100755 --- a/templates/index.html.template +++ b/templates/index.html.template @@ -15,9 +15,15 @@ a description for the board </p> <br> - <p style="text-align:left;font-size: 9px; margin-right: 25%; margin-left: 25%"> - readdat () { read -r var; echo "$var" | grep -qE "[a-Z].*" || var=$2; echo "$var" > "$1"; };echo "Post to:"; read -r parent; echo "$parent" | grep -qE "([a-Z]|[1-9]).*" || exit;postdir=$(echo "$parent"-"$RANDOM");mkdir ./"$postdir" && cd ./"$postdir" || exit; echo "Name (blank for anonymous):";readdat "user" "Anonymous";echo "Title (blank for none):";readdat "title" "";echo "Absolute path to image (blank for none):";read -r imagepath; echo "$imagepath" | grep -qE "[a-Z].*" && if test -f "$imagepath"; then cp "$imagepath" .;else echo "could not find $imagepath";fi;echo "Post text (Ctrl-D to finish):"; cat > content; cd ..; tar -cf "$postdir".tar ./"$postdir";rm -rf "$postdir"; echo "posting..."; nc -q2 [the adress shi is at] [the port shi is running on] < "$postdir".tar - </p> + <form> + Name: <br> + <input type="text" name="username"> <br> + Post title: <br> + <input type="text" name="title"> <br> + Content: <br> + <input type="text" name="content"> <br> + <input type="submit" value="Submit"> <br> + </form> <br> <div class="posts"> <!--BEGIN POSTS--> diff --git a/templates/main.css.template b/templates/main.css.template index 3d0097a..87ac843 100755 --- a/templates/main.css.template +++ b/templates/main.css.template @@ -106,3 +106,9 @@ body { padding: 20px; background-color: black; } +input { + background-color: black; +} +textarea { + background-color: black; +} diff --git a/templates/oppost-noimage.template b/templates/oppost-noimage.template index 4645fb5..df379a3 100755 --- a/templates/oppost-noimage.template +++ b/templates/oppost-noimage.template @@ -1,7 +1,7 @@ <!--BEGIN OP POST {{{ POSTNUM }}}--> <div class="opContainer"> <div class="postHeader"> - <a class="postHeaderText" href="{{{ POSTNUM }}}/index.html">Post #{{{ POSTNUM }}} by {{{ USER }}} {{{ DATE }}}</a> + <a class="postHeaderText" href="{{{ POSTURL }}}">Post #{{{ POSTNUM }}} by {{{ USER }}} {{{ DATE }}}</a> <p class="postTitle">{{{ POSTTITLE }}}</p> </div> <div class="opContent"> diff --git a/templates/oppost.template b/templates/oppost.template index a7b29cc..72468f1 100755 --- a/templates/oppost.template +++ b/templates/oppost.template @@ -1,13 +1,13 @@ <!--BEGIN OP POST {{{ POSTNUM }}}--> <div class="opContainer"> <div class="postHeader"> - <a class="postHeaderText" href="{{{ POSTNUM }}}/index.html">Post #{{{ POSTNUM }}} by {{{ USER }}} {{{ DATE }}}</a> + <a class="postHeaderText" href="{{{ POSTURL }}}">Post #{{{ POSTNUM }}} by {{{ USER }}} {{{ DATE }}}</a> <p class="postTitle" >{{{ POSTTITLE }}}</p> </div> <div class="opContent"> <div class="imageContainer"> - <a href="/shi/{{{ IMAGEPATH }}}" target="_blank"> - <img class="postImage" src="/shi/{{{ THUMBPATH }}}" alt="{{{ IMAGENAME }}}"> + <a href="{{{ IMAGEPATH }}}" target="_blank"> + <img class="postImage" src="{{{ THUMBPATH }}}" alt="{{{ IMAGENAME }}}"> </a> <p class="imageName"> {{{ IMAGENAME }}} ({{{ IMAGESIZE }}})</p> </div> diff --git a/templates/reply.template b/templates/reply.template index 7783fcd..2cdccdb 100755 --- a/templates/reply.template +++ b/templates/reply.template @@ -5,8 +5,8 @@ </div> <div class="postContent"> <div class="imageContainer"> - <a href="/shi/{{{ THUMBPATH }}}" target="_blank"> - <img class="postImage" src="/shi/{{{ IMAGEPATH }}}" alt="{{{ IMAGENAME }}}"> + <a href="{{{ THUMBPATH }}}" target="_blank"> + <img class="postImage" src="{{{ IMAGEPATH }}}" alt="{{{ IMAGENAME }}}"> </a> <p class="imageName"> {{{ IMAGENAME }}} ({{{ IMAGESIZE }}})</p> </div> diff --git a/util.sh b/util.sh new file mode 100755 index 0000000..14ef9d6 --- /dev/null +++ b/util.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# These are the functions that are run by the admin to manage posts + +file_path_to_post() { + post_id="$1" + find "$board_dir" -maxdepth 4 -name "$post_id" -not -path '*/\.*' +} + +delete_post() { + post_id="$1" + rm -rf "$(path_to_post "$post_id")" +} + +regen_board() { + . ./shi2.sh + board="$1" + posts="$(find "$board" \ + -mindepth 1 \ + -maxdepth 1 \ + -type d \ + -iname '[0-9]*' \ + -printf '%p\n' | sort)" + for post in $posts + do + echo "Generating post $post" + insert_post "$post" "$board/index.html" + done + +}