This commit is contained in:
sose 2019-10-16 22:17:39 -07:00
parent 65e628570c
commit db0190b0ff
21 changed files with 1186 additions and 66 deletions

BIN
.shi2.sh.swo Normal file

Binary file not shown.

BIN
.shi2.sh.swp Normal file

Binary file not shown.

140
README.md
View File

@ -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 <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
### 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:<board name>
parent:<parent of post>
user:<user>
title:<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 :(

43
console.sh Executable file
View File

@ -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

157
file_db_backend.sh Executable file
View File

@ -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

8
globals.sh Executable file
View File

@ -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/])

147
http_backend.sh Executable file
View File

@ -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

108
main.css Executable file
View File

@ -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;
}

3
post.sh Executable file
View File

@ -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";

11
problems.txt Normal file
View File

@ -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

View File

@ -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

33
shi/README.md Normal file
View File

@ -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

View File

@ -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

364
shi2.sh Executable file
View File

@ -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/&/&amp;/g' \
-e 's/</\&lt;/g' \
-e 's/>/\&gt;/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

99
tar_backend.sh Executable file
View File

@ -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
}

View File

@ -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-->

View File

@ -106,3 +106,9 @@ body {
padding: 20px;
background-color: black;
}
input {
background-color: black;
}
textarea {
background-color: black;
}

View File

@ -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">

View File

@ -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>

View File

@ -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>

29
util.sh Executable file
View File

@ -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
}