client/rpc/extractor.rs
1// Copyright 2015-2019 Parity Technologies (UK) Ltd.
2// This file is part of Parity Ethereum.
3
4// Parity Ethereum is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Parity Ethereum is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
16
17//! Parity-specific metadata extractors.
18
19use crate::rpc::{http_common::HttpMetaExtractor, Metadata, Origin};
20use cfx_types::H256;
21use jsonrpc_pubsub::Session;
22use jsonrpc_tcp_server as tcp;
23use jsonrpc_ws_server as ws;
24use std::sync::Arc;
25//use ws;
26
27/// Common HTTP & IPC metadata extractor.
28pub struct RpcExtractor;
29
30impl HttpMetaExtractor for RpcExtractor {
31 type Metadata = Metadata;
32
33 fn read_metadata(
34 &self, origin: Option<String>, user_agent: Option<String>,
35 ) -> Metadata {
36 Metadata {
37 origin: Origin::Rpc(format!(
38 "{} / {}",
39 origin.unwrap_or_else(|| "unknown origin".to_string()),
40 user_agent.unwrap_or_else(|| "unknown agent".to_string())
41 )),
42 session: None,
43 }
44 }
45}
46
47impl tcp::MetaExtractor<Metadata> for RpcExtractor {
48 fn extract(&self, req: &tcp::RequestContext) -> Metadata {
49 Metadata {
50 origin: Origin::Tcp(req.peer_addr),
51 session: Some(Arc::new(Session::new(req.sender.clone()))),
52 }
53 }
54}
55
56impl ws::MetaExtractor<Metadata> for RpcExtractor {
57 fn extract(&self, req: &ws::RequestContext) -> Metadata {
58 Metadata {
59 origin: Origin::Ws {
60 session: H256::from_low_u64_be(req.session_id),
61 },
62 session: Some(Arc::new(Session::new(req.sender()))),
63 }
64 }
65}
66
67///// WebSockets server metadata extractor and request middleware.
68//pub struct WsExtractor {
69// authcodes_path: Option<PathBuf>,
70//}
71//
72//impl WsExtractor {
73// /// Creates new `WsExtractor` with given authcodes path.
74// pub fn new(path: Option<&Path>) -> Self {
75// WsExtractor {
76// authcodes_path: path.map(ToOwned::to_owned),
77// }
78// }
79//}
80//
81//impl ws::MetaExtractor<Metadata> for WsExtractor {
82// fn extract(&self, req: &ws::RequestContext) -> Metadata {
83// let id = req.session_id as u64;
84//
85// let origin = match self.authcodes_path {
86// Some(ref path) => {
87// let authorization = req
88// .protocols
89// .get(0)
90// .and_then(|p| auth_token_hash(&path, p, true));
91// match authorization {
92// Some(id) => Origin::Signer { session: id },
93// None => Origin::Ws {
94// session: H256::from_low_u64_be(id),
95// },
96// }
97// }
98// None => Origin::Ws {
99// session: H256::from_low_u64_be(id),
100// },
101// };
102// let session = Some(Arc::new(Session::new(req.sender())));
103// Metadata { origin, session }
104// }
105//}
106//
107//impl ws::RequestMiddleware for WsExtractor {
108// fn process(&self, req: &ws::ws::Request) -> ws::MiddlewareAction {
109// use self::ws::ws::Response;
110//
111// // Reply with 200 OK to HEAD requests.
112// if req.method() == "HEAD" {
113// let mut response = Response::new(200, "OK", vec![]);
114// add_security_headers(&mut response);
115// return Some(response).into();
116// }
117//
118// // Display WS info.
119// if req.header("sec-websocket-key").is_none() {
120// let mut response = Response::new(200, "OK", b"WebSocket interface
121// is active. Open WS connection to access RPC.".to_vec());
122// add_security_headers(&mut response); return Some(response).into();
123// }
124//
125// // If protocol is provided it needs to be valid.
126// let protocols = req.protocols().ok().unwrap_or_else(Vec::new);
127// if let Some(ref path) = self.authcodes_path {
128// if protocols.len() == 1 {
129// let authorization = auth_token_hash(&path, protocols[0],
130// false); if authorization.is_none() {
131// warn!(
132// "Blocked connection from {} using invalid token.",
133// req.header("origin")
134// .and_then(|e| ::std::str::from_utf8(e).ok())
135// .unwrap_or("Unknown Origin")
136// );
137// let mut response = Response::new(403, "Forbidden",
138// vec![]); add_security_headers(&mut response);
139// return Some(response).into();
140// }
141// }
142// }
143//
144// // Otherwise just proceed.
145// ws::MiddlewareAction::Proceed
146// }
147//}
148//
149//fn add_security_headers(res: &mut ws::ws::Response) {
150// let headers = res.headers_mut();
151// headers.push(("X-Frame-Options".into(), b"SAMEORIGIN".to_vec()));
152// headers.push(("X-XSS-Protection".into(), b"1; mode=block".to_vec()));
153// headers.push(("X-Content-Type-Options".into(), b"nosniff".to_vec()));
154// headers.push(("Content-Security-Policy".into(),
155// b"default-src 'self';form-action
156// 'none';block-all-mixed-content;sandbox allow-scripts;".to_vec() ));
157//}
158//
159//fn auth_token_hash(
160// codes_path: &Path, protocol: &str, save_file: bool,
161//) -> Option<H256> {
162// let mut split = protocol.split('_');
163// let auth = split.next().and_then(|v| v.parse().ok());
164// let time = split.next().and_then(|v| u64::from_str_radix(v, 10).ok());
165//
166// if let (Some(auth), Some(time)) = (auth, time) {
167// // Check if the code is valid
168// return authcodes::AuthCodes::from_file(codes_path)
169// .ok()
170// .and_then(|mut codes| {
171// // remove old tokens
172// codes.clear_garbage();
173//
174// let res = codes.is_valid(&auth, time);
175//
176// if save_file {
177// // make sure to save back authcodes - it might have been
178// modified if codes.to_file(codes_path).is_err() {
179// warn!(target: "signer", "Couldn't save authorization
180// codes to file."); }
181// }
182//
183// if res {
184// Some(auth)
185// } else {
186// None
187// }
188// });
189// }
190//
191// None
192//}
193//
194///// WebSockets RPC usage statistics.
195//pub struct WsStats {
196// stats: Arc<RpcStats>,
197//}
198//
199//impl WsStats {
200// /// Creates new WS usage tracker.
201// pub fn new(stats: Arc<RpcStats>) -> Self { WsStats { stats } }
202//}
203//
204//impl ws::SessionStats for WsStats {
205// fn open_session(&self, _id: ws::SessionId) { self.stats.open_session() }
206//
207// fn close_session(&self, _id: ws::SessionId) { self.stats.close_session() }
208//}
209//
210///// WebSockets middleware dispatching requests to different handles dependning
211///// on metadata.
212//pub struct WsDispatcher<M: core::Middleware<Metadata>> {
213// full_handler: core::MetaIoHandler<Metadata, M>,
214//}
215//
216//impl<M: core::Middleware<Metadata>> WsDispatcher<M> {
217// /// Create new `WsDispatcher` with given full handler.
218// pub fn new(full_handler: core::MetaIoHandler<Metadata, M>) -> Self {
219// WsDispatcher { full_handler }
220// }
221//}
222//
223//impl<M: core::Middleware<Metadata>> core::Middleware<Metadata>
224// for WsDispatcher<M>
225//{
226// type CallFuture = core::middleware::NoopCallFuture;
227// type Future = Either<
228// core::FutureRpcResult<M::Future, M::CallFuture>,
229// core::FutureResponse,
230// >;
231//
232// fn on_request<F, X>(
233// &self, request: core::Request, meta: Metadata, process: F,
234// ) -> Either<Self::Future, X>
235// where
236// F: FnOnce(core::Request, Metadata) -> X,
237// X: core::futures::Future<Item = Option<core::Response>, Error = ()>
238// + Send
239// + 'static,
240// {
241// let use_full = match &meta.origin {
242// Origin::Signer { .. } => true,
243// _ => false,
244// };
245//
246// if use_full {
247// Either::A(Either::A(
248// self.full_handler.handle_rpc_request(request, meta),
249// ))
250// } else {
251// Either::B(process(request, meta))
252// }
253// }
254//}
255
256#[cfg(test)]
257mod tests {
258 use super::{HttpMetaExtractor, Origin, RpcExtractor};
259
260 #[test]
261 fn should_extract_rpc_origin() {
262 // given
263 let extractor = RpcExtractor;
264
265 // when
266 let meta1 = extractor.read_metadata(None, None);
267 let meta2 = extractor
268 .read_metadata(None, Some("https://conflux-chain.org".to_owned()));
269 let meta3 = extractor
270 .read_metadata(None, Some("https://conflux-chain.org".to_owned()));
271
272 // then
273 assert_eq!(
274 meta1.origin,
275 Origin::Rpc("unknown origin / unknown agent".into())
276 );
277 assert_eq!(
278 meta2.origin,
279 Origin::Rpc("unknown origin / https://conflux-chain.org".into())
280 );
281 assert_eq!(
282 meta3.origin,
283 Origin::Rpc("unknown origin / https://conflux-chain.org".into())
284 );
285 }
286}