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