]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // |
2 | // Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) | |
3 | // | |
4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | |
5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |
6 | // | |
7 | ||
8 | #ifndef BEAST_WEBSOCKET_IMPL_STREAM_IPP | |
9 | #define BEAST_WEBSOCKET_IMPL_STREAM_IPP | |
10 | ||
11 | #include <beast/websocket/teardown.hpp> | |
12 | #include <beast/websocket/detail/hybi13.hpp> | |
13 | #include <beast/websocket/detail/pmd_extension.hpp> | |
14 | #include <beast/http/read.hpp> | |
15 | #include <beast/http/write.hpp> | |
16 | #include <beast/http/reason.hpp> | |
17 | #include <beast/http/rfc7230.hpp> | |
18 | #include <beast/core/buffer_cat.hpp> | |
19 | #include <beast/core/buffer_concepts.hpp> | |
20 | #include <beast/core/consuming_buffers.hpp> | |
21 | #include <beast/core/prepare_buffers.hpp> | |
22 | #include <beast/core/static_streambuf.hpp> | |
23 | #include <beast/core/stream_concepts.hpp> | |
24 | #include <beast/core/detail/type_traits.hpp> | |
25 | #include <boost/assert.hpp> | |
26 | #include <boost/endian/buffers.hpp> | |
27 | #include <algorithm> | |
28 | #include <memory> | |
29 | #include <stdexcept> | |
30 | #include <utility> | |
31 | ||
32 | namespace beast { | |
33 | namespace websocket { | |
34 | ||
35 | template<class NextLayer> | |
36 | template<class... Args> | |
37 | stream<NextLayer>:: | |
38 | stream(Args&&... args) | |
39 | : stream_(std::forward<Args>(args)...) | |
40 | { | |
41 | } | |
42 | ||
43 | template<class NextLayer> | |
44 | void | |
45 | stream<NextLayer>:: | |
46 | set_option(permessage_deflate const& o) | |
47 | { | |
48 | if( o.server_max_window_bits > 15 || | |
49 | o.server_max_window_bits < 9) | |
50 | throw std::invalid_argument{ | |
51 | "invalid server_max_window_bits"}; | |
52 | if( o.client_max_window_bits > 15 || | |
53 | o.client_max_window_bits < 9) | |
54 | throw std::invalid_argument{ | |
55 | "invalid client_max_window_bits"}; | |
56 | if( o.compLevel < 0 || | |
57 | o.compLevel > 9) | |
58 | throw std::invalid_argument{ | |
59 | "invalid compLevel"}; | |
60 | if( o.memLevel < 1 || | |
61 | o.memLevel > 9) | |
62 | throw std::invalid_argument{ | |
63 | "invalid memLevel"}; | |
64 | pmd_opts_ = o; | |
65 | } | |
66 | ||
67 | //------------------------------------------------------------------------------ | |
68 | ||
69 | template<class NextLayer> | |
70 | void | |
71 | stream<NextLayer>:: | |
72 | reset() | |
73 | { | |
74 | failed_ = false; | |
75 | rd_.cont = false; | |
76 | wr_close_ = false; | |
77 | wr_.cont = false; | |
78 | wr_block_ = nullptr; // should be nullptr on close anyway | |
79 | ping_data_ = nullptr; // should be nullptr on close anyway | |
80 | ||
81 | stream_.buffer().consume( | |
82 | stream_.buffer().size()); | |
83 | } | |
84 | ||
85 | template<class NextLayer> | |
86 | http::request_header | |
87 | stream<NextLayer>:: | |
88 | build_request(boost::string_ref const& host, | |
89 | boost::string_ref const& resource, std::string& key) | |
90 | { | |
91 | http::request_header req; | |
92 | req.url = { resource.data(), resource.size() }; | |
93 | req.version = 11; | |
94 | req.method = "GET"; | |
95 | req.fields.insert("Host", host); | |
96 | req.fields.insert("Upgrade", "websocket"); | |
97 | req.fields.insert("Connection", "upgrade"); | |
98 | key = detail::make_sec_ws_key(maskgen_); | |
99 | req.fields.insert("Sec-WebSocket-Key", key); | |
100 | req.fields.insert("Sec-WebSocket-Version", "13"); | |
101 | if(pmd_opts_.client_enable) | |
102 | { | |
103 | detail::pmd_offer config; | |
104 | config.accept = true; | |
105 | config.server_max_window_bits = | |
106 | pmd_opts_.server_max_window_bits; | |
107 | config.client_max_window_bits = | |
108 | pmd_opts_.client_max_window_bits; | |
109 | config.server_no_context_takeover = | |
110 | pmd_opts_.server_no_context_takeover; | |
111 | config.client_no_context_takeover = | |
112 | pmd_opts_.client_no_context_takeover; | |
113 | detail::pmd_write( | |
114 | req.fields, config); | |
115 | } | |
116 | d_(req); | |
117 | return req; | |
118 | } | |
119 | ||
120 | template<class NextLayer> | |
121 | http::response_header | |
122 | stream<NextLayer>:: | |
123 | build_response(http::request_header const& req) | |
124 | { | |
125 | auto err = | |
126 | [&](std::string const& text) | |
127 | { | |
128 | http::response<http::string_body> res; | |
129 | res.status = 400; | |
130 | res.reason = http::reason_string(res.status); | |
131 | res.version = req.version; | |
132 | res.body = text; | |
133 | d_(res); | |
134 | prepare(res, | |
135 | (is_keep_alive(req) && keep_alive_) ? | |
136 | http::connection::keep_alive : | |
137 | http::connection::close); | |
138 | return res; | |
139 | }; | |
140 | if(req.version < 11) | |
141 | return err("HTTP version 1.1 required"); | |
142 | if(req.method != "GET") | |
143 | return err("Wrong method"); | |
144 | if(! is_upgrade(req)) | |
145 | return err("Expected Upgrade request"); | |
146 | if(! req.fields.exists("Host")) | |
147 | return err("Missing Host"); | |
148 | if(! req.fields.exists("Sec-WebSocket-Key")) | |
149 | return err("Missing Sec-WebSocket-Key"); | |
150 | if(! http::token_list{req.fields["Upgrade"]}.exists("websocket")) | |
151 | return err("Missing websocket Upgrade token"); | |
152 | { | |
153 | auto const version = | |
154 | req.fields["Sec-WebSocket-Version"]; | |
155 | if(version.empty()) | |
156 | return err("Missing Sec-WebSocket-Version"); | |
157 | if(version != "13") | |
158 | { | |
159 | http::response<http::string_body> res; | |
160 | res.status = 426; | |
161 | res.reason = http::reason_string(res.status); | |
162 | res.version = req.version; | |
163 | res.fields.insert("Sec-WebSocket-Version", "13"); | |
164 | d_(res); | |
165 | prepare(res, | |
166 | (is_keep_alive(req) && keep_alive_) ? | |
167 | http::connection::keep_alive : | |
168 | http::connection::close); | |
169 | return res; | |
170 | } | |
171 | } | |
172 | http::response_header res; | |
173 | { | |
174 | detail::pmd_offer offer; | |
175 | detail::pmd_offer unused; | |
176 | pmd_read(offer, req.fields); | |
177 | pmd_negotiate( | |
178 | res.fields, unused, offer, pmd_opts_); | |
179 | } | |
180 | res.status = 101; | |
181 | res.reason = http::reason_string(res.status); | |
182 | res.version = req.version; | |
183 | res.fields.insert("Upgrade", "websocket"); | |
184 | res.fields.insert("Connection", "upgrade"); | |
185 | { | |
186 | auto const key = | |
187 | req.fields["Sec-WebSocket-Key"]; | |
188 | res.fields.insert("Sec-WebSocket-Accept", | |
189 | detail::make_sec_ws_accept(key)); | |
190 | } | |
191 | res.fields.replace("Server", "Beast.WebSocket"); | |
192 | d_(res); | |
193 | return res; | |
194 | } | |
195 | ||
196 | template<class NextLayer> | |
197 | void | |
198 | stream<NextLayer>:: | |
199 | do_response(http::response_header const& res, | |
200 | boost::string_ref const& key, error_code& ec) | |
201 | { | |
202 | // VFALCO Review these error codes | |
203 | auto fail = [&]{ ec = error::response_failed; }; | |
204 | if(res.version < 11) | |
205 | return fail(); | |
206 | if(res.status != 101) | |
207 | return fail(); | |
208 | if(! is_upgrade(res)) | |
209 | return fail(); | |
210 | if(! http::token_list{res.fields["Upgrade"]}.exists("websocket")) | |
211 | return fail(); | |
212 | if(! res.fields.exists("Sec-WebSocket-Accept")) | |
213 | return fail(); | |
214 | if(res.fields["Sec-WebSocket-Accept"] != | |
215 | detail::make_sec_ws_accept(key)) | |
216 | return fail(); | |
217 | detail::pmd_offer offer; | |
218 | pmd_read(offer, res.fields); | |
219 | // VFALCO see if offer satisfies pmd_config_, | |
220 | // return an error if not. | |
221 | pmd_config_ = offer; // overwrite for now | |
222 | open(detail::role_type::client); | |
223 | } | |
224 | ||
225 | } // websocket | |
226 | } // beast | |
227 | ||
228 | #endif |