Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- ////////////////////////////////////////////////////////////////////////////////
- // //
- // @title Torque Webserver //
- // @author Truce //
- // @version 3.0 - Era of the WebSocket //
- // //
- // Dynamically writes pages created by the user by parsing raw Torquescript //
- // located between the <?tqs and ?> tags and supports standard web protocol //
- // //
- ////////////////////////////////////////////////////////////////////////////////
- if(!isObject(Webserver))
- {
- new ScriptGroup(WebSockets);
- new TCPObject(Webserver)
- {
- port = 80;
- debug = false;
- localOnly = false;
- blockIPs = "78.69.143.58";
- timeout = 1000;
- accounts = "config/accounts.dat";
- folder = "config/pages";
- index = "/index.tqs";
- tagPrefix = "<?tqs ";
- tagSuffix = " ?>";
- };
- Webserver.listen(Webserver.port);
- WebSockets.server = Webserver.getID();
- Webserver.sockets = WebSockets.getID();
- }
- ////////////////////////////////////////
- // Webserver Methods //
- ////////////////////////////////////////
- function Webserver::debug(%this,%line)
- {
- if(%this.debug)
- echo("[Webserver] " @ %line);
- }
- function Webserver::onConnectRequest(%this,%address,%id)
- {
- %address = getWord(strReplace(%address,":"," "),1);
- %client = new TCPObject(Webclient,%id).getID();
- %client.ip = %address;
- %client.server = %this;
- %this.debug("Connect request from IP " @ %address @ " (" @ %client @ ")");
- if(%this.localOnly && !isLANAddress(%address))
- {
- %this.debug("> Client is not local and will be disconnected.");
- %client.disconnectClean();
- return;
- }
- %blockIPs = %this.blockIPs;
- %count = getWordCount(%blockIPs);
- for(%i = 0; %i < %count; %i++)
- {
- %blockIP = getWord(%blockIPs,%i);
- if(strPos(%address,%blockIP) == 0)
- {
- %this.debug("> Client is on blacklist and will be disconnected.");
- %client.disconnectClean();
- return;
- }
- }
- %timeout = %this.timeout;
- %client.timeout = %client.schedule(%timeout,timeout);
- %this.debug("> Client timeout in " @ %timeout @ " milliseconds.");
- }
- function Webclient::timeout(%this)
- {
- %this.server.debug("Client " @ %this @ " timed out after some time.");
- %this.disconnectClean();
- }
- function Webclient::onLine(%this,%line)
- {
- cancel(%this.timeout);
- %server = %this.server;
- %this.timeout = %this.schedule(%server.timeout,finish);
- %this.packet = %this.packet @ %line @ "\n";
- %length = %this._SERVER["HTTP_CONTENT_LENGTH"];
- if(!%this.lineCount)
- {
- %this.command = getWord(%line,0);
- %this.page = getWord(%line,1);
- %this.version = getWord(%line,2);
- }
- else if(%line $= "" && %length !$= "")
- {
- %this.binarySize = %length;
- %this.setBinary(1);
- }
- else
- {
- %args = strReplace(%line,": ","\t");
- %name = "HTTP_" @ strReplace(getField(%args,0),"-","_");
- %value = getField(%args,1);
- %this._SERVER[%name] = %value;
- }
- %this.lineCount++;
- }
- function Webclient::onBinChunk(%this,%chunk)
- {
- %server = %this.server;
- if(%this.websocket)
- {
- %server.debug(%chunk @ " bytes of WebSocket data received.");
- %this.chunk = %chunk;
- %this.saveBufferToFile("config/temp_websocket");
- %this.setBinarySize(1);
- %this.decodeFrame();
- return;
- }
- if(%chunk >= %this.binarySize)
- %this.saveBufferToFile("config/temp");
- cancel(%this.timeout);
- %this.timeout = %this.schedule(%server.timeout,finish);
- }
- function Webclient::finish(%this)
- {
- %server = %this.server;
- %packet = %this.packet;
- %request = getRecord(%packet,0);
- if(isFile("config/temp"))
- {
- %file = new FileObject();
- %file.openForRead("config/temp");
- while(!%file.isEOF())
- %packet = %packet @ %file.readLine() @ "\n";
- %file.close();
- %file.delete();
- fileDelete("config/temp");
- }
- %server.debug("Packet terminated from client " @ %this @ ".");
- %command = getWord(%request,0);
- %page = getWord(%request,1);
- %version = getWord(%request,2);
- %server.debug("> First line is " @ %command SPC %page SPC %version);
- if(%version !$= "HTTP/1.1")
- {
- %server.debug("> Client using old HTTP and will be disconnected.");
- %this.disconnectClean();
- return;
- }
- deleteVariables("$_GET*");
- deleteVariables("$_POST*");
- deleteVariables("$_SERVER*");
- $_SERVER["REMOTE_ADDR"] = %this.ip;
- if((%pos = strPos(%page,"?")) != -1)
- {
- %args = getSubStr(%page,%pos + 1,strLen(%page));
- %page = getSubStr(%page,0,%pos);
- %server.debug("Parsing client " @ %this @ "'s GET args: " @ %args);
- %args = strReplace(%args,"&","\t");
- %num = getFieldCount(%args);
- if(!%num)
- %server.debug("> No GET args found to parse!");
- for(%i = 0; %i < %num; %i++)
- {
- %arg = getField(%args,%i);
- %arg = strReplace(%arg,"=","\t");
- %name = getField(%arg,0);
- %value = getField(%arg,1);
- $_GET[%name] = %value;
- %server.debug("> Assigning " @ %value @ " to " @ %name @ ".");
- }
- }
- %header = getRecords(%packet,1);
- %count = getRecordCount(%header);
- %server.debug("Parsing client " @ %this @ "'s header: " @ %header);
- for(%i = 0; %i < %count; %i++)
- {
- %record = getRecord(%header,%i);
- if(%record $= "")
- {
- %args = getRecord(%header,%i + 1);
- %server.debug("Parsing client " @ %this @ "'s POST args: " @ %args);
- %args = strReplace(%args,"&","\t");
- %num = getFieldCount(%args);
- if(!%num)
- %server.debug("> No POST args found to parse!");
- for(%i = 0; %i < %num; %i++)
- {
- %arg = getField(%args,%i);
- %arg = strReplace(%arg,"=","\t");
- %name = getField(%arg,0);
- %value = getField(%arg,1);
- $_POST[%name] = %value;
- %server.debug("> Assigning " @ %value @ " to " @ %name @ ".");
- }
- break;
- }
- %record = strReplace(%record,": ","\t");
- %name = "HTTP_" @ strReplace(getField(%record,0),"-","_");
- %value = getField(%record,1);
- $_SERVER[%name] = %value;
- %server.debug("> Assigning " @ %value @ " to " @ %name @ ".");
- }
- if($_SERVER["HTTP_UPGRADE"] $= "websocket")
- {
- %server.debug("Upgrading client connection to websocket.");
- if(%page !$= $WebSocketPassword)
- {
- %server.debug("> Client unauthorized and will be disconnected.");
- %this.disconnectClean();
- return;
- }
- %magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
- %key_in = $_SERVER["HTTP_SEC_WEBSOCKET_KEY"];
- %key_out = convertWebkey(%key_in @ %magic);
- %server.debug("> Client provided the websocket key " @ %key_in);
- %server.debug("> Server returned the websocket key " @ %key_out);
- %this.send("HTTP/1.1 101 Switching Protocols\r\n");
- %this.send("Upgrade: websocket\r\n");
- %this.send("Connection: Upgrade\r\n");
- %this.send("Sec-WebSocket-Accept: " @ %key_out @ "\r\n");
- %this.send("\r\n");
- %this.websocket = true;
- %this.setBinary(1);
- WebSockets.add(%this);
- return;
- }
- if(%page $= "/")
- %page = %server.index;
- %server.debug("Deploying file: " @ %page);
- if(fileExt(%server.folder @ %page) $= ".tqss")
- {
- if(!isFile(%server.accounts))
- {
- %server.debug("> Requested secured page, but no accounts file exists.");
- %this.disconnectClean();
- return;
- }
- if($_SERVER["HTTP_AUTHORIZATION"] $= "")
- {
- %server.debug("> Requested secured page, prompting for authentication.");
- %this.send("HTTP/1.1 401 Authorization Required\r\n");
- %this.send("WWW-Authenticate: Basic realm=\"TQS Webserver\"\r\n");
- %this.send("\r\n");
- %this.disconnectClean();
- return;
- }
- %auth = getWord($_SERVER["HTTP_AUTHORIZATION"],1);
- %auth = base64Decode(%auth);
- %file = new FileObject();
- %file.openForRead(%server.accounts);
- while(!%file.isEOF())
- {
- %line = %file.readLine();
- %pos = strPos(%line,"#");
- if(%pos != -1)
- %line = getSubStr(%line,0,%pos);
- %line = trim(%line);
- if(%line !$= "" && %line $= %auth)
- {
- %authed = true;
- break;
- }
- }
- %file.close();
- %file.delete();
- if(!%authed)
- {
- %server.debug("> Client provided invalid credentials, access denied.");
- %this.send("HTTP/1.1 401 Authorization Required\r\n");
- %this.send("WWW-Authenticate: Basic realm=\"TQS Webserver\"\r\n");
- %this.send("\r\n");
- %this.disconnectClean();
- return;
- }
- %server.debug("> Client provided proper credentials, access granted.");
- }
- if(striPos(%page,"http://") != -1)
- {
- %server.debug("> Client tempting security and will be disconnected.");
- %this.disconnectClean();
- return;
- }
- if(isFile(%server.folder @ %page))
- {
- %server.debug("> File found! Including all its contents.");
- $Webkey = -1;
- %body = input(%page);
- %this.send("HTTP/1.1 200 OK\r\n");
- %this.send("Content-Length: " @ strLen(%body) @ "\r\n");
- %this.send("Content-Type: text/html; charset=UTF-8\r\n");
- %this.send("Connection: close\r\n");
- %this.send("\r\n");
- %this.send(%body @ "\r\n");
- }
- else
- {
- %server.debug("> File not found! Deploying 404 instead.");
- %this.send("HTTP/1.1 404 Not Found\r\n");
- %this.send("Connection: close\r\n");
- %this.send("\r\n");
- }
- %this.disconnectClean();
- }
- function Webclient::disconnectClean(%this)
- {
- %this.disconnect();
- %this.schedule(0,delete);
- %this.server.debug("Client " @ %this @ " has been disconnected.");
- }
- ////////////////////////////////////////
- // Inline Scripting //
- ////////////////////////////////////////
- function parse(%_body)
- {
- %_pre = Webserver.tagPrefix;
- %_suf = Webserver.tagSuffix;
- %_pos = striPos(%_body,%_pre);
- %_rest = getSubStr(%_body,%_pos + strLen(%_pre),strLen(%_body));
- %_body = getSubStr(%_body,0,%_pos);
- %_pos = striPos(%_rest,%_suf);
- %_eval = getSubStr(%_rest,0,%_pos);
- %_rest = getSubStr(%_rest,%_pos + strLen(%_suf),strLen(%_rest));
- $Webcache[$Webkey] = "";
- eval(%_eval);
- return %_body @ $Webcache[$Webkey] @ %_rest;
- }
- function input(%path)
- {
- %path = Webserver.folder @ %path;
- if(!isFile(%path))
- return;
- %file = new FileObject();
- %file.openForRead(%path);
- while(!%file.isEOF())
- %body = %body @ strReplace(%file.readLine(),"\t"," ") @ " \n ";
- %file.close();
- %file.delete();
- %body = getSubStr(%body,0,strLen(%body) - 3);
- %pre = Webserver.tagPrefix;
- $Webkey++;
- while(striPos(%body,%pre) != -1)
- %body = parse(%body);
- $Webkey--;
- return %body;
- }
- function include(%path)
- {
- print(input(%path));
- }
- function print(%str)
- {
- $Webcache[$Webkey] = $Webcache[$Webkey] @ %str;
- }
- function puts(%str)
- {
- print(%str @ "\n");
- }
- ////////////////////////////////////////
- // Basetype Conversion //
- ////////////////////////////////////////
- $Basetype[ 2] = "01";
- $Basetype[ 10] = "0123456789";
- $Basetype[ 16] = "0123456789ABCDEF";
- $Basetype[ 64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
- $Basetype[256] = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F" @
- "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F" @
- "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2A\x2B\x2C\x2D\x2E\x2F" @
- "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3A\x3B\x3C\x3D\x3E\x3F" @
- "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4A\x4B\x4C\x4D\x4E\x4F" @
- "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5A\x5B\x5C\x5D\x5E\x5F" @
- "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6A\x6B\x6C\x6D\x6E\x6F" @
- "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7A\x7B\x7C\x7D\x7E\x7F" @
- "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F" @
- "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F" @
- "\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF" @
- "\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF" @
- "\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF" @
- "\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF" @
- "\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF" @
- "\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF";
- function convertBase(%val,%atype,%btype)
- {
- %vlen = strLen(%val);
- %alen = strLen(%atype);
- %blen = strLen(%btype);
- for(%i = 0; %i < %vlen; %i++)
- %sum += striPos(%atype,getSubStr(%val,%i,1)) * mPow(%alen,%vlen - %i - 1);
- while(1)
- {
- %rem = %sum % %blen;
- %new = getSubStr(%btype,%rem,1) @ %new;
- %sum = mFloor(%sum / %blen);
- if(!%sum)
- break;
- }
- return %new;
- }
- function base64Encode(%str)
- {
- %len = strLen(%str);
- for(%i = 0; %i < %len; %i++)
- {
- %chr = getSubStr(%str,%i,1);
- %ascii = strPos($Basetype[256],%chr) + 32;
- %bin = convertBase(%ascii,$Basetype[10],$Basetype[2]);
- %bin = padBase(%bin,$Basetype[2],8);
- %all = %all @ %bin;
- }
- %len = strLen(%all);
- for(%i = 0; %i < %len; %i += 6)
- {
- %pack = getSubStr(%all,%i,6);
- while(strLen(%pack) < 6)
- %pack = %pack @ "0";
- %dec = convertBase(%pack,$Basetype[2],$Basetype[10]);
- %new = %new @ getSubStr($Basetype[64],%dec,1);
- }
- while(strLen(%new) % 4 > 0)
- %new = %new @ "=";
- return %new;
- }
- function base64Encode_Nulls(%str)
- {
- %len = getWordCount(%str);
- for(%i = 0; %i < %len; %i++)
- {
- %dec = getWord(%str,%i);
- %bin = convertBase(%dec,$Basetype[10],$Basetype[2]);
- %bin = padBase(%bin,$Basetype[2],8);
- %all = %all @ %bin;
- }
- %len = strLen(%all);
- for(%i = 0; %i < %len; %i += 6)
- {
- %pack = getSubStr(%all,%i,6);
- while(strLen(%pack) < 6)
- %pack = %pack @ "0";
- %dec = convertBase(%pack,$Basetype[2],$Basetype[10]);
- %new = %new @ getSubStr($Basetype[64],%dec,1);
- }
- while(strLen(%new) % 4 > 0)
- %new = %new @ "=";
- return %new;
- }
- function base64Decode(%str)
- {
- while(getSubStr(%str,strLen(%str) - 1,1) $= "=")
- %str = getSubStr(%str,0,strLen(%str) - 1);
- %len = strLen(%str);
- for(%i = 0; %i < %len; %i++)
- {
- %chr = getSubStr(%str,%i,1);
- %pos = strPos($Basetype[64],%chr);
- %bin = convertBase(%pos,$Basetype[10],$Basetype[2]);
- %bin = padBase(%bin,$Basetype[2],6);
- %all = %all @ %bin;
- }
- while(strLen(%all) % 8 > 0)
- %all = getSubStr(%all,0,strLen(%all) - 1);
- %len = strLen(%all);
- for(%i = 0; %i < %len; %i += 8)
- {
- %bin = getSubStr(%all,%i,8);
- %dec = convertBase(%bin,$Basetype[2],$Basetype[10]) - 32;
- %chr = getSubStr($Basetype[256],%dec,1);
- %new = %new @ %chr;
- }
- return %new;
- }
- function padBase(%val,%type,%len)
- {
- while(strLen(%val) < %len)
- %val = getSubStr(%type,0,1) @ %val;
- return %val;
- }
- ////////////////////////////////////////
- // WebSocket Methods //
- ////////////////////////////////////////
- function Webclient::encodeFrame(%this,%opcode,%data)
- {
- if(%data $= "")
- %data = "\x01";
- %fin = 1;
- %rsv1 = 0;
- %rsv2 = 0;
- %rsv3 = 0;
- %mask = 0;
- %len = strLen(%data);
- %opcode = padBase(convertBase(%opcode,$Basetype[10],$Basetype[2]),$Basetype[2],4);
- %length = padBase(convertBase(%len, $Basetype[10],$Basetype[2]),$Basetype[2],7);
- %byte[0] = %fin @ %rsv1 @ %rsv2 @ %rsv3 @ %opcode;
- %byte[1] = %mask @ %length;
- %byte[0] = getSubStr($Basetype[256],convertBase(%byte[0],$Basetype[2],$Basetype[10]) - 1,1);
- %byte[1] = getSubStr($Basetype[256],convertBase(%byte[1],$Basetype[2],$Basetype[10]) - 1,1);
- for(%i = 0; %i < %len; %i++)
- %frame = %frame @ getSubStr(%data,%i,1);
- %this.send(%byte[0] @ %byte[1] @ %frame);
- }
- function Webclient::decodeFrame(%this)
- {
- %server = %this.server;
- %server.debug("Decoding the current WebSocket frame.");
- %file = new FileObject();
- %file.openForRead("config/temp_websocket");
- %str = %file.readLine();
- %len = strLen(%str);
- %file.delete();
- fileDelete("config/temp_websocket");
- for(%i = 0; %i < %len; %i++)
- {
- %chr = strPos($Basetype[256],getSubStr(%str,%i,1)) + 1;
- %bits = convertBase(%chr,$Basetype[10],$Basetype[2]);
- %pad = padBase(%bits,$Basetype[2],8);
- %byte[%i] = %pad;
- }
- %fin = getSubStr(%byte[0],0,1);
- %rsv1 = getSubStr(%byte[0],1,1);
- %rsv2 = getSubStr(%byte[0],2,1);
- %rsv3 = getSubStr(%byte[0],3,1);
- %opcode = getSubStr(%byte[0],4,4);
- %ismask = getSubStr(%byte[1],0,1);
- %length = getSubStr(%byte[1],1,7);
- %opcode = convertBase(%opcode,$Basetype[2],$Basetype[10]);
- %length = convertBase(%length,$Basetype[2],$Basetype[10]);
- %pos = 2;
- if(%len != %this.chunk)
- {
- %server.debug("> The data does not have the expected length.");
- switch(%opcode)
- {
- case 1:
- %server.debug("> Requesting a text frame resend.");
- %this.encodeFrame(1,"\x02");
- case 10:
- %server.debug("> Requesting a pong frame resend.");
- %this.encodeFrame(9,%this.ping);
- }
- return;
- }
- if(%ismask)
- {
- for(%i = %pos; %i < %pos + 4; %i++)
- %mask = %mask @ %byte[%i];
- %pos += 4;
- }
- for(%i = %pos; %i < %len; %i++)
- %data = %data @ %byte[%i];
- for(%i = 0; %i < %len - %pos; %i++)
- {
- %data = %byte[%pos + %i];
- if(%ismask)
- {
- %mask = %byte[2 + %i % 4];
- %data = manualXor(%data,%mask);
- }
- %dec = convertBase(%data,$Basetype[2],$Basetype[10]);
- %val = getSubStr($Basetype[256],%dec - 1,1);
- %value = %value @ %val;
- }
- switch(%opcode)
- {
- case 1:
- %server.debug("> The frame is a text frame.");
- %server.debug("> The result was: " @ %value);
- %this.onWebSocketLine(%value);
- case 8:
- %server.debug("> The frame is a connection close.");
- %this.disconnectClean();
- case 9:
- %server.debug("> The frame is a ping frame.");
- %server.debug("> The result was: " @ %value);
- case 10:
- %server.debug("> The frame is a pong frame.");
- %server.debug("> The result was: " @ %value);
- %ping = %this.ping;
- if(%ping $= "")
- return;
- if(%value !$= %ping)
- {
- %server.debug("> Client " @ %this @ " failed ping and will be disconnected.");
- %this.disconnectClean();
- return;
- }
- %this.ping = "";
- cancel(%this.timeout);
- }
- }
- function Webclient::onWebSocketLine(%this,%line)
- {
- %server = %this.server;
- %sockets = %server.sockets;
- %count = %sockets.getCount();
- %line = strReplace(%line,"<","<");
- %line = strReplace(%line,">",">");
- %line = %this.ip SPC %line;
- echo(%line);
- %this.encodeFrame(1,"\x03");
- for(%i = 0; %i < %count; %i++)
- %sockets.getObject(%i).encodeFrame(1,%line);
- %sockets.pingAll();
- }
- function WebSockets::pingAll(%this)
- {
- %time = %this.server.timeout;
- %count = %this.getCount();
- for(%i = 0; %i < %count; %i++)
- {
- %sock = %this.getObject(%i);
- if(%sock.ping !$= "")
- continue;
- %ping = mFloor($Sim::Time);
- %sock.encodeFrame(9,%ping);
- %sock.ping = %ping;
- %sock.timeout = %sock.schedule(%time,pingTimeout);
- }
- }
- function Webclient::pingTimeout(%this)
- {
- %this.server.debug("Client " @ %this @ " failed ping and will be disconnected.");
- %this.disconnectClean();
- }
- ////////////////////////////////////////
- // WebSocket Support //
- ////////////////////////////////////////
- function convertWebkey(%key)
- {
- %sha = sha1(%key);
- %len = strLen(%sha);
- for(%i = 0; %i < %len; %i += 2)
- %str = %str SPC convertBase(getSubStr(%sha,%i,2),$Basetype[16],$Basetype[10]);
- %str = trim(%str);
- %b64 = base64Encode_Nulls(%str);
- return %b64;
- }
- function manualXor(%a,%b)
- {
- %len = strLen(%a);
- for(%i = 0; %i < %len; %i++)
- %xor = %xor @ (getSubStr(%a,%i,1) ^ getSubStr(%b,%i,1));
- return %xor;
- }
- function manualAbsSub(%a)
- {
- %a = getSubStr(%a,1,strLen(%a));
- %len = strLen(%a);
- %dec = 1;
- for(%i = %len - 1; %i >= 0; %i--)
- {
- %int = getSubStr(%a,%i,1);
- if(%dec)
- {
- if(%int-- < 0) %int = 0;
- else %dec = 0;
- }
- %val = %int @ %val;
- }
- return %val;
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement