commit b8f3169f57febf0e1fc4cb6f572896595e279925 Author: Dominika Date: Sat May 23 22:13:11 2020 +0200 Initial commit, importing from tarball diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..0927556 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,157 @@ +### GNU LESSER GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +This version of the GNU Lesser General Public License incorporates the +terms and conditions of version 3 of the GNU General Public License, +supplemented by the additional permissions listed below. + +#### 0. Additional Definitions. + +As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the +GNU General Public License. + +"The Library" refers to a covered work governed by this License, other +than an Application or a Combined Work as defined below. + +An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + +A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + +The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + +The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + +#### 1. Exception to Section 3 of the GNU GPL. + +You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + +#### 2. Conveying Modified Versions. + +If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + +- a) under this License, provided that you make a good faith effort + to ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or +- b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + +#### 3. Object Code Incorporating Material from Library Header Files. + +The object code form of an Application may incorporate material from a +header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + +- a) Give prominent notice with each copy of the object code that + the Library is used in it and that the Library and its use are + covered by this License. +- b) Accompany the object code with a copy of the GNU GPL and this + license document. + +#### 4. Combined Works. + +You may convey a Combined Work under terms of your choice that, taken +together, effectively do not restrict modification of the portions of +the Library contained in the Combined Work and reverse engineering for +debugging such modifications, if you also do each of the following: + +- a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. +- b) Accompany the Combined Work with a copy of the GNU GPL and this + license document. +- c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. +- d) Do one of the following: + - 0) Convey the Minimal Corresponding Source under the terms of + this License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + - 1) Use a suitable shared library mechanism for linking with + the Library. A suitable mechanism is one that (a) uses at run + time a copy of the Library already present on the user's + computer system, and (b) will operate properly with a modified + version of the Library that is interface-compatible with the + Linked Version. +- e) Provide Installation Information, but only if you would + otherwise be required to provide such information under section 6 + of the GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the Application + with a modified version of the Linked Version. (If you use option + 4d0, the Installation Information must accompany the Minimal + Corresponding Source and Corresponding Application Code. If you + use option 4d1, you must provide the Installation Information in + the manner specified by section 6 of the GNU GPL for conveying + Corresponding Source.) + +#### 5. Combined Libraries. + +You may place library facilities that are a work based on the Library +side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + +- a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities, conveyed under the terms of this License. +- b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + +#### 6. Revised Versions of the GNU Lesser General Public License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +as you received it specifies that a certain numbered version of the +GNU Lesser General Public License "or any later version" applies to +it, you have the option of following the terms and conditions either +of that published version or of any later version published by the +Free Software Foundation. If the Library as you received it does not +specify a version number of the GNU Lesser General Public License, you +may choose any version of the GNU Lesser General Public License ever +published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/README.md b/README.md new file mode 100644 index 0000000..239df1b --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# HTTP.sh +Node.js, but `| sed s/Node/HTTP/;s/js/sh/`. + +Launch with `./http.sh`. Does not need root priviliges ~~- in fact, DO NOT run it as superuser~~ unless you're running it on ports lower than 1024. If you're running on 80 and 443, superuser is more or less mandatory, but THIS MAY BE UNSAFE. + +To prevent running malicious scripts, by default only scripts with extension `.shs` can be run by the server, but this can be changed in the config. Also, cfg[index] ignores this. SHS stands for Shell Server. + +Originally made for Junction Stupidhack 2020; Created by @redsPL, @selfisekai and @ptrcnull. + +## Dependencies + +- Bash or 100% compatible shell +- [Ncat](https://nmap.org/ncat) +- pkill +- mktemp +- dd (for accounts, multipart/form-data and websockets) +- sha1sum, sha256sum (for accounts and simple auth) +- curl (for some demos) + +## Known faults + +- can't change the HTTP status code from Shell Server scripts. This could theoretically be done with custom vhost configs and some `if` statements, but this would be a rather nasty solution to that problem. +- `$post_multipart` doesn't keep original names - could be fixed by parsing individual headers from the multipart request instead of skipping them all + +## Directory structure (incomplete) +- config + - master.sh: main config file, loaded with every request + - localhost:1337: example vhost file, loaded if `Host: ...` equals its name +- src + - server source files and modules (e.g. `ws.sh`) + - response + - files corresponding to specific HTTP status codes + - listing.sh (code 210) is actually HTTP 200, but triggered in a directory with autoindex turned on and without a valid `index.shs` file +- templates + - section templates go here +- webroot + - place your files **here** +- secret + - users/passwords go here +- storage + - random data storage for shs apps + +## Variables that we think are cool! + +- $post_data - array, contains data from urlencoded POSTs +- $post_multipart - array, contains URIs to uploaded files from multipart/form-data POSTs +- $get_data - array, contains data from GETs +- $cfg - array, contains config values (from master.sh and vhost configs) +- $r - array, contains data generated from the request - URI, URL and that kinda stuff. diff --git a/config/localhost b/config/localhost new file mode 100644 index 0000000..9aa52e1 --- /dev/null +++ b/config/localhost @@ -0,0 +1 @@ +cfg[title]='Laura is cute :3' diff --git a/config/master.sh b/config/master.sh new file mode 100644 index 0000000..0d27f65 --- /dev/null +++ b/config/master.sh @@ -0,0 +1,20 @@ +declare -A cfg + +cfg[port]=1337 + +cfg[root]='webroot/' +cfg[index]='index.shs' +cfg[autoindex]=true + +cfg[auth_required]=false +cfg[auth_realm]="Laura is cute <3" + +cfg[ssl]=true +cfg[ssl_port]=8443 +cfg[ssl_cert]='' +cfg[ssl_key]='' + +cfg[extension]='shs' +cfg[extra_headers]='server: HTTP.sh/0.9' + +cfg[title]='ddd defies development' diff --git a/http.sh b/http.sh new file mode 100755 index 0000000..fad92f9 --- /dev/null +++ b/http.sh @@ -0,0 +1,24 @@ +#!/bin/bash +trap ctrl_c INT + +function ctrl_c() { + pkill -P $$ + echo -e "Killed all remaining processes.\nHave a great day!!" +} + +source config/master.sh +echo "HTTP.sh" + +if [[ ${cfg[ssl]} == true ]]; then + echo "listening on port ${cfg[port]} (HTTP) and ${cfg[ssl_port]} (HTTPS)" + ncat -l -p ${cfg[port]} -c ./src/server.sh -k & + if [[ ${cfg[ssl_key]} != '' && ${cfg[ssl_cert]} != '' ]]; then + ncat -l -p ${cfg[ssl_port]} -c ./src/server.sh -k --ssl --ssl-cert ${cfg[ssl_cert]} --ssl-key ${cfg[ssl_key]} + else + ncat -l -p ${cfg[ssl_port]} -c ./src/server.sh -k --ssl + fi +else + echo "listening on port ${cfg[port]} (HTTP)" + ncat -l -p ${cfg[port]} -c ./src/server.sh -k +fi + diff --git a/secret/users.dat b/secret/users.dat new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/secret/users.dat @@ -0,0 +1 @@ + diff --git a/src/account.sh b/src/account.sh new file mode 100755 index 0000000..e942d0b --- /dev/null +++ b/src/account.sh @@ -0,0 +1,81 @@ +#!/bin/bash +# account.sh - account and session mgmt + + +# register(username, password) +function register() { + local username=$(echo -ne $(printf "$1" | sed -E "s/ /_/g;s/\:/\-/g;s/\%/\\x/g")) + + if [[ $(grep "$username:" secret/users.dat) != '' ]]; then + reason="This user already exists!" + return 1 + fi + + local salt=$(dd if=/dev/urandom bs=256 count=1 | sha1sum | cut -c 1-16) + local hash=$(echo -n $2$salt | sha256sum | cut -c 1-64) + local token=$(dd if=/dev/urandom bs=32 count=1 | sha1sum | cut -c 1-40) + set_cookie "sh_session" $token + set_cookie "username" $username + + echo "$username:$hash:$salt:$token" >> secret/users.dat +} + +# login(username, password) +function login() { + local username=$(echo -ne $(echo "$1" | sed -E 's/%/\\x/g')) + echo $1 $username > /dev/stderr + IFS=':' + local user=($(grep "$username:" secret/users.dat)) + unset IFS + if [[ $(echo -n $2${user[2]} | sha256sum | cut -c 1-64 ) == ${user[1]} ]]; then + set_cookie "sh_session" ${user[3]} + set_cookie "username" $username + return 0 + else + remove_cookie "sh_session" + remove_cookie "username" + reason="Invalid credentials!!11" + return 1 + fi +} + +# login_simple(base64) +function login_simple() { + local data=$(echo $3 | base64 -d) + echo $3 > /dev/stderr + local password=$(echo $data | sed -E 's/^(.*)\://') + local login=$(echo $data | sed -E 's/\:(.*)$//') + + IFS=':' + local user=($(grep "$login:" secret/users.dat)) + unset IFS + if [[ $(echo -n $password${user[2]} | sha256sum | cut -c 1-64 ) == ${user[1]} ]]; then + r[authorized]=true + echo "nay" > /dev/stderr + else + r[authorized]=false + fi +} + +# logout() +function logout() { + remove_cookie "sh_session" + remove_cookie "username" +} + +# session_verify(session) +function session_verify() { + if [[ $(grep ":$1" secret/users.dat) != '' && $1 != '' ]]; then + return 0 + else + return 1 + fi +} + +# session_get_username(session) +function session_get_username() { + IFS=':' + local data=($(grep ":$1" secret/users.dat)) + unset IFS + echo ${data[0]} +} diff --git a/src/mime.sh b/src/mime.sh new file mode 100755 index 0000000..e387bbe --- /dev/null +++ b/src/mime.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# mime.sh - determine what Content-Type should be passed on +# +# Common HTML files (.html/.htm) -> text/html +# Shell Server Scripts (.shs) -> leaves without any content type, TBD by the browser +# CSS files (.css) -> text/css +# Text files (mimetype starting with 'text/') -> text/plain (fixes XSS in pastebin) +# All else -> pass real mimetype + +function get_mime() { + local file=$@ + local mime=$(file --mime-type -b $file) + echo $file $mime > /dev/stderr + if [[ $file == *".htm" || $file == *".html" ]]; then + content_type="text/html" + return 0 + elif [[ $file == *".shs" ]]; then + content_type="" + return 0 + elif [[ $file == *".css" ]]; then + content_type="text/css" + elif [[ $mime == "text/"* ]]; then + content_type="text/plain" + else + content_type="$mime" + fi +} diff --git a/src/misc.sh b/src/misc.sh new file mode 100755 index 0000000..70bb4e5 --- /dev/null +++ b/src/misc.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# misc.sh - miscellaneous functions + +# set_cookie(cookie_name, cookie_content) +function set_cookie() { + r[headers]+="Set-Cookie: $1=$2\r\n" +} + +# remove_cookie(cookie_name) +function remove_cookie() { + r[headers]+="Set-Cookie: $1=; Expires=Sat, 02 Apr 2005 20:37:00 GMT\r\n" +} + +# header(header, header...) +function header() { + for i in "$@"; do + r[headers]+="$i\r\n" + done +} diff --git a/src/response/101.sh b/src/response/101.sh new file mode 100755 index 0000000..c3721c8 --- /dev/null +++ b/src/response/101.sh @@ -0,0 +1,31 @@ +echo "HTTP/1.1 101 Web Socket Protocol Handshake +Connection: Upgrade +Upgrade: WebSocket +${cfg[extra_headers]}" +if [[ ${r[websocket_key]} != '' ]]; then + accept=$(echo -ne $(printf "${r[websocket_key]}""258EAFA5-E914-47DA-95CA-C5AB0DC85B11" | sha1sum | sed 's/ //g;s/-//g;s/.\{2\}/\\x&/g') | base64) + echo "Sec-WebSocket-Accept: "$accept +fi +printf "\r\n\r\n" + +#echo "Laura is cute <3" +#WebSocket-Location: ws://localhost:1337/ +#WebSocket-Origin: http://localhost:1337/\r\n\r\n " + +source ./src/ws.sh + +#input='' +#while read -N 1 chr; do +# input=$input$chr +# if [[ $chr == "\r" ]]; then +# break +# fi +#done + + +exit 0 +#while true; do +# read test +# echo $test +# sleep 1 +#done diff --git a/src/response/200.sh b/src/response/200.sh new file mode 100755 index 0000000..60f5f78 --- /dev/null +++ b/src/response/200.sh @@ -0,0 +1,16 @@ +printf "HTTP/1.0 200 OK +${cfg[extra_headers]}\r\n" +get_mime ${r[uri]} +[[ $content_type != '' ]] && printf "content-type: $content_type\r\n" + +if [[ ${r[uri]} =~ \.${cfg[extension]}$ ]]; then + temp=$(mktemp) + source "${r[uri]}" > $temp + [[ ${r[headers]} != '' ]] && printf "${r[headers]}\r\n\r\n" || printf "\r\n" + cat $temp + rm $temp +else + printf "\r\n" + cat "${r[uri]}" +fi + diff --git a/src/response/401.sh b/src/response/401.sh new file mode 100755 index 0000000..e3bb213 --- /dev/null +++ b/src/response/401.sh @@ -0,0 +1,3 @@ +printf "HTTP/1.0 401 Unauthorized +WWW-Authenticate: Basic realm=\"${cfg[auth_realm]}\" +${cfg[extra_headers]}\r\n" diff --git a/src/response/403.sh b/src/response/403.sh new file mode 100755 index 0000000..44a2efb --- /dev/null +++ b/src/response/403.sh @@ -0,0 +1,4 @@ +printf "HTTP/1.0 403 Forbidden +${cfg[extra_headers]}\r\n\r\n" +source templates/head.sh +echo "

