Annotation of parser3/src/classes/websocket.C, revision 1.1
1.1 ! moko 1: /** @file
! 2: Parser: @b websocket parser class.
! 3:
! 4: Copyright (c) 2001-2026 Art. Lebedev Studio (https://www.artlebedev.com)
! 5: Authors: Konstantin Morshnev <moko@design.ru>
! 6: */
! 7:
! 8: #include "pa_vmethod_frame.h"
! 9: #include "pa_request.h"
! 10: #include "pa_vhash.h"
! 11: #include "pa_vint.h"
! 12: #include "pa_vvoid.h"
! 13: #include "pa_vfile.h"
! 14: #include "pa_vresponse.h"
! 15: #include "pa_charsets.h"
! 16: #include "pa_websocket.h"
! 17:
! 18: volatile const char * IDENT_WEBSOCKET_C="$Id$" IDENT_PA_WEBSOCKET_H;
! 19:
! 20: // class
! 21:
! 22: class MWebsocket: public Methoded {
! 23: public:
! 24: MWebsocket();
! 25: };
! 26:
! 27: // global variable
! 28:
! 29: DECLARE_CLASS_VAR(websocket, new MWebsocket);
! 30:
! 31: // defines
! 32:
! 33: const size_t MAX_MESSAGE_SIZE_DEFAULT=10*0x400*0x400;
! 34: #define MAX_CONTROL_SIZE 125
! 35: #define MAX_REASON_SIZE (MAX_CONTROL_SIZE-2)
! 36:
! 37: static const String::Body status_name_upper("STATUS");
! 38: static const String::Body upgrade_name_upper("UPGRADE");
! 39: static const String::Body connection_name_upper("CONNECTION");
! 40: static const String::Body accept_name_upper("SEC-WEBSOCKET-ACCEPT");
! 41:
! 42: // methods
! 43:
! 44: static WebSocket& get_websocket(Request& r) {
! 45: if(!r.websocket)
! 46: throw Exception("websocket", 0, "websocket is available in httpd mode only");
! 47: return *r.websocket;
! 48: }
! 49:
! 50: static WebSocket& get_accepted_websocket(Request& r) {
! 51: WebSocket& result=get_websocket(r);
! 52: if(!result.accepted)
! 53: throw Exception("websocket", 0, "connection is not accepted, use ^websocket:accept[] first");
! 54: return result;
! 55: }
! 56:
! 57: // text goes in UTF-8 and the string is in the source charset, tainted data is untainted as in the response
! 58: static String::C to_utf8(Request& r, const String& string) {
! 59: String::Body body=string.cstr_to_string_body_untaint(r.response.lang, r.connection(false), &r.charsets);
! 60: return Charset::transcode(String::C(body.cstr(), body.length()), r.charsets.source(), pa_UTF8_charset);
! 61: }
! 62:
! 63: static void _accept(Request& r, MethodParams&) {
! 64: WebSocket& websocket=get_websocket(r);
! 65: if(websocket.accepted)
! 66: throw Exception("websocket", 0, "connection is accepted already");
! 67: if(r.response.started)
! 68: throw Exception("websocket", 0, "unable to accept: headers are already sent");
! 69:
! 70: const char* accept_key=pa_websocket_accept_key(
! 71: r.request_info.method,
! 72: SAPI::Env::get(r.sapi_info, "HTTP_UPGRADE"),
! 73: SAPI::Env::get(r.sapi_info, "HTTP_CONNECTION"),
! 74: SAPI::Env::get(r.sapi_info, "HTTP_SEC_WEBSOCKET_KEY"),
! 75: SAPI::Env::get(r.sapi_info, "HTTP_SEC_WEBSOCKET_VERSION"));
! 76:
! 77: // $response:fields, such as sec-websocket-protocol, are sent too
! 78: HashStringValue& fields=r.response.fields();
! 79: fields.put(status_name_upper, new VInt(101));
! 80: fields.put(upgrade_name_upper, new VString(*new String("websocket")));
! 81: fields.put(connection_name_upper, new VString(*new String("Upgrade")));
! 82: fields.put(accept_name_upper, new VString(*new String(accept_key)));
! 83: r.start(VResponse::WEBSOCKET);
! 84:
! 85: websocket.start(MAX_MESSAGE_SIZE_DEFAULT);
! 86: }
! 87:
! 88: static void _receive(Request& r, MethodParams& params) {
! 89: WebSocket& websocket=get_accepted_websocket(r);
! 90:
! 91: double timeout=params.as_double(0, "timeout must be expression", r);
! 92: if(timeout<0)
! 93: throw Exception(PARSER_RUNTIME, 0, "timeout must not be negative");
! 94:
! 95: WebSocket::Message message;
! 96: if(!websocket.receive(timeout*1000 < INT_MAX ? (int)(timeout*1000) : INT_MAX, message)) {
! 97: r.write(*VVoid::get());
! 98: return;
! 99: }
! 100:
! 101: // the data is copied as the buffer is reused
! 102: const char* data=pa_strdup(message.data, message.size);
! 103:
! 104: VHash& result=*new VHash;
! 105: HashStringValue& fields=result.hash();
! 106:
! 107: if(message.opcode==WebSocket::BINARY) {
! 108: fields.put("type", new VString(*new String("binary")));
! 109: VFile& file=*new VFile;
! 110: file.set_binary(true/*tainted*/, data, message.size);
! 111: fields.put("data", &file);
! 112: } else {
! 113: String::C text=Charset::transcode(String::C(data, message.size), pa_UTF8_charset, r.charsets.source());
! 114: fields.put("type", new VString(*new String(message.opcode==WebSocket::TEXT ? "text" : "close")));
! 115: fields.put("data", new VString(*new String(text, String::L_TAINTED)));
! 116: if(message.opcode==WebSocket::CLOSE)
! 117: fields.put("code", new VInt(message.code));
! 118: }
! 119:
! 120: r.write(result);
! 121: }
! 122:
! 123: static void _send(Request& r, MethodParams& params) {
! 124: WebSocket& websocket=get_accepted_websocket(r);
! 125:
! 126: if(VFile* file=dynamic_cast<VFile*>(¶ms[0])) {
! 127: websocket.send(WebSocket::BINARY, file->value_ptr(), file->value_size());
! 128: } else {
! 129: String::C text=to_utf8(r, params.as_string(0, "message must be string or file"));
! 130: websocket.send(WebSocket::TEXT, text.str, text.length);
! 131: }
! 132: }
! 133:
! 134: static void _ping(Request& r, MethodParams& params) {
! 135: WebSocket& websocket=get_accepted_websocket(r);
! 136:
! 137: String::C data;
! 138: if(params.count())
! 139: data=to_utf8(r, params.as_string(0, "data must be string"));
! 140: if(data.length>MAX_CONTROL_SIZE)
! 141: throw Exception(PARSER_RUNTIME, 0, "data is too long, it must be up to %d bytes", MAX_CONTROL_SIZE);
! 142: websocket.send(WebSocket::PING, data.str, data.length);
! 143: }
! 144:
! 145: static void _close(Request& r, MethodParams& params) {
! 146: WebSocket& websocket=get_accepted_websocket(r);
! 147:
! 148: int code=params.count()>0 ? params.as_int(0, "code must be int", r) : WebSocket::NORMAL;
! 149: if(!WebSocket::valid_status(code))
! 150: throw Exception(PARSER_RUNTIME, 0, "code(%d) is not valid", code);
! 151:
! 152: String::C reason;
! 153: if(params.count()>1)
! 154: reason=to_utf8(r, params.as_string(1, "reason must be string"));
! 155: if(reason.length>MAX_REASON_SIZE)
! 156: throw Exception(PARSER_RUNTIME, 0, "reason is too long, it must be up to %d bytes", MAX_REASON_SIZE);
! 157:
! 158: websocket.close(code, reason.str ? reason.str : "", reason.length);
! 159: }
! 160:
! 161: // constructor
! 162:
! 163: MWebsocket::MWebsocket(): Methoded("websocket") {
! 164: // ^accept[]
! 165: add_native_method("accept", Method::CT_STATIC, _accept, 0, 0);
! 166:
! 167: // ^receive(timeout in seconds), returns message hash
! 168: add_native_method("receive", Method::CT_STATIC, _receive, 1, 1);
! 169:
! 170: // ^send[string or file]
! 171: add_native_method("send", Method::CT_STATIC, _send, 1, 1);
! 172:
! 173: // ^ping[] or ^ping[data]
! 174: add_native_method("ping", Method::CT_STATIC, _ping, 0, 1);
! 175:
! 176: // ^close[] or ^close(code) or ^close(code;reason)
! 177: add_native_method("close", Method::CT_STATIC, _close, 0, 2);
! 178: }
E-mail: