diff --git a/.shi2.sh.swo b/.shi2.sh.swo
new file mode 100644
index 0000000..2e6ed7e
Binary files /dev/null and b/.shi2.sh.swo differ
diff --git a/.shi2.sh.swp b/.shi2.sh.swp
new file mode 100644
index 0000000..113fe30
Binary files /dev/null and b/.shi2.sh.swp differ
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:
+ image_name:
+ image content:
+ 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 `
+ - `create post `
+ - `get_post_data -
+ - 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
+\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 `
+- `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
+
+
+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 ""
+ then
+ newPost "$post" "reply"
+ printf "\t\t\t\t\t\t\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 ""
+ 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 ""
- then
- newPost "$post" "reply"
- printf "\t\t\t\t\t\t\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 ""
- 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 '
' 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/
/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/.*\(.*\).*/\1/g" \
+ > "$temp_file"
+
+ sed -i -e ':a' -e 'N' -e '$!ba' \
+ -e "s/.*//g" \
+ "$temp_file"
+
+ cat "$temp_file"
+
+# printf "\n\n\n" \
+# "$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 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=".*"
+ insert_at=""
+ ;;
+ "reply")
+ # In the future, we will want to display replies in reverse chronological order
+ parent="$(get_post_data "$post_db_path" "parent")"
+ replace=".*\n"
+ insert_at=""
+ ;;
+ 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
-
- 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
-
+
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 @@
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 @@
-
-
+
+
{{{ IMAGENAME }}} ({{{ IMAGESIZE }}})
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 @@
-
-
+
+
{{{ IMAGENAME }}} ({{{ IMAGESIZE }}})
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
+
+}