403: You've been naughty

" diff --git a/src/response/404.sh b/src/response/404.sh new file mode 100755 index 0000000..b60b500 --- /dev/null +++ b/src/response/404.sh @@ -0,0 +1,4 @@ +printf "HTTP/1.0 404 Not Found +${cfg[extra_headers]}\r\n\r\n" +source templates/head.sh +echo "

404 Not Found

" diff --git a/src/response/listing.sh b/src/response/listing.sh new file mode 100755 index 0000000..8fea773 --- /dev/null +++ b/src/response/listing.sh @@ -0,0 +1,33 @@ +printf "HTTP/1.0 200 OK +${cfg[extra_headers]}\r\n\r\n" + +source templates/head.sh + +printf "

Index of $([[ ${r[url]} == '' ]] && echo '/' || echo ${r[url]})

" + +if [[ ${cookies[username]} != '' ]]; then + echo "Logged in as ${cookies[username]}" +fi + +printf " + + + + + + + +" +IFS=$'\n' + +for i in $(ls ${r[uri]}); do + unset IFS + stats=($(ls -hld "${r[uri]}/$i")) # -hld stands for Half-Life Dedicated + if [[ -d ${r[uri]}'/'$i ]]; then + printf "" + else + printf "" + fi +done + +printf "
FileSizeDate
..
$i<DIR>${stats[5]} ${stats[6]} ${stats[7]}
$i${stats[4]}B${stats[5]} ${stats[6]} ${stats[7]}

