summaryrefslogtreecommitdiffstats
path: root/websocketpp/processors/hybi00.hpp
blob: 95ad9dfa846b3bd636af74346dafa876d7d2612d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
/*
 * Copyright (c) 2014, Peter Thorson. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the WebSocket++ Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#ifndef WEBSOCKETPP_PROCESSOR_HYBI00_HPP
#define WEBSOCKETPP_PROCESSOR_HYBI00_HPP

#include <websocketpp/frame.hpp>
#include <websocketpp/http/constants.hpp>

#include <websocketpp/utf8_validator.hpp>
#include <websocketpp/common/network.hpp>
#include <websocketpp/common/md5.hpp>
#include <websocketpp/common/platforms.hpp>

#include <websocketpp/processors/processor.hpp>

#include <algorithm>
#include <cstdlib>
#include <string>
#include <vector>

namespace websocketpp {
namespace processor {

/// Processor for Hybi Draft version 00
/**
 * There are many differences between Hybi 00 and Hybi 13
 */
template <typename config>
class hybi00 : public processor<config> {
public:
    typedef processor<config> base;

    typedef typename config::request_type request_type;
    typedef typename config::response_type response_type;

    typedef typename config::message_type message_type;
    typedef typename message_type::ptr message_ptr;

    typedef typename config::con_msg_manager_type::ptr msg_manager_ptr;

    explicit hybi00(bool secure, bool p_is_server, msg_manager_ptr manager)
      : processor<config>(secure, p_is_server)
      , msg_hdr(0x00)
      , msg_ftr(0xff)
      , m_state(HEADER)
      , m_msg_manager(manager) {}

    int get_version() const {
        return 0;
    }

    lib::error_code validate_handshake(request_type const & r) const {
        if (r.get_method() != "GET") {
            return make_error_code(error::invalid_http_method);
        }

        if (r.get_version() != "HTTP/1.1") {
            return make_error_code(error::invalid_http_version);
        }

        // required headers
        // Host is required by HTTP/1.1
        // Connection is required by is_websocket_handshake
        // Upgrade is required by is_websocket_handshake
        if (r.get_header("Sec-WebSocket-Key1").empty() ||
            r.get_header("Sec-WebSocket-Key2").empty() ||
            r.get_header("Sec-WebSocket-Key3").empty())
        {
            return make_error_code(error::missing_required_header);
        }

        return lib::error_code();
    }

    lib::error_code process_handshake(request_type const & req,
        std::string const & subprotocol, response_type & res) const
    {
        char key_final[16];

        // copy key1 into final key
        decode_client_key(req.get_header("Sec-WebSocket-Key1"), &key_final[0]);

        // copy key2 into final key
        decode_client_key(req.get_header("Sec-WebSocket-Key2"), &key_final[4]);

        // copy key3 into final key
        // key3 should be exactly 8 bytes. If it is more it will be truncated
        // if it is less the final key will almost certainly be wrong.
        // TODO: decide if it is best to silently fail here or produce some sort
        //       of warning or exception.
        std::string const & key3 = req.get_header("Sec-WebSocket-Key3");
        std::copy(key3.c_str(),
                  key3.c_str()+(std::min)(static_cast<size_t>(8), key3.size()),
                  &key_final[8]);

        res.append_header(
            "Sec-WebSocket-Key3",
            md5::md5_hash_string(std::string(key_final,16))
        );

        res.append_header("Upgrade","WebSocket");
        res.append_header("Connection","Upgrade");

        // Echo back client's origin unless our local application set a
        // more restrictive one.
        if (res.get_header("Sec-WebSocket-Origin").empty()) {
            res.append_header("Sec-WebSocket-Origin",req.get_header("Origin"));
        }

        // Echo back the client's request host unless our local application
        // set a different one.
        if (res.get_header("Sec-WebSocket-Location").empty()) {
            uri_ptr uri = get_uri(req);
            res.append_header("Sec-WebSocket-Location",uri->str());
        }

        if (!subprotocol.empty()) {
            res.replace_header("Sec-WebSocket-Protocol",subprotocol);
        }

        return lib::error_code();
    }

