View Javadoc
1   /*
2    * Copyright (c) 2002-2017 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package com.gargoylesoftware.htmlunit.javascript.host.geo;
16  
17  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
18  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.EDGE;
19  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF;
20  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.IE;
21  
22  import java.io.BufferedReader;
23  import java.io.IOException;
24  import java.io.InputStreamReader;
25  import java.util.ArrayList;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Locale;
29  
30  import org.apache.commons.lang3.StringUtils;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  
34  import com.gargoylesoftware.htmlunit.BrowserVersion;
35  import com.gargoylesoftware.htmlunit.Page;
36  import com.gargoylesoftware.htmlunit.WebClient;
37  import com.gargoylesoftware.htmlunit.WebWindow;
38  import com.gargoylesoftware.htmlunit.html.HtmlPage;
39  import com.gargoylesoftware.htmlunit.javascript.JavaScriptEngine;
40  import com.gargoylesoftware.htmlunit.javascript.SimpleScriptable;
41  import com.gargoylesoftware.htmlunit.javascript.background.BackgroundJavaScriptFactory;
42  import com.gargoylesoftware.htmlunit.javascript.background.JavaScriptJob;
43  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass;
44  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstructor;
45  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxFunction;
46  
47  import net.sourceforge.htmlunit.corejs.javascript.Function;
48  
49  /**
50   * A JavaScript object for {@code Geolocation}.
51   *
52   * @author Ahmed Ashour
53   */
54  @JsxClass({IE, EDGE})
55  @JsxClass(isJSObject = false, value = {CHROME, FF})
56  public class Geolocation extends SimpleScriptable {
57  
58      private static final Log LOG = LogFactory.getLog(Geolocation.class);
59  
60      /* Do not use this URL without Google permission! */
61      private static String PROVIDER_URL_ = "https://maps.googleapis.com/maps/api/browserlocation/json";
62      private Function successHandler_;
63  
64      @SuppressWarnings("unused")
65      private Function errorHandler_;
66  
67      /**
68       * Creates an instance.
69       */
70      @JsxConstructor(EDGE)
71      public Geolocation() {
72      }
73  
74      /**
75       * Gets the current position.
76       * @param successCallback success callback
77       * @param errorCallback optional error callback
78       * @param options optional options
79       */
80      @JsxFunction
81      public void getCurrentPosition(final Function successCallback, final Object errorCallback,
82              final Object options) {
83          successHandler_ = successCallback;
84          if (errorCallback instanceof Function) {
85              errorHandler_ = (Function) errorCallback;
86          }
87          else {
88              errorHandler_ = null;
89          }
90          final WebWindow webWindow = getWindow().getWebWindow();
91          if (webWindow.getWebClient().getOptions().isGeolocationEnabled()) {
92              final JavaScriptJob job = BackgroundJavaScriptFactory.theFactory()
93                      .createJavaScriptJob(0, null, new Runnable() {
94                          @Override
95                          public void run() {
96                              doGetPosition();
97                          }
98                      });
99              webWindow.getJobManager().addJob(job, getWindow().getWebWindow().getEnclosedPage());
100         }
101     }
102 
103     /**
104      * Notifies the callbacks whenever the position changes, till clearWatch() is called.
105      * @param successCallback success callback
106      * @param errorCallback optional error callback
107      * @param options optional options
108      * @return the watch id
109      */
110     @JsxFunction
111     public int watchPosition(final Function successCallback, final Object errorCallback,
112             final Object options) {
113         return 0;
114     }
115 
116     /**
117      * Clears the specified watch ID.
118      * @param watchId the watch id
119      */
120     @JsxFunction
121     public void clearWatch(final int watchId) {
122     }
123 
124     private void doGetPosition() {
125         final String os = System.getProperty("os.name").toLowerCase(Locale.ROOT);
126         String wifiStringString = null;
127         if (os.contains("win")) {
128             wifiStringString = getWifiStringWindows();
129         }
130         if (wifiStringString != null) {
131             String url = PROVIDER_URL_;
132             if (url.contains("?")) {
133                 url += '&';
134             }
135             else {
136                 url += '?';
137             }
138             url += "browser=firefox&sensor=true";
139             url += wifiStringString;
140 
141             while (url.length() >= 1900) {
142                 url = url.substring(0, url.lastIndexOf("&wifi="));
143             }
144 
145             if (LOG.isInfoEnabled()) {
146                 LOG.info("Invoking URL: " + url);
147             }
148 
149             try (WebClient webClient = new WebClient(BrowserVersion.FIREFOX_45)) {
150                 final Page page = webClient.getPage(url);
151                 final String content = page.getWebResponse().getContentAsString();
152                 if (LOG.isDebugEnabled()) {
153                     LOG.debug("Receieved Content: " + content);
154                 }
155                 final double latitude = Double.parseDouble(getJSONValue(content, "lat"));
156                 final double longitude = Double.parseDouble(getJSONValue(content, "lng"));
157                 final double accuracy = Double.parseDouble(getJSONValue(content, "accuracy"));
158 
159                 final Coordinates coordinates = new Coordinates(latitude, longitude, accuracy);
160                 coordinates.setPrototype(getPrototype(coordinates.getClass()));
161 
162                 final Position position = new Position(coordinates);
163                 position.setPrototype(getPrototype(position.getClass()));
164 
165                 final JavaScriptEngine jsEngine =
166                         (JavaScriptEngine) getWindow().getWebWindow().getWebClient().getJavaScriptEngine();
167                 jsEngine.callFunction((HtmlPage) getWindow().getWebWindow().getEnclosedPage(), successHandler_, this,
168                         getParentScope(), new Object[] {position});
169             }
170             catch (final Exception e) {
171                 LOG.error("", e);
172             }
173         }
174         else {
175             LOG.error("Operating System not supported: " + os);
176         }
177     }
178 
179     private static String getJSONValue(final String content, final String key) {
180         final StringBuilder builder = new StringBuilder();
181         int index = content.indexOf("\"" + key + "\"") + key.length() + 2;
182         for (index = content.indexOf(':', index) + 1; index < content.length(); index++) {
183             final char ch = content.charAt(index);
184             if (ch == ',' || ch == '}') {
185                 break;
186             }
187             builder.append(ch);
188         }
189         return builder.toString().trim();
190     }
191 
192     String getWifiStringWindows() {
193         final StringBuilder builder = new StringBuilder();
194         try {
195             final List<String> lines = runCommand("netsh wlan show networks mode=bssid");
196             for (final Iterator<String> it = lines.iterator(); it.hasNext();) {
197                 String line = it.next();
198                 if (line.startsWith("SSID ")) {
199                     final String name = line.substring(line.lastIndexOf(' ') + 1);
200                     if (it.hasNext()) {
201                         it.next();
202                     }
203                     if (it.hasNext()) {
204                         it.next();
205                     }
206                     if (it.hasNext()) {
207                         it.next();
208                     }
209                     while (it.hasNext()) {
210                         line = it.next();
211                         if (line.trim().startsWith("BSSID ")) {
212                             final String mac = line.substring(line.lastIndexOf(' ') + 1);
213                             if (it.hasNext()) {
214                                 line = it.next().trim();
215                                 if (line.startsWith("Signal")) {
216                                     final String signal = line.substring(line.lastIndexOf(' ') + 1, line.length() - 1);
217                                     final int signalStrength = Integer.parseInt(signal) / 2 - 100;
218                                     builder.append("&wifi=")
219                                         .append("mac:")
220                                         .append(mac.replace(':', '-'))
221                                         .append("%7C")
222                                         .append("ssid:")
223                                         .append(name)
224                                         .append("%7C")
225                                         .append("ss:")
226                                         .append(signalStrength);
227                                 }
228                             }
229                         }
230                         if (StringUtils.isBlank(line)) {
231                             break;
232                         }
233                     }
234                 }
235             }
236         }
237         catch (final IOException e) {
238             //
239         }
240         return builder.toString();
241     }
242 
243     private static List<String> runCommand(final String command) throws IOException {
244         final List<String> list = new ArrayList<>();
245         final Process p = Runtime.getRuntime().exec(command);
246         try (BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
247             String line;
248             while ((line = reader.readLine()) != null) {
249                 list.add(line);
250             }
251         }
252         return list;
253     }
254 
255     static void setProviderUrl(final String url) {
256         PROVIDER_URL_ = url;
257     }
258 }