HTTP.sh server on ${r[host]}

laura is cute

" diff --git a/src/server.sh b/src/server.sh new file mode 100755 index 0000000..4ed5c23 --- /dev/null +++ b/src/server.sh @@ -0,0 +1,174 @@ +#!/bin/bash +source config/master.sh +source src/mime.sh +source src/misc.sh +source src/account.sh + +declare -A r # current request / response +declare -A meta # metadata for templates +declare -A cookies # cookies! + +r[status]=210 # Mommy always said that I was special +post_length=0 +post=false +get=false + +while read param; do + if [[ $param == $'\015' ]]; then + break + + elif [[ $param == *"Content-Length:"* ]]; then + r[content_length]=$(echo -n $param | sed 's/Content-Length: //;s/\r//') + + elif [[ $param == *"Content-Type:"* ]]; then + r[content_type]=$(echo -n $param | sed 's/Content-Type: //;s/\r//') + if [[ ${r[content_type]} == *"multipart/form-data"* ]]; then + tmpdir=$(mktemp -d) + fi + if [[ ${r[content_type]} == *"boundary="* ]]; then + r[content_boundary]=$(echo -n ${r[content_type]} | sed -E 's/(.*)boundary=//;s/\r//;s/ //') + fi + + elif [[ $param == *"Host:"* ]]; then + r[host]=$(printf "$param" | sed 's/Host: //;s/\r//') + r[host_portless]=$(echo ${r[host]} | sed -E 's/:(.*)$//') + if [[ -f "config/${r[host]}" ]]; then + source "config/${r[host]}" + elif [[ -f "config/${r[host_portless]}" ]]; then + source "config/${r[host_portless]}" + fi + + elif [[ $param == *"Upgrade:"* && $(printf "$param" | sed 's/Upgrade: //;s/\r//') == "websocket" ]]; then + r[status]=101 + + elif [[ $param == *"Sec-WebSocket-Key:"* ]]; then + r[websocket_key]=$(printf "$param" | sed 's/Sec-WebSocket-Key: //;s/\r//') + + elif [[ $param == *"Authorization: Basic"* ]]; then + login_simple $param + + elif [[ $param == *"Cookie: "* ]]; then + for i in $(echo $param | sed -E 's/Cookie: //;s/\;//g;s/%/\\x/g'); do + name=$(echo $i | sed -E 's/\=(.*)$//') + value=$(echo $i | sed -E 's/^(.*)\=//') + cookies[$name]=$(echo -e $value) + done + + elif [[ $param == *"GET "* ]]; then + r[url]=$(echo -ne "$(echo -n $param | sed -E 's/GET //;s/HTTP\/[0-9]+\.[0-9]+//;s/ //g;s/\%/\\x/g;s/\/*\r//g;s/\/\/*/\//g')") + data=$(echo ${r[url]} | sed -E 's/^(.*)\?//;s/\&/ /g') + if [[ $data != ${r[url]} ]]; then + declare -A get_data + for i in $data; do + name=$(echo $i | sed -E 's/\=(.*)$//') + value=$(echo $i | sed "s/$name\=//") + get_data[$name]=$value + done + fi + + elif [[ $param == *"POST "* ]]; then + r[url]=$(echo -ne "$(echo -n $param | sed -E 's/POST //;s/HTTP\/[0-9]+\.[0-9]+//;s/ //g;s/\%/\\x/g;s/\/*\r//g;s/\/\/*/\//g')") + r[post]=true + # below shamelessly copied from GET, should be moved to a function + data=$(echo ${r[url]} | sed -E 's/^(.*)\?//;s/\&/ /g') + if [[ $data != ${r[url]} ]]; then + declare -A get_data + for i in $data; do + name=$(echo $i | sed -E 's/\=(.*)$//') + value=$(echo $i | sed "s/$name\=//") + get_data[$name]=$value + done + fi + + fi +done + +r[uri]=$(realpath ${cfg[root]}$(echo ${r[url]} | sed -E 's/\?(.*)$//')) +[[ -d "${r[uri]}/" ]] && pwd="${r[uri]}" || pwd=$(dirname "${r[uri]}") + +# shitty logging +echo "-------------" >> log +echo $(date) >> log +echo "URL: ${r[url]}, GET_data: ${get_data[@]}, POST_data: ${post_data[@]}, POST_multipart: ${post_multipart[@]}" >> log + +echo ${r[uri]} > /dev/stderr + +if [[ ${r[status]} != 101 ]]; then + if [[ -a ${r[uri]} && ! -r ${r[uri]} ]]; then + r[status]=403 + elif [[ "$(echo -n ${r[uri]})" != "$(realpath ${cfg[root]})"* ]]; then + r[status]=403 + elif [[ -f ${r[uri]} ]]; then + r[status]=200 + elif [[ -d ${r[uri]} ]]; then + for name in ${cfg[index]}; do + if [[ -f "${r[uri]}/$name" ]]; then + r[uri]="${r[uri]}/$name" + r[status]=200 + fi + done + else + r[status]=404 + fi +fi + +if [[ ${cfg[auth_required]} == true && ${r[authorized]} != true ]]; then + echo "Auth failed." >> log + r[status]=401 +fi + +if [[ ${r[post]} == true && ${r[status]} == 200 ]]; then + + # This whole ordeal is here to prevent passing binary data as a variable. + # I could have done it as an array, but this solution works, and it's + # speedy enough so I don't care. + + if [[ $tmpdir ]]; then + declare post_multipart + tmpfile=$(mktemp -p $tmpdir) + dd iflag=fullblock of=$tmpfile ibs=${r[content_length]} count=1 obs=1M + + delimeter_len=$(echo -n "$content_boundary"$'\015' | wc -c) + boundaries_list=$(echo -ne $(grep $tmpfile -ao -e ${r[content_boundary]} --byte-offset | sed -E 's/:(.*)//g') | sed -E 's/ [0-9]+$//') + + for i in $boundaries_list; do + tmpout=$(mktemp -p $tmpdir) + dd iflag=fullblock if=$tmpfile ibs=$(($i+$delimeter_len)) obs=1M skip=1 | while true; do + read line + if [[ $line == $'\015' ]]; then + cat - > $tmpout + break + fi + done + length=$(grep $tmpout --byte-offset -ae ${r[content_boundary]} | sed -E 's/:(.*)//' | head -n 1) + outfile=$(mktemp -p $tmpdir) + post_multipart+=($outfile) + dd iflag=fullblock if=$tmpout ibs=$length count=1 obs=1M of=$outfile + rm $tmpout + done + rm $tmpfile + else + read -N ${r[content_length]} data + declare -A post_data + + for i in $(echo $data | sed -s 's/\&/ /g;'); do + name=$(echo $i | sed -E 's/\=(.*)$//') + param=$(echo $i | sed "s/$name\=//") + post_data[$name]=$param + done + fi +fi + +if [[ ${r[status]} == 210 && ${cfg[autoindex]} == true ]]; then + source "src/response/listing.sh" +elif [[ ${r[status]} == 200 ]]; then + source "src/response/200.sh" +elif [[ ${r[status]} == 401 ]]; then + source "src/response/401.sh" +elif [[ ${r[status]} == 404 ]]; then + source "src/response/404.sh" +elif [[ ${r[status]} == 101 ]]; then + source "src/response/101.sh" +else + source "src/response/403.sh" +fi diff --git a/src/ws.sh b/src/ws.sh new file mode 100755 index 0000000..bd08867 --- /dev/null +++ b/src/ws.sh @@ -0,0 +1,69 @@ +#!/bin/zsh +read bytes + +echo "bytes:" > /dev/stderr +echo $bytes | hexdump -C > /dev/stderr + +readarray -t data <<< $(echo -n $bytes | hexdump -e '/1 "%u\n"') + +echo "data:" > /dev/stderr +echo ${data[@]} | hexdump -C > /dev/stderr + + +echo "FIN bit:" $(( ${data[0]} >> 7 )) > /dev/stderr + +echo "opcode:" $(( ${data[0]} & 0x0f )) > /dev/stderr + +echo ${data[0]} > /dev/stderr + +mask_bit=$(( ${data[1]} >> 7 )) +echo "Mask (bit):" $mask_bit > /dev/stderr + +len=$(( ${data[1]} & 0x7f )) +offset=2 # 2 for header + +if [[ $len == 126 ]]; then + len=$(( ${data[2]} << 8 + ${data[3]} )) + offset=4 # 2 for header, 2 for extended length +elif [[ $len == 127 ]]; then + len=0 + for i in {0..8}; do + len=$(( $len << 8 + ${data[2+i]} )) + done + offset=10 # 2 for header, 8 for extended length +fi + +echo "Data length:" $len > /dev/stderr + +echo "Offset:" $offset > /dev/stderr + +if [[ $mask_bit == 1 ]]; then + read -ra mask <<< ${data[@]:$offset:4} + + echo "Mask:" $mask > /dev/stderr + + offset=$(( $offset + 4 )) +fi + +read -ra payload <<< ${data[@]:$offset:$len} + +echo "Payload:" ${payload[@]} > /dev/stderr + +if [[ $mask_bit == 1 ]]; then + for ((i = 0; i < $len; i++)); do + byte=$(( ${payload[$i]} ^ ${mask[$i % 4]} )) + echo "Byte $i: $byte" > /dev/stderr + payload[$i]=$byte + done + + echo "Payload unmasked:" ${payload[@]} > /dev/stderr +fi + +payload_hex="" +for ((i = 0; i < $len; i++)); do + hex=$(printf '\\x%x\n' $(( ${payload[$i]} ))) + payload_hex+=$hex +done + +echo "Payload hex:" $payload_hex > /dev/stderr +echo -e "Payload: $payload_hex" > /dev/stderr diff --git a/storage/faves b/storage/faves new file mode 100644 index 0000000..7b71c6e --- /dev/null +++ b/storage/faves @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/storage/pastes b/storage/pastes new file mode 100644 index 0000000..e69de29 diff --git a/templates/head.sh b/templates/head.sh new file mode 100755 index 0000000..10271b7 --- /dev/null +++ b/templates/head.sh @@ -0,0 +1,36 @@ +#!/bin/bash +echo ' + + + ' + +if [[ ${meta[title]} ]]; then echo "${meta[title]} - ${cfg[title]}"; else echo "${cfg[title]}"; fi +if [[ ${meta[author]} ]]; then echo ""; fi +if [[ ${meta[description]} ]]; then echo ""; fi +if [[ ${meta[keywords]} ]]; then echo ""; fi +if [[ ${meta[refresh]} ]]; then echo ""; fi +if [[ ${meta[redirect]} ]]; then echo ""; fi +if [[ ${meta[css]} ]]; then echo ""; fi +if [[ ${meta[title]} ]]; then echo ""; fi +#if [[ ${meta[og:type]} ]]; then echo ""; fi +if [[ ${cfg[url]} ]]; then echo ""; fi +#if [[ ${meta[og:image} ]]; then echo ""; fi +if [[ ${meta[lang]} ]]; then echo ""; fi +if [[ ${meta[description]} ]]; then echo ""; fi + +echo " +" diff --git a/webroot/allegro/index.shs b/webroot/allegro/index.shs new file mode 100644 index 0000000..0fe519a --- /dev/null +++ b/webroot/allegro/index.shs @@ -0,0 +1,8 @@ +#!/bin/bash + +source templates/head.sh + +echo "
+ + +
" diff --git a/webroot/allegro/listing.shs b/webroot/allegro/listing.shs new file mode 100755 index 0000000..ce0148b --- /dev/null +++ b/webroot/allegro/listing.shs @@ -0,0 +1,68 @@ +#!/bin/bash +cat webroot/allegro/listing_top.txt + +query="$(echo "${get_data[q]}" | sed -s "s/ /%20/g;s/+/%20/g")" +query_nice=$(echo ${get_data[q]} | sed -s "s/+/ /g;") +[[ ${get_data[order]} ]] && order=${get_data[order]} || order='p' + +echo "Searching for '$query_nice'" + +IFS=$'\n' +data=$(curl "https://allegro.pl/listing?string=$query&order=$order" | sed -E 's/<\/article>/\n/g' | grep -ohE "allegro.pl/oferta(.*)") + +cost=($(echo "$data" | grep -Eoh "span class=\"[A-Za-z0-9_]{12}\">([0-9]+|[0-9]+ [0-9]+),<\/span>[0-9]+" | sed -E 's/span class=\"[A-Za-z0-9_]{12}\">//g;s///g;s/<\/span>//g' | grep -ohE "[a-z0-9].allegroimg.com/s[0-9]+/[a-z0-9]{6}/[a-z0-9]{28}/(.*)")) +name=($(echo "${data}" | sed -E 's/<\/a>/\n/g' | grep -ohE 'title="">(.*)' | sed -s 's/title="">//g')) +links=($(echo "${data}" | sed -E 's/"(.*)//g')) + +for (( i=0; i<${#img[@]}; i++ )) do + #echo "" + + echo " + + + + + + + + +
+ + ${name[$i]} + + + + + + +
+ + + + Kup Teraz! ${cost[$i]} zł + + + +
+ + z dostawą: + ??? zł + +
+ + + + Popularność: ??? + + + Do końca: ??? + + + + " + + +done + +cat webroot/allegro/listing_bottom.txt diff --git a/webroot/allegro/listing_bottom.txt b/webroot/allegro/listing_bottom.txt new file mode 100644 index 0000000..4178eaa --- /dev/null +++ b/webroot/allegro/listing_bottom.txt @@ -0,0 +1,820 @@ + + + + + + + + + + + +
+
+ +  1  + + 2 + + 3 + + ... 179 + + + +
+
+ Wersja do druku + +
+ + + + + +
+ + +
+