    /// Fill in a set of request headers for a client connection request
    /**
     * The Hybi 00 processor only implements incoming connections so this will
     * always return an error.
     *
     * @param [out] req  Set of headers to fill in
     * @param [in] uri The uri being connected to
     * @param [in] subprotocols The list of subprotocols to request
     */
    lib::error_code client_handshake_request(request_type &, uri_ptr,
        std::vector<std::string> const &) const
    {
        return error::make_error_code(error::no_protocol_support);
    }

    /// Validate the server's response to an outgoing handshake request
    /**
     * The Hybi 00 processor only implements incoming connections so this will
     * always return an error.
     *
     * @param req The original request sent
     * @param res The reponse to generate
     * @return An error code, 0 on success, non-zero for other errors
     */
    lib::error_code validate_server_handshake_response(request_type const &,
        response_type &) const
    {
        return error::make_error_code(error::no_protocol_support);
    }

    std::string get_raw(response_type const & res) const {
        response_type temp = res;
        temp.remove_header("Sec-WebSocket-Key3");
        return temp.raw() + res.get_header("Sec-WebSocket-Key3");
    }

    std::string const & get_origin(request_type const & r) const {
        return r.get_header("Origin");
    }

    /// Extracts requested subprotocols from a handshake request
    /**
     * hybi00 does support subprotocols
     * https://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00#section-1.9
     *
     * @param [in] req The request to extract from
     * @param [out] subprotocol_list A reference to a vector of strings to store
     * the results in.
     */
    lib::error_code extract_subprotocols(request_type const & req,
        std::vector<std::string> & subprotocol_list)
    {
        if (!req.get_header("Sec-WebSocket-Protocol").empty()) {
            http::parameter_list p;

             if (!req.get_header_as_plist("Sec-WebSocket-Protocol",p)) {
                 http::parameter_list::const_iterator it;

                 for (it = p.begin(); it != p.end(); ++it) {
                     subprotocol_list.push_back(it->first);
                 }
             } else {
                 return error::make_error_code(error::subprotocol_parse_error);
             }
        }
        return lib::error_code();
    }

    uri_ptr get_uri(request_type const & request) const {
        std::string h = request.get_header("Host");

        size_t last_colon = h.rfind(":");
        size_t last_sbrace = h.rfind("]");

        // no : = hostname with no port
        // last : before ] = ipv6 literal with no port
        // : with no ] = hostname with port
        // : after ] = ipv6 literal with port

        if (last_colon == std::string::npos ||
            (last_sbrace != std::string::npos && last_sbrace > last_colon))
        {
            return lib::make_shared<uri>(base::m_secure, h, request.get_uri());
        } else {
            return lib::make_shared<uri>(base::m_secure,
                                   h.substr(0,last_colon),
                                   h.substr(last_colon+1),
                                   request.get_uri());
        }

        // TODO: check if get_uri is a full uri
    }

    /// Get hybi00 handshake key3
    /**
     * @todo This doesn't appear to be used anymore. It might be able to be
     * removed
     */
    std::string get_key3() const {
        return "";
    }

    /// Process new websocket connection bytes
    size_t consume(uint8_t * buf, size_t len, lib::error_code & ec) {
        // if in state header we are expecting a 0x00 byte, if we don't get one
        // it is a fatal error
        size_t p = 0; // bytes processed
        size_t l = 0;

        ec = lib::error_code();

        while (p < len) {
            if (m_state == HEADER) {
                if (buf[p] == msg_hdr) {
                    p++;
                    m_msg_ptr = m_msg_manager->get_message(frame::opcode::text,1);

                    if (!m_msg_ptr) {
                        ec = make_error_code(websocketpp::error::no_incoming_buffers);
                        m_state = FATAL_ERROR;
                    } else {
                        m_state = PAYLOAD;
                    }
                } else {
                    ec = make_error_code(error::protocol_violation);
                    m_state = FATAL_ERROR;
                }
            } else if (m_state == PAYLOAD) {
                uint8_t *it = std::find(buf+p,buf+len,msg_ftr);

                // 0    1    2    3    4    5
                // 0x00 0x23 0x23 0x23 0xff 0xXX

                // Copy payload bytes into message
                l = static_cast<size_t>(it-(buf+p));
                m_msg_ptr->append_payload(buf+p,l);
                p += l;

                if (it != buf+len) {
                    // message is done, copy it and the trailing
                    p++;
                    // TODO: validation
                    m_state = READY;
                }
            } else {
                // TODO
                break;
            }
        }
        // If we get one, we create a new message and move to application state

        // if in state application we are copying bytes into the output message
        // and validating them for UTF8 until we hit a 0xff byte. Once we hit
        // 0x00, the message is complete and is dispatched. Then we go back to
        // header state.

        //ec = make_error_code(error::not_implemented);
        return p;
    }

