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*>(&params[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: