1 use regex::{Regex, Captures};
2 use rustc_serialize::json::Json;
3 
4 use hyper::method::Method;
5 use hyper::method::Method::{Get, Post, Delete};
6 
7 use command::{WebDriverCommand, WebDriverMessage, WebDriverExtensionCommand,
8               VoidWebDriverExtensionCommand};
9 use error::{WebDriverResult, WebDriverError, ErrorStatus};
10 
standard_routes<U:WebDriverExtensionRoute>() -> Vec<(Method, &'static str, Route<U>)>11 fn standard_routes<U:WebDriverExtensionRoute>() -> Vec<(Method, &'static str, Route<U>)> {
12     return vec![(Post, "/session", Route::NewSession),
13                 (Delete, "/session/{sessionId}", Route::DeleteSession),
14                 (Post, "/session/{sessionId}/url", Route::Get),
15                 (Get, "/session/{sessionId}/url", Route::GetCurrentUrl),
16                 (Post, "/session/{sessionId}/back", Route::GoBack),
17                 (Post, "/session/{sessionId}/forward", Route::GoForward),
18                 (Post, "/session/{sessionId}/refresh", Route::Refresh),
19                 (Get, "/session/{sessionId}/title", Route::GetTitle),
20                 (Get, "/session/{sessionId}/source", Route::GetPageSource),
21                 (Get, "/session/{sessionId}/window", Route::GetWindowHandle),
22                 (Get, "/session/{sessionId}/window/handles", Route::GetWindowHandles),
23                 (Delete, "/session/{sessionId}/window", Route::CloseWindow),
24                 (Get, "/session/{sessionId}/window/size", Route::GetWindowSize),
25                 (Post, "/session/{sessionId}/window/size", Route::SetWindowSize),
26                 (Get, "/session/{sessionId}/window/position", Route::GetWindowPosition),
27                 (Post, "/session/{sessionId}/window/position", Route::SetWindowPosition),
28                 (Get, "/session/{sessionId}/window/rect", Route::GetWindowRect),
29                 (Post, "/session/{sessionId}/window/rect", Route::SetWindowRect),
30                 (Post, "/session/{sessionId}/window/minimize", Route::MinimizeWindow),
31                 (Post, "/session/{sessionId}/window/maximize", Route::MaximizeWindow),
32                 (Post, "/session/{sessionId}/window/fullscreen", Route::FullscreenWindow),
33                 (Post, "/session/{sessionId}/window", Route::SwitchToWindow),
34                 (Post, "/session/{sessionId}/frame", Route::SwitchToFrame),
35                 (Post, "/session/{sessionId}/frame/parent", Route::SwitchToParentFrame),
36                 (Post, "/session/{sessionId}/element", Route::FindElement),
37                 (Post, "/session/{sessionId}/elements", Route::FindElements),
38                 (Post, "/session/{sessionId}/element/{elementId}/element", Route::FindElementElement),
39                 (Post, "/session/{sessionId}/element/{elementId}/elements", Route::FindElementElements),
40                 (Get, "/session/{sessionId}/element/active", Route::GetActiveElement),
41                 (Get, "/session/{sessionId}/element/{elementId}/displayed", Route::IsDisplayed),
42                 (Get, "/session/{sessionId}/element/{elementId}/selected", Route::IsSelected),
43                 (Get, "/session/{sessionId}/element/{elementId}/attribute/{name}", Route::GetElementAttribute),
44                 (Get, "/session/{sessionId}/element/{elementId}/property/{name}", Route::GetElementProperty),
45                 (Get, "/session/{sessionId}/element/{elementId}/css/{propertyName}", Route::GetCSSValue),
46                 (Get, "/session/{sessionId}/element/{elementId}/text", Route::GetElementText),
47                 (Get, "/session/{sessionId}/element/{elementId}/name", Route::GetElementTagName),
48                 (Get, "/session/{sessionId}/element/{elementId}/rect", Route::GetElementRect),
49                 (Get, "/session/{sessionId}/element/{elementId}/enabled", Route::IsEnabled),
50                 (Post, "/session/{sessionId}/execute/sync", Route::ExecuteScript),
51                 (Post, "/session/{sessionId}/execute/async", Route::ExecuteAsyncScript),
52                 (Get, "/session/{sessionId}/cookie", Route::GetCookies),
53                 (Get, "/session/{sessionId}/cookie/{name}", Route::GetNamedCookie),
54                 (Post, "/session/{sessionId}/cookie", Route::AddCookie),
55                 (Delete, "/session/{sessionId}/cookie", Route::DeleteCookies),
56                 (Delete, "/session/{sessionId}/cookie/{name}", Route::DeleteCookie),
57                 (Get, "/session/{sessionId}/timeouts", Route::GetTimeouts),
58                 (Post, "/session/{sessionId}/timeouts", Route::SetTimeouts),
59                 (Post, "/session/{sessionId}/element/{elementId}/click", Route::ElementClick),
60                 (Post, "/session/{sessionId}/element/{elementId}/tap", Route::ElementTap),
61                 (Post, "/session/{sessionId}/element/{elementId}/clear", Route::ElementClear),
62                 (Post, "/session/{sessionId}/element/{elementId}/value", Route::ElementSendKeys),
63                 (Post, "/session/{sessionId}/alert/dismiss", Route::DismissAlert),
64                 (Post, "/session/{sessionId}/alert/accept", Route::AcceptAlert),
65                 (Get, "/session/{sessionId}/alert/text", Route::GetAlertText),
66                 (Post, "/session/{sessionId}/alert/text", Route::SendAlertText),
67                 (Get, "/session/{sessionId}/screenshot", Route::TakeScreenshot),
68                 (Get, "/session/{sessionId}/element/{elementId}/screenshot", Route::TakeElementScreenshot),
69                 (Post, "/session/{sessionId}/actions", Route::PerformActions),
70                 (Delete, "/session/{sessionId}/actions", Route::ReleaseActions),
71                 (Get, "/status", Route::Status),]
72 }
73 
74 #[derive(Clone, Copy, Debug)]
75 pub enum Route<U:WebDriverExtensionRoute> {
76     NewSession,
77     DeleteSession,
78     Get,
79     GetCurrentUrl,
80     GoBack,
81     GoForward,
82     Refresh,
83     GetTitle,
84     GetPageSource,
85     GetWindowHandle,
86     GetWindowHandles,
87     CloseWindow,
88     GetWindowSize,  // deprecated
89     SetWindowSize,  // deprecated
90     GetWindowPosition,  // deprecated
91     SetWindowPosition,  // deprecated
92     GetWindowRect,
93     SetWindowRect,
94     MinimizeWindow,
95     MaximizeWindow,
96     FullscreenWindow,
97     SwitchToWindow,
98     SwitchToFrame,
99     SwitchToParentFrame,
100     FindElement,
101     FindElements,
102     FindElementElement,
103     FindElementElements,
104     GetActiveElement,
105     IsDisplayed,
106     IsSelected,
107     GetElementAttribute,
108     GetElementProperty,
109     GetCSSValue,
110     GetElementText,
111     GetElementTagName,
112     GetElementRect,
113     IsEnabled,
114     ExecuteScript,
115     ExecuteAsyncScript,
116     GetCookies,
117     GetNamedCookie,
118     AddCookie,
119     DeleteCookies,
120     DeleteCookie,
121     GetTimeouts,
122     SetTimeouts,
123     ElementClick,
124     ElementTap,
125     ElementClear,
126     ElementSendKeys,
127     PerformActions,
128     ReleaseActions,
129     DismissAlert,
130     AcceptAlert,
131     GetAlertText,
132     SendAlertText,
133     TakeScreenshot,
134     TakeElementScreenshot,
135     Status,
136     Extension(U),
137 }
138 
139 pub trait WebDriverExtensionRoute : Clone + Send + PartialEq {
140     type Command: WebDriverExtensionCommand + 'static;
141 
command(&self, &Captures, &Json) -> WebDriverResult<WebDriverCommand<Self::Command>>142     fn command(&self, &Captures, &Json) -> WebDriverResult<WebDriverCommand<Self::Command>>;
143 }
144 
145 #[derive(Clone, Debug, PartialEq)]
146 pub struct VoidWebDriverExtensionRoute;
147 
148 impl WebDriverExtensionRoute for VoidWebDriverExtensionRoute {
149     type Command = VoidWebDriverExtensionCommand;
150 
command(&self, _:&Captures, _:&Json) -> WebDriverResult<WebDriverCommand<VoidWebDriverExtensionCommand>>151     fn command(&self, _:&Captures, _:&Json) -> WebDriverResult<WebDriverCommand<VoidWebDriverExtensionCommand>> {
152         panic!("No extensions implemented");
153     }
154 }
155 
156 #[derive(Clone, Debug)]
157 struct RequestMatcher<U: WebDriverExtensionRoute> {
158     method: Method,
159     path_regexp: Regex,
160     match_type: Route<U>
161 }
162 
163 impl <U: WebDriverExtensionRoute> RequestMatcher<U> {
new(method: Method, path: &str, match_type: Route<U>) -> RequestMatcher<U>164     pub fn new(method: Method, path: &str, match_type: Route<U>) -> RequestMatcher<U> {
165         let path_regexp = RequestMatcher::<U>::compile_path(path);
166         RequestMatcher {
167             method: method,
168             path_regexp: path_regexp,
169             match_type: match_type
170         }
171     }
172 
get_match<'t>(&'t self, method: Method, path: &'t str) -> (bool, Option<Captures>)173     pub fn get_match<'t>(&'t self, method: Method, path: &'t str) -> (bool, Option<Captures>) {
174         let captures = self.path_regexp.captures(path);
175         (method == self.method, captures)
176     }
177 
compile_path(path: &str) -> Regex178     fn compile_path(path: &str) -> Regex {
179         let mut rv = String::new();
180         rv.push_str("^");
181         let components = path.split('/');
182         for component in components {
183             if component.starts_with("{") {
184                 if !component.ends_with("}") {
185                     panic!("Invalid url pattern")
186                 }
187                 rv.push_str(&format!("(?P<{}>[^/]+)/", &component[1..component.len()-1])[..]);
188             } else {
189                 rv.push_str(&format!("{}/", component)[..]);
190             }
191         }
192         //Remove the trailing /
193         rv.pop();
194         rv.push_str("$");
195         //This will fail at runtime if the regexp is invalid
196         Regex::new(&rv[..]).unwrap()
197     }
198 }
199 
200 #[derive(Debug)]
201 pub struct WebDriverHttpApi<U: WebDriverExtensionRoute> {
202     routes: Vec<(Method, RequestMatcher<U>)>,
203 }
204 
205 impl <U: WebDriverExtensionRoute> WebDriverHttpApi<U> {
new(extension_routes: &[(Method, &str, U)]) -> WebDriverHttpApi<U>206     pub fn new(extension_routes: &[(Method, &str, U)]) -> WebDriverHttpApi<U> {
207         let mut rv = WebDriverHttpApi::<U> {
208             routes: vec![],
209         };
210         debug!("Creating routes");
211         for &(ref method, ref url, ref match_type) in standard_routes::<U>().iter() {
212             rv.add(method.clone(), *url, (*match_type).clone());
213         };
214         for &(ref method, ref url, ref extension_route) in extension_routes.iter() {
215             rv.add(method.clone(), *url, Route::Extension(extension_route.clone()));
216         };
217         rv
218     }
219 
add(&mut self, method: Method, path: &str, match_type: Route<U>)220     fn add(&mut self, method: Method, path: &str, match_type: Route<U>) {
221         let http_matcher = RequestMatcher::new(method.clone(), path, match_type);
222         self.routes.push((method, http_matcher));
223     }
224 
decode_request(&self, method: Method, path: &str, body: &str) -> WebDriverResult<WebDriverMessage<U>>225     pub fn decode_request(&self, method: Method, path: &str, body: &str) -> WebDriverResult<WebDriverMessage<U>> {
226         let mut error = ErrorStatus::UnknownPath;
227         for &(ref match_method, ref matcher) in self.routes.iter() {
228             if method == *match_method {
229                 let (method_match, captures) = matcher.get_match(method.clone(), path);
230                 if captures.is_some() {
231                     if method_match {
232                         return WebDriverMessage::from_http(matcher.match_type.clone(),
233                                                            &captures.unwrap(),
234                                                            body,
235                                                            method == Post)
236                     } else {
237                         error = ErrorStatus::UnknownMethod;
238                     }
239                 }
240             }
241         }
242         Err(WebDriverError::new(error,
243                                 format!("{} {} did not match a known command", method, path)))
244     }
245 }
246