toymaker.sh 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. #!/bin/sh
  2. #
  3. # Based on server by Upper Stream (c) 2017, MIT license
  4. # (https://gist.github.com/upperstream/b9b77429bc024541b7605aea76086ad8)
  5. . start.sh
  6. . get.sh
  7. . password.sh
  8. set -e
  9. exec >>~/toymaker.log
  10. exec 2>&1
  11. program=${0##*/}
  12. usage() {
  13. cat <<-EOF
  14. Usage:
  15. $program [-p port] [docroot]
  16. $program -h
  17. -p port : specify listening port number; defaults to 1313
  18. -h : print this help and quit
  19. docroot : specify document root directory; defaults to the current directory
  20. EOF
  21. }
  22. # default listening port
  23. port=1313
  24. nc=$(command -v nc) || true
  25. pid_file=/tmp/$program.pid
  26. slashes() {
  27. printf "%s" "$1" | sed 's|[^/]||g' | wc -m
  28. }
  29. field() {
  30. echo "$1" | cut -d '/' -f "$2"
  31. }
  32. makefifo() {
  33. name="$1"
  34. mkfifo -m 0600 "/tmp/$name.fifo" && echo "/tmp/$name.fifo"
  35. }
  36. templateMessage() {
  37. message="$1"
  38. templateName=$2
  39. contentTypeHeader=$3
  40. while [ -n "$contentTypeHeader" ]
  41. do
  42. contentType=$(echo "$contentTypeHeader" | cut -d ',' -f1)
  43. contentTypeHeader=$(echo "$contentTypeHeader" | cut -d ',' -f2-)
  44. contentType=$(echo "$contentType" | cut -d ';' -f1)
  45. case $contentType in
  46. text/plain)
  47. ext='txt'
  48. ;;
  49. text/html|\*|\*/\*|'')
  50. ext='html'
  51. ;;
  52. image/\*|image/svg)
  53. ext='svg'
  54. ;;
  55. *)
  56. continue
  57. ;;
  58. esac
  59. [ -f "templates/$templateName.$ext" ] && break
  60. ext=''
  61. done
  62. [ -n "$ext" ] || return 1
  63. #shellcheck disable=SC1090
  64. . "templates/$templateName.$ext"
  65. printf '%s\n' "$contentType"
  66. template "$message"
  67. }
  68. respond200() {
  69. page="$1"
  70. contentType="$2"
  71. [ -z "$contentType" ] && contentType='text/html'
  72. printf "HTTP/1.1 200 OK\r\nContent-Length: %s\r\nContent-Type: %s\r\n\r\n%s\r\n" "${#page}" "$contentType" "$page" > "$fifo1"
  73. printf "200 %s" "${#page}"
  74. }
  75. respond200file() {
  76. filePath="$1"
  77. contentType="$2"
  78. { cat "$fifo2" > /dev/null; printf ""; } > "$fifo1" &
  79. printf "HTTP/1.1 200 OK\r\nContent-Length: %s\r\nContent-Type: %s\r\n\r\n" "$(wc -c "$filePath" | cut -d ' ' -f1)" "$contentType" >> "$fifo1"
  80. cat "$filePath" >> "$fifo1"
  81. sleep 1; echo "end" > "$fifo2"
  82. printf "200 %s" "$(wc -c "$filePath" | cut -d ' ' -f1)"
  83. }
  84. respond202() {
  85. toy=$1
  86. item=$2
  87. message=$3
  88. contentType="$4"
  89. [ -z "$contentType" ] && contentType='text/html'
  90. printf "HTTP/1.1 202 Accepted\r\nContent-Length: %s\r\nLocation: /toys/%s/%s\r\n\r\n%s\r\n" "${#message}" "$toy" "$item" "$message" > "$fifo1"
  91. printf "202 -"
  92. }
  93. respond401() {
  94. message=""
  95. printf "HTTP/1.1 401 Unauthorized\r\nContent-Length: %s\r\n\r\n%s\r\n" "${#message}" "$message" > "$fifo1"
  96. printf "401 -"
  97. }
  98. respond403() {
  99. message=""
  100. printf "HTTP/1.1 403 Forbidden\r\nContent-Length: %s\r\n\r\n%s\r\n" "${#message}" "$message" > "$fifo1"
  101. printf "403 -"
  102. }
  103. respond404() {
  104. path=$1
  105. message=$(templateMessage "$path not found" 'message' 'text/html' | tail -n+2)
  106. printf "HTTP/1.1 404 Not Found\r\nContent-Length: %s\r\n\r\n%s\r\n" "${#message}" "$message" > "$fifo1"
  107. printf "404 -"
  108. }
  109. respond405() {
  110. method=$1
  111. message=$(templateMessage "$method unsupported" 'message' 'text/html' | tail -n+2)
  112. printf "HTTP/1.1 405 Method Not Allowed\r\nContent-Length: %s\r\n\r\n%s\r\n" "${#message}" "$message" > "$fifo1"
  113. printf "405 -"
  114. }
  115. respond406() {
  116. contentType=$1
  117. message="$contentType unsupported"
  118. printf "HTTP/1.1 406 Not Acceptable\r\nContent-Length: %s\r\n\r\n%s\r\n" "${#message}" "$message" > "$fifo1"
  119. printf "406 -"
  120. }
  121. respond500() {
  122. message=$(templateMessage "Server error: $1" 'message' 'text/html' | tail -n+2)
  123. printf "HTTP/1.1 500 Internal Server Error\r\nContent-Length: %s\r\n\r\n%s\r\n" "${#message}" "$message" > "$fifo1"
  124. printf "500 -"
  125. }
  126. check_auth() {
  127. authorization=$1
  128. stored=$(cat password)
  129. if [ -z "$stored" ] || [ -z "$authorization" ]
  130. then
  131. respond401
  132. return 1
  133. fi
  134. if ! printf '%s' "$authorization" | verify "$stored"
  135. then
  136. respond403
  137. return 1
  138. fi
  139. }
  140. execute() {
  141. method="$1"
  142. path="$2"
  143. authorization="$3"
  144. accept="$4"
  145. _toys=$(field "$path" 2)
  146. if [ "$_toys" != 'toys' ]
  147. then
  148. respond404 "$path"
  149. return 1
  150. fi
  151. case $method in
  152. GET)
  153. if [ "$(slashes "$path")" -eq 2 ]
  154. then
  155. toy=$(field "$path" 3)
  156. if [ "$toy" = '' ]
  157. then
  158. if page=$(templateMessage "$(list_toys)" 'toys' 'text/html')
  159. then
  160. respond200 "$(echo "$page" | tail -n+2)"
  161. else
  162. respond406 "$accept"
  163. return 1
  164. fi
  165. else
  166. r=$(list_items "$toy")
  167. status=$(echo "$r" | cut -d ';' -f1)
  168. content=$(echo "$r" | cut -d ';' -f2)
  169. if [ "$status" = 'n' ]
  170. then
  171. respond404 "toy $toy"
  172. return 1
  173. elif page=$(templateMessage "$toy;$content" 'toy' 'text/html')
  174. then
  175. respond200 "$(echo "$page" | tail -n+2)"
  176. else
  177. respond406 "$accept"
  178. return 1
  179. fi
  180. fi
  181. elif [ "$(slashes "$path")" -eq 3 ]
  182. then
  183. toy=$(field "$path" 3)
  184. item=$(field "$path" 4)
  185. if [ -z "$item" ]
  186. then
  187. respond404 "$path"
  188. return 1
  189. fi
  190. r=$(show_item "$toy" "$item")
  191. artifacts=$(item_artifacts "$toy" "$item")
  192. status=$(echo "$r" | head -n1 | cut -d ';' -f1)
  193. if [ "$status" = 'n' ]
  194. then
  195. respond404 "item $toy/$item"
  196. return 1
  197. elif page=$(templateMessage "$artifacts;$toy;$item;$r" 'item' "$accept")
  198. then
  199. body=$(echo "$page" | tail -n+2)
  200. contentType=$(echo "$page" | head -n1)
  201. if [ "$status" = 'r' ]
  202. then
  203. respond202 "$toy" "$item" "$body" "$contentType"
  204. else
  205. respond200 "$body" "$contentType"
  206. fi
  207. else
  208. respond406 "$accept"
  209. return 1
  210. fi
  211. elif [ "$(slashes "$path")" -eq 4 ]
  212. then
  213. toy=$(field "$path" 3)
  214. item=$(field "$path" 4)
  215. artifactName=$(field "$path" 5)
  216. r=$(get_artifact "$toy" "$item" "$artifactName")
  217. status=$(echo "$r" | cut -d ';' -f 1)
  218. if [ "$status" = 'n' ]
  219. then
  220. respond404 "artifact $toy/$item/$artifactName"
  221. return 1
  222. else
  223. contentType=$(echo "$r" | cut -d ';' -f 2)
  224. filePath=$(echo "$r" | cut -d ';' -f 3)
  225. respond200file "$filePath" "$contentType"
  226. fi
  227. else
  228. respond404 "$path"
  229. return 1
  230. fi
  231. ;;
  232. POST)
  233. check_auth "$authorization" || return 1
  234. toy=$(field "$path" 3)
  235. if [ "$(slashes "$path")" -ne 3 ] || [ -n "$(field "$path" 4)" ]
  236. then
  237. respond404 "$path"
  238. return 1
  239. fi
  240. if ! (podman image ls --format "{{.Repository}}" | grep -x "localhost/toy_$toy" -q)
  241. then
  242. respond404 "toy $toy"
  243. return 1
  244. fi
  245. if item=$(start_item "$toy")
  246. then
  247. respond202 "$toy" "$item"
  248. else
  249. respond500 "error while creating $toy"
  250. return 1
  251. fi
  252. ;;
  253. DELETE)
  254. check_auth "$authorization" || return 1
  255. if [ "$(slashes "$path")" -eq 3 ]
  256. then
  257. toy=$(field "$path" 3)
  258. item=$(field "$path" 4)
  259. if [ -z "$toy" ] || [ -z "$item" ]
  260. then
  261. respond404 "$path"
  262. return 1
  263. fi
  264. if stop_item "$toy" "$item"
  265. then
  266. respond200 ""
  267. else
  268. respond500 "error while stopping $toy/$item"
  269. return 1
  270. fi
  271. else
  272. respond404 "$path"
  273. return 1
  274. fi
  275. ;;
  276. *)
  277. respond405 "$method"
  278. return 1
  279. ;;
  280. esac
  281. }
  282. cleanup() {
  283. rm "$fifo1" "$fifo2" "$pid_file"
  284. }
  285. interrupt() {
  286. cleanup
  287. exit 0
  288. }
  289. parse() {
  290. cr=$(printf "\r")
  291. while read -r line; do
  292. line="${line%"$cr"}"
  293. case "$line" in
  294. GET*|POST*|PUT*|DELETE*|HEAD*|OPTIONS*|TRACE*)
  295. # shellcheck disable=SC2086
  296. set -- $line
  297. method=$1
  298. path=$2
  299. ;;
  300. Host:*)
  301. host=${line#Host: }
  302. ;;
  303. Accept:*)
  304. accept=${line#Accept: }
  305. ;;
  306. accept:*)
  307. accept=${line#accept: }
  308. ;;
  309. Authorization:*)
  310. authorization=${line#Authorization: }
  311. ;;
  312. authorization:*)
  313. authorization=${line#authorization: }
  314. ;;
  315. *:*)
  316. ;;
  317. "")
  318. date=$(date +'%d/%m/%Y:%H:%M:%S %z')
  319. status_length=$(execute "$method" "$path" "$authorization" "$accept")
  320. printf "%s %s \"http://%s%s\" %s\n" "$date" "$method" "$host" "$path" "$status_length"
  321. trap - INT
  322. exit
  323. ;;
  324. *)
  325. ;;
  326. esac
  327. done
  328. }
  329. # dependency verification
  330. for cmd in nc podman awk
  331. do
  332. command -v $cmd >/dev/null || { echo "$0: \`$cmd\` not found"; exit 2; }
  333. done
  334. while getopts p:h opt; do
  335. case $opt in
  336. p) port=$OPTARG;;
  337. h) usage; exit 255;;
  338. *) usage; exit 255;;
  339. esac
  340. done
  341. shift $((OPTIND-1))
  342. if [ $# -eq 0 ]; then
  343. docroot=.
  344. else
  345. docroot="$1"
  346. fi
  347. docroot=$(cd "$docroot"; pwd)
  348. # FIFO to write HTTP response
  349. if ! fifo1=$(makefifo "${0##*/}.$$.1"); then
  350. echo "$0: creating a named pipe failed. Already running?" 1>&2
  351. exit 1
  352. fi
  353. # FIFO for internal event notification
  354. if ! fifo2=$(makefifo "${0##*/}.$$.2"); then
  355. echo "$0: creating a named pipe failed. Already running?" 1>&2
  356. exit 1
  357. fi
  358. trap interrupt INT
  359. echo $$ > "$pid_file"
  360. cat 1>&2 <<EOF
  361. Simple HTTP Server
  362. Listening at the port number $port.
  363. Type ^C to quit.
  364. EOF
  365. # shellcheck disable=SC2002
  366. while cat "$fifo1" | "$nc" -l -p "$port" | parse; do
  367. :
  368. done
  369. cleanup
  370. exit