+ Korzystanie z serwisu oznacza akceptację regulaminu +

+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/webroot/allegro/listing_top.txt b/webroot/allegro/listing_top.txt new file mode 100644 index 0000000..5b7852d --- /dev/null +++ b/webroot/allegro/listing_top.txt @@ -0,0 +1,1321 @@ + + + + + + + + + Allegro.pl - Więcej niż aukcje. Najlepsze oferty na największej platformie handlowej. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + + + + +
+
+

Podgląd strony do wydruku

+

Niniejszy komunikat nie zostanie wydrukowany.

+
+ +
+ + +
+
+
+
+
+

ibm

- znaleziono 8945 przedmiotów. +
+
+   +   +
+ + + +
+
+
+
+
+ Dodaj do ulubionych kryteriów wyszukiwania
+ RSS dla tych wyników wyszukiwania
+
+
+
+ + + + + +
+ + + + + + +
+ + +
+
+

+
    +
+
+
+
+ + + + + +
+ +
+ + + + + + +
+ + +
+
+ +
+ +
+ + + + + + + + + + + + + +
+ +
+ + + + + +
+
+ +
+
+
+ Porównanie + + Widok:  + + + +
+
+ Strona: + + +   z 179 +
+
+ +Sortuj według: + + +
+ + + + +
+ + + + + + + + + + + + + + diff --git a/webroot/boards/i/empty b/webroot/boards/i/empty new file mode 100644 index 0000000..e69de29 diff --git a/webroot/boards/index.shs b/webroot/boards/index.shs new file mode 100644 index 0000000..a710133 --- /dev/null +++ b/webroot/boards/index.shs @@ -0,0 +1,34 @@ +#!/bin/bash +domainprefix='https://megumin.tech/boards' + +meta[title]='bashchan' +meta[css]='style.css' +source templates/head.sh + +echo " +
+
+ bashchan
- a place for all your shitposts +
+" +IFS=$'\n' +for i in $(ls -t storage/threads); do + IFS=':' + array=($(cat storage/threads/$i | head -n 1)) + echo "
" + if [[ ${array[1]} != '' ]]; then + echo "${array[1]} | " + fi + echo "${array[0]}
${array[2]}