    bool ready() const {
        return (m_state == READY);
    }

    bool get_error() const {
        return false;
    }

    message_ptr get_message() {
        message_ptr ret = m_msg_ptr;
        m_msg_ptr = message_ptr();
        m_state = HEADER;
        return ret;
    }

    /// Prepare a message for writing
    /**
     * Performs validation, masking, compression, etc. will return an error if
     * there was an error, otherwise msg will be ready to be written
     */
    virtual lib::error_code prepare_data_frame(message_ptr in, message_ptr out)
    {
        if (!in || !out) {
            return make_error_code(error::invalid_arguments);
        }

        // TODO: check if the message is prepared already

        // validate opcode
        if (in->get_opcode() != frame::opcode::text) {
            return make_error_code(error::invalid_opcode);
        }

        std::string& i = in->get_raw_payload();
        //std::string& o = out->get_raw_payload();

        // validate payload utf8
        if (!utf8_validator::validate(i)) {
            return make_error_code(error::invalid_payload);
        }

        // generate header
        out->set_header(std::string(reinterpret_cast<char const *>(&msg_hdr),1));

        // process payload
        out->set_payload(i);
        out->append_payload(std::string(reinterpret_cast<char const *>(&msg_ftr),1));

        // hybi00 doesn't support compression
        // hybi00 doesn't have masking

        out->set_prepared(true);

        return lib::error_code();
    }

    /// Prepare a ping frame
    /**
     * Hybi 00 doesn't support pings so this will always return an error
     *
     * @param in The string to use for the ping payload
     * @param out The message buffer to prepare the ping in.
     * @return Status code, zero on success, non-zero on failure
     */
    lib::error_code prepare_ping(std::string const &, message_ptr) const
    {
        return lib::error_code(error::no_protocol_support);
    }

    /// Prepare a pong frame
    /**
     * Hybi 00 doesn't support pongs so this will always return an error
     *
     * @param in The string to use for the pong payload
     * @param out The message buffer to prepare the pong in.
     * @return Status code, zero on success, non-zero on failure
     */
    lib::error_code prepare_pong(std::string const &, message_ptr) const
    {
        return lib::error_code(error::no_protocol_support);
    }

    /// Prepare a close frame
    /**
     * Hybi 00 doesn't support the close code or reason so these parameters are
     * ignored.
     *
     * @param code The close code to send
     * @param reason The reason string to send
     * @param out The message buffer to prepare the fame in
     * @return Status code, zero on success, non-zero on failure
     */
    lib::error_code prepare_close(close::status::value, std::string const &, 
        message_ptr out) const
    {
        if (!out) {
            return lib::error_code(error::invalid_arguments);
        }

        std::string val;
        val.append(1,'\xff');
        val.append(1,'\x00');
        out->set_payload(val);
        out->set_prepared(true);

        return lib::error_code();
    }
private:
    void decode_client_key(std::string const & key, char * result) const {
        unsigned int spaces = 0;
        std::string digits;
        uint32_t num;

        // key2
        for (size_t i = 0; i < key.size(); i++) {
            if (key[i] == ' ') {
                spaces++;
            } else if (key[i] >= '0' && key[i] <= '9') {
                digits += key[i];
            }
        }

        num = static_cast<uint32_t>(strtoul(digits.c_str(), NULL, 10));
        if (spaces > 0 && num > 0) {
            num = htonl(num/spaces);
            std::copy(reinterpret_cast<char*>(&num),
                      reinterpret_cast<char*>(&num)+4,
                      result);
        } else {
            std::fill(result,result+4,0);
        }
    }

    enum state {
        HEADER = 0,
        PAYLOAD = 1,
        READY = 2,
        FATAL_ERROR = 3
    };

    uint8_t const msg_hdr;
    uint8_t const msg_ftr;

    state m_state;

    msg_manager_ptr m_msg_manager;
    message_ptr m_msg_ptr;
    utf8_validator::validator m_validator;
};

} // namespace processor
} // namespace websocketpp

#endif //WEBSOCKETPP_PROCESSOR_HYBI00_HPP