" +done + + +echo "

Submit a new thread

+
+ + +
+
+ + + " diff --git a/webroot/boards/post.shs b/webroot/boards/post.shs new file mode 100644 index 0000000..58d4c42 --- /dev/null +++ b/webroot/boards/post.shs @@ -0,0 +1,48 @@ +#!/bin/bash + +title=$(cat ${post_multipart[2]} | sed -s 's//g;s/://g' | tr -d '\n\r') +img=${post_multipart[4]} +img_name=$(basename ${post_multipart[4]}) +rel=$(cat ${post_multipart[0]} | sed -E 's/' ]]; then + echo "Post something!" + return 1 +fi + +if session_verify ${cookies[sh_session]}; then + username=$(session_get_username ${cookies[sh_session]}) +else + username='Anon' +fi + +if [[ $rel == *"new"* ]]; then + rel=$(($(ls storage/threads/ | wc -l)+1)) + touch "storage/threads/$rel" +fi + +if [[ -f "storage/threads/$rel" ]]; then + echo "$username:$title:$msg:$img_name" | sed -E "s/\r//g" >> "storage/threads/$rel" +else + echo "No such thread!!" + return 1 +fi + +meta[redirect]="thread.shs?id=$rel" + +source templates/head.sh + +echo "Submitted succesfully.
+Click here if you're not redirected" diff --git a/webroot/boards/style.css b/webroot/boards/style.css new file mode 100644 index 0000000..8d1b432 --- /dev/null +++ b/webroot/boards/style.css @@ -0,0 +1,42 @@ +.center { + margin-left: auto; + margin-right: auto; + max-width: 100%; + display: block; + text-align: center; + margin-bottom: 30px; +} + +.file { + width: 225px +} +.msg { + width: 325px; + height: 125px; +} +.title { + width: 318px; +} +.submit { + width: 95px; +} +.pic { + width: 200px; +} + +.thread_title { + color: #f44; +} + +.poster { + color: #4f4; +} + +.post { + margin: 5px; + background-color: #222; +} +.nolinkdecoration, .nolinkdecoration > div { + color: #ccc; + text-decoration: none; +} diff --git a/webroot/boards/thread.shs b/webroot/boards/thread.shs new file mode 100644 index 0000000..4df422d --- /dev/null +++ b/webroot/boards/thread.shs @@ -0,0 +1,46 @@ +#!/bin/bash +domainprefix='https://megumin.tech/boards' + +id=${get_data[id]} +if [[ $id == '' ]]; then + return 1 +elif [[ $id == *".."* ]]; then + return 1 +fi + +meta[css]='style.css' +meta[title]="Thread #$id" +source templates/head.sh + +echo " +
+
+ bashchan
- a place for all your shitposts +
+" + +IFS=$'\n' +for i in $(cat storage/threads/$id); do + IFS=':' + array=($i) + echo "
" + if [[ ${array[1]} != '' ]]; then + echo "${array[1]} | " + fi + echo "${array[0]}
" + if [[ -a "$pwd/i/${array[3]}" ]]; then + echo "" + fi + echo "${array[2]}
" +done + + +echo "
+ + +
+
+ + + +" diff --git a/webroot/img/branding_chinacat.png b/webroot/img/branding_chinacat.png new file mode 100644 index 0000000..6dd0b4c Binary files /dev/null and b/webroot/img/branding_chinacat.png differ diff --git a/webroot/img/branding_pxplus.png b/webroot/img/branding_pxplus.png new file mode 100644 index 0000000..a2201a6 Binary files /dev/null and b/webroot/img/branding_pxplus.png differ diff --git a/webroot/index.shs b/webroot/index.shs new file mode 100644 index 0000000..414d27e --- /dev/null +++ b/webroot/index.shs @@ -0,0 +1,65 @@ +#!/bin/bash + +meta[title]='HTTP.sh' +meta[author]='Dominika, Laura, Patrycja' +meta[description]='HTTP.sh - a quick and dirty webserver, written in Bash' +meta[keywords]='bash,http,web,server,http.sh' +meta[lang]='en' + +source templates/head.sh + +echo " +" +if [[ ! ${cookies[sh_session]} ]]; then + echo "" +else + echo "
Logged in as ${cookies[username]}| Log out
" +fi +echo " +

HTTP.sh - a quick and dirty webserver, written in Bash

+ +

+Although we've seen several attempts to create a Bash web framework, all of them fell flat one way or another.
+We've decided to change that. Did we succeed? You be the judge! +

+ +

Download

+

+This web server was tested as much as the time allowed, but we can't say for certain that it's bug free.
+If you want to check out our (dirty) code, download the current tarball.

+We also can't guarantee that HTTP.sh is asbestos free, so proceed with caution. +

+ +

Dependencies

+
    +
  1. Bash or 100% compatible shell
  2. +
  3. Ncat - nc/nc.traditional WILL NOT work
  4. +
  5. GNU coreutils (dd, base64 and others)
  6. +
  7. pkill
  8. +
  9. mktemp
  10. +
  11. sha1sum, sha256sum
  12. +
  13. curl (only for a few demos)
  14. +
  15. certbot (only if you'd like to get a SSL cert) +
+ +

Demos

+
    +
  1. bashchan (an imageboard)
  2. +
  3. bashbin (a pastebin)
  4. +
  5. bashtube (a YouTube frontend)
  6. +
  7. bashurl (a URL shortener)
  8. +
  9. bashupload (a file uploader)
  10. +
  11. bashallgero (a frontend for a Polish auction site)
  12. +
  13. listing (just a file listing, but it looks mighty cool)
  14. +
+ +
+Made by reds, selfisekai and ptrcnull. IF YOU LOG IN, THIS SITE WILL STORE COOKIES ON YOUR PERSONAL COMPUTING DEVICE" diff --git a/webroot/login.shs b/webroot/login.shs new file mode 100755 index 0000000..63e818b --- /dev/null +++ b/webroot/login.shs @@ -0,0 +1,30 @@ +#!/bin/bash + +if [[ ${post_data[login]} != '' && ${post_data[password]} != '' ]]; then + login ${post_data[login]} ${post_data[password]} + status=$? + if [[ $status == 0 ]]; then + echo ${get_data[r]} > /dev/stderr + if [[ ${get_data[r]} == '' ]]; then + meta[redirect]='/' + else + meta[redirect]="$(echo ${get_data[r]} | sed -s 's/Log in... +
+ +
+ +
+ + +(please make sure that you're connecting over SSL)" diff --git a/webroot/logout.shs b/webroot/logout.shs new file mode 100755 index 0000000..538ef39 --- /dev/null +++ b/webroot/logout.shs @@ -0,0 +1,7 @@ +#!/bin/bash + +logout +meta[redirect]='/' +source templates/head.sh + +echo "Logged out successully. Redirecting.." diff --git a/webroot/pastebin/index.shs b/webroot/pastebin/index.shs new file mode 100755 index 0000000..fcd27a2 --- /dev/null +++ b/webroot/pastebin/index.shs @@ -0,0 +1,56 @@ +#!/bin/bash +domainprefix="https://paste.megumin.tech/" + +if [[ ${post_data[paste]} ]]; then + filepath=$(mktemp -p $pwd XXX) + filename=$(basename $filepath) + if [[ ${post_data[public]} == "on" ]]; then + echo $filename >> $pwd/list + fi + if session_verify ${cookies[sh_session]}; then + echo "$filename:$(session_get_username ${cookies[sh_session]})" >> storage/pastes + fi + echo -e $(echo "${post_data[paste]}" | sed -E 's/\+/ /g;s/\%/\\x/g') > $filepath + meta[redirect]="$domainprefix$filename" +fi + +source templates/head.sh + +echo " + +
+ + +
+ + +" + +if session_verify ${cookies[sh_session]}; then + echo "

Your pastes:

" + pastes=$(grep -E "$(session_get_username ${cookies[sh_session]})\$" storage/pastes) + if [[ $pastes != '' ]]; then + echo "
    " + for i in $pastes; do + IFS=':' + array=($i) + echo "
  1. ${array[0]}
  2. " + done + echo "
" + else + echo "You currently do not have any pastes." + fi +else + echo "If you log in, we'll be able to show you your paste history here :)" +fi + +echo "

Latest public pastes:

    " +public=$(tac $pwd/list | head -n 10) + +IFS=$'\n' +for i in $public; do + echo "
  1. $i
  2. " +done +unset IFS +echo "" + diff --git a/webroot/pastebin/list b/webroot/pastebin/list new file mode 100644 index 0000000..e69de29 diff --git a/webroot/register.shs b/webroot/register.shs new file mode 100755 index 0000000..4913367 --- /dev/null +++ b/webroot/register.shs @@ -0,0 +1,25 @@ +#!/bin/bash + +if [[ ${post_data[login]} != '' && ${post_data[password]} != '' ]]; then + register ${post_data[login]} ${post_data[password]} + status=$? + if [[ $status == 0 ]]; then + meta[redirect]='/' + fi +fi + +source templates/head.sh + +if [[ $status == 1 && $reason != '' ]]; then + echo $reason +fi + +echo "

    Register

    +
    + +
    + +
    + + +(please make sure that you're connecting over SSL)" diff --git a/webroot/shortener/index.shs b/webroot/shortener/index.shs new file mode 100755 index 0000000..b785821 --- /dev/null +++ b/webroot/shortener/index.shs @@ -0,0 +1,35 @@ +#!/bin/bash +domainprefix="https://url.megumin.tech/" +not_found=false + +if [[ ${get_data[u]} ]]; then + id=${get_data[u]} + if [[ -f storage/shortener/$id ]]; then + meta[redirect]=$(cat storage/shortener/$id) + else + not_found=true + fi +fi + +meta[title]="url shortener i guess" +source templates/head.sh + +if [[ $not_found == "true" ]]; then + echo "

    ID not found

    " +else + if [[ ${post_data[url]} ]]; then + if [[ ! -d storage/shortener ]]; then + mkdir storage/shortener + fi + id=$(dd if=/dev/urandom bs=256 count=1 | sha1sum | cut -c 1-8) + link=$(printf $(sed 's/%/\\x/g' <<< ${post_data[url]})) + echo -n $link > storage/shortener/$id + echo "

    URL created: link

    " + else + echo '

    Revolutionary URL shortener

    +
    + + + '; + fi +fi diff --git a/webroot/upload/index.shs b/webroot/upload/index.shs new file mode 100755 index 0000000..1b017f2 --- /dev/null +++ b/webroot/upload/index.shs @@ -0,0 +1,18 @@ +#!/bin/bash +source templates/head.sh +domainprefix="https://f.megumin.tech/" + +if [[ ${post_multipart} != '' ]]; then + for i in ${post_multipart[@]}; do + asdf=$(mktemp -p $(dirname ${r[uri]}) XXX) + relative=$(realpath --relative-to "$(dirname ${r[uri]})" $asdf) + mv "$i" $asdf + rm "$i" + echo "Your uploaded file" + done +else +echo '
    +
    + + ' +fi diff --git a/webroot/yt/fav.shs b/webroot/yt/fav.shs new file mode 100644 index 0000000..f9d0444 --- /dev/null +++ b/webroot/yt/fav.shs @@ -0,0 +1,19 @@ +#!/bin/bash + +if session_verify ${cookies[sh_session]} && [[ ${get_data[v]} != '' ]]; then + title=$(youtube-dl -e "https://youtube.com/watch?v=${get_data[v]}") + if [[ $(cat storage/faves | grep -F "$title" | grep "$(echo ${cookies[username]} | sed -E "s/\r//")") == '' ]]; then + echo "$(session_get_username ${cookies[sh_session]}):${get_data[v]}:$title" >> storage/faves + fi +fi + +source templates/head.sh + +echo "<--- back to main

    Your favourites

    " + +IFS=$'\n' +for i in $(tac storage/faves); do + IFS=':' + array=($i) + echo ""; +done diff --git a/webroot/yt/index.shs b/webroot/yt/index.shs new file mode 100755 index 0000000..2e57b6d --- /dev/null +++ b/webroot/yt/index.shs @@ -0,0 +1,16 @@ +#!/bin/bash +source templates/head.sh + +echo "
    + + + + +

    All user's favourites

    " + +IFS=$'\n' +for i in $(tac storage/faves); do + IFS=':' + array=($i) + echo "

    ${array[@]:2:999}

    Favourited by ${array[0]}
    "; +done diff --git a/webroot/yt/search.shs b/webroot/yt/search.shs new file mode 100755 index 0000000..514f324 --- /dev/null +++ b/webroot/yt/search.shs @@ -0,0 +1,34 @@ +#!/bin/bash +meta[title]="youtube but not really" +source templates/head.sh + +# GOOD LUCK FIXING IT WHEN GOOGLE BREAKS IT LOL + +echo "
    + + + +" + +if [[ ${get_data[q]} ]]; then + query=${get_data[q]} + query_nice=$(echo ${get_data[q]} | sed -s 's/+/ /g') + + echo "

    Searching for '$query_nice'

    " + data='' + while [[ $data == '' ]]; do + data=$(curl -s "https://www.youtube.com/results?search_query=$query&hl=en&hs=en") + done + + + IFS=$'\n' + id=($(echo $data | sed -E 's/<\/a>/\n/g' | grep -ohE 'watch\?v\=[-A-Za-z0-9_]{11}" (.*) rel\="spf-prefetch"' | sed -E 's/watch\?v\=//g;s/" class="yt-uix-tile-link(.*)//g')) + title=($(echo $data | sed -E 's/<\/a>/\n/g' | grep -ohE 'watch\?v\=[-A-Za-z0-9_]{11}" (.*) rel\="spf-prefetch"' | sed -E 's/watch\?v\=[-A-Za-z0-9_]{11}" class\="yt-uix-tile-link(.*)title\="/ /g;s/" rel\="spf-prefetch"//g')) + unset IFS + + for (( i=0; i<${#id[@]}; i++ )); do + echo "
    ${title[$i]}

    " + done +else + echo "search for something will 'ya?" +fi diff --git a/webroot/yt/unfav.shs b/webroot/yt/unfav.shs new file mode 100644 index 0000000..6ed4337 --- /dev/null +++ b/webroot/yt/unfav.shs @@ -0,0 +1,10 @@ +#!/bin/bash + +if session_verify ${cookies[sh_session]} && [[ ${get_data[v]} != '' ]]; then + name=$(session_get_username ${cookies[sh_session]}) + vid=$(echo -n ${get_data[v]} | sed -E 's/\r//g') + sed -i "/^$name:$vid/d" storage/faves +fi +meta[redirect]='fav.shs' + +source templates/head.sh diff --git a/webroot/yt/watch.shs b/webroot/yt/watch.shs new file mode 100755 index 0000000..cc55bd8 --- /dev/null +++ b/webroot/yt/watch.shs @@ -0,0 +1,59 @@ +#!/bin/bash + +# fetching recommended disabled for speed + +#for i in $(curl https://www.youtube.com/watch?v=${get_data[v]} | grep -ohE "watch\?v\=[A-zaz0-9]{11}" | uniq | cut -c 9-19); do +# echo "

    "; +#done + +if [[ ${get_data[v]} ]]; then + video=$(youtube-dl -J "http://youtube.com/watch?v=${get_data[v]}") + if [[ $video == '' ]]; then + video=$(youtube-dl -J "http://youtube.com/watch?v=${get_data[v]}") + if [[ $video == '' ]]; then + return + fi + fi + #echo "http://youtube.com/watch?v=${get_data[v]}" > /dev/stderr + title=$(echo $video | jq -r '.title') + meta[title]=$title + source templates/head.sh + IFS=$'\n' + urls=($(echo $video | jq -r '.formats[] | select(.format_id == "22" or .format_id == "18").url')) + unset IFS + if [[ ${urls[1]} != '' ]]; then + url=${urls[1]} + else + url=${urls[0]} + fi + echo "
    + + + +

    +

    $title

    +
    +
    + Uploaded by $(echo $video | jq -r '.uploader') on $(date -d "$(echo $video | jq -r '.upload_date')" "+%d %B %Y") +
    +
    + $(echo $video | jq -r '.view_count') views
    + $(echo $video | jq -r '.like_count') likes, $(echo $video | jq -r '.dislike_count') dislikes.
    " + if [[ ${cookies[sh_session]} ]]; then + if [[ $(cat storage/faves | grep $(echo -n ${cookies[username]} | sed -E 's/\r//g') | grep ${get_data[v]}) == '' ]]; then + echo "Add to favourites
    " + else + echo "Remove from favourites
    " + fi + else + echo "Log in to add this video to your favourites!
    " + fi + echo "

    +
    +
    + Description:
    $(echo $video | jq -r '.description' | sed -E 's/$/
    /g')
    +
    "; +else + source templates/head.sh + echo "pls add ?v param i'm still WiP" +fi
lista promowanych przedmiotów
+ + + + + Nazwa + + + + Cena + + + + z dostawą + + + + Popularność + + + + Do końca +