001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.protocol.http; 018 019import java.lang.reflect.Field; 020import java.lang.reflect.Modifier; 021import java.util.Collection; 022import java.util.TimeZone; 023 024import javax.servlet.http.Cookie; 025 026import org.apache.wicket.markup.html.pages.BrowserInfoPage; 027import org.apache.wicket.request.IRequestParameters; 028import org.apache.wicket.request.cycle.RequestCycle; 029import org.apache.wicket.request.http.WebRequest; 030import org.apache.wicket.util.io.IClusterable; 031import org.apache.wicket.util.string.AppendingStringBuffer; 032 033 034/** 035 * Description of various user agent (browser) properties. To fill the properties with values from 036 * the user agent you need to probe the browser using javascript and request header analysis. Wicket 037 * provides a default implementation of this in {@link BrowserInfoPage}. 038 * <p> 039 * A convenient way of letting Wicket do a sneaky redirect to {@link BrowserInfoPage} (and back 040 * again) is to put this in your Application's init method: 041 * 042 * <pre> 043 * getRequestCycleSettings().setGatherExtendedBrowserInfo(true); 044 * </pre> 045 * 046 * <p> 047 * 048 * WARNING: Be sure you think about the dangers of depending on information you pull from the client 049 * too much. They may be easily spoofed or inaccurate in other ways, and properties like window and 050 * browser size are all too easy to be used naively. 051 * 052 * @see BrowserInfoPage 053 * @author Frank Bille (frankbille) 054 */ 055public class ClientProperties implements IClusterable 056{ 057 private static final long serialVersionUID = 1L; 058 059 private int browserHeight = -1; 060 private int browserWidth = -1; 061 private boolean navigatorCookieEnabled; 062 private boolean navigatorJavaEnabled; 063 private String navigatorAppCodeName; 064 private String navigatorAppName; 065 private String navigatorAppVersion; 066 private String navigatorLanguage; 067 private String navigatorPlatform; 068 private String navigatorUserAgent; 069 private String remoteAddress; 070 private int screenColorDepth = -1; 071 private int screenHeight = -1; 072 private int screenWidth = -1; 073 private String utcDSTOffset; 074 private String utcOffset; 075 private String jsTimeZone; 076 private String hostname; 077 078 private boolean javaScriptEnabled; 079 080 /** Cached timezone for repeating calls to {@link #getTimeZone()} */ 081 private transient TimeZone timeZone; 082 083 /** 084 * @return The browser height at the time it was measured 085 */ 086 public int getBrowserHeight() 087 { 088 return browserHeight; 089 } 090 091 /** 092 * @return The browser width at the time it was measured 093 */ 094 public int getBrowserWidth() 095 { 096 return browserWidth; 097 } 098 099 /** 100 * @return The client's navigator.appCodeName property. 101 */ 102 public String getNavigatorAppCodeName() 103 { 104 return navigatorAppCodeName; 105 } 106 107 /** 108 * @return The client's navigator.appName property. 109 */ 110 public String getNavigatorAppName() 111 { 112 return navigatorAppName; 113 } 114 115 /** 116 * @return The client's navigator.appVersion property. 117 */ 118 public String getNavigatorAppVersion() 119 { 120 return navigatorAppVersion; 121 } 122 123 /** 124 * @return The client's navigator.language (or navigator.userLanguage) property. 125 */ 126 public String getNavigatorLanguage() 127 { 128 return navigatorLanguage; 129 } 130 131 /** 132 * @return The client's navigator.platform property. 133 */ 134 public String getNavigatorPlatform() 135 { 136 return navigatorPlatform; 137 } 138 139 /** 140 * @return The client's navigator.userAgent property. 141 */ 142 public String getNavigatorUserAgent() 143 { 144 return navigatorUserAgent; 145 } 146 147 /** 148 * @return The client's remote/ip address. 149 */ 150 public String getRemoteAddress() 151 { 152 return remoteAddress; 153 } 154 155 /** 156 * @return The clients hostname shown in the browser 157 */ 158 public String getHostname() 159 { 160 return hostname; 161 } 162 163 /** 164 * @return Color depth of the screen in bits (integer). 165 */ 166 public int getScreenColorDepth() 167 { 168 return screenColorDepth; 169 } 170 171 /** 172 * @return Height of the screen in pixels (integer). 173 */ 174 public int getScreenHeight() 175 { 176 return screenHeight; 177 } 178 179 /** 180 * @return Height of the screen in pixels (integer). 181 */ 182 public int getScreenWidth() 183 { 184 return screenWidth; 185 } 186 187 /** 188 * Get the client's time zone if that could be detected. 189 * 190 * @return The client's time zone 191 */ 192 public TimeZone getTimeZone() 193 { 194 if (timeZone == null && jsTimeZone != null) 195 { 196 TimeZone temptimeZone = TimeZone.getTimeZone(jsTimeZone); 197 if (jsTimeZone.equals(temptimeZone.getID())) 198 { 199 timeZone = temptimeZone; 200 } 201 } 202 if (timeZone == null) 203 { 204 String utc = getUtcOffset(); 205 if (utc != null) 206 { 207 // apparently it is platform dependent on whether you get the 208 // offset in a decimal form or not. This parses the decimal 209 // form of the UTC offset, taking into account several 210 // possibilities 211 // such as getting the format in +2.5 or -1.2 212 213 int dotPos = utc.indexOf('.'); 214 if (dotPos >= 0) 215 { 216 String hours = utc.substring(0, dotPos); 217 String hourPart = utc.substring(dotPos + 1); 218 219 if (hours.startsWith("+")) 220 { 221 hours = hours.substring(1); 222 } 223 int offsetHours = Integer.parseInt(hours); 224 int offsetMins = (int)(Double.parseDouble(hourPart) * 6); 225 226 // construct a GMT timezone offset string from the retrieved 227 // offset which can be parsed by the TimeZone class. 228 229 AppendingStringBuffer sb = new AppendingStringBuffer("GMT"); 230 sb.append(offsetHours > 0 ? '+' : '-'); 231 sb.append(Math.abs(offsetHours)); 232 sb.append(':'); 233 if (offsetMins < 10) 234 { 235 sb.append('0'); 236 } 237 sb.append(offsetMins); 238 timeZone = TimeZone.getTimeZone(sb.toString()); 239 } 240 else 241 { 242 int offset = Integer.parseInt(utc); 243 if (offset < 0) 244 { 245 utc = utc.substring(1); 246 } 247 timeZone = TimeZone.getTimeZone("GMT" + ((offset > 0) ? '+' : '-') + utc); 248 } 249 250 String dstOffset = getUtcDSTOffset(); 251 if (timeZone != null && dstOffset != null) 252 { 253 TimeZone dstTimeZone; 254 dotPos = dstOffset.indexOf('.'); 255 if (dotPos >= 0) 256 { 257 String hours = dstOffset.substring(0, dotPos); 258 String hourPart = dstOffset.substring(dotPos + 1); 259 260 if (hours.startsWith("+")) 261 { 262 hours = hours.substring(1); 263 } 264 int offsetHours = Integer.parseInt(hours); 265 int offsetMins = (int)(Double.parseDouble(hourPart) * 6); 266 267 // construct a GMT timezone offset string from the 268 // retrieved 269 // offset which can be parsed by the TimeZone class. 270 271 AppendingStringBuffer sb = new AppendingStringBuffer("GMT"); 272 sb.append(offsetHours > 0 ? '+' : '-'); 273 sb.append(Math.abs(offsetHours)); 274 sb.append(':'); 275 if (offsetMins < 10) 276 { 277 sb.append('0'); 278 } 279 sb.append(offsetMins); 280 dstTimeZone = TimeZone.getTimeZone(sb.toString()); 281 } 282 else 283 { 284 int offset = Integer.parseInt(dstOffset); 285 if (offset < 0) 286 { 287 dstOffset = dstOffset.substring(1); 288 } 289 dstTimeZone = TimeZone.getTimeZone("GMT" + ((offset > 0) ? '+' : '-') + 290 dstOffset); 291 } 292 // if the dstTimezone (1 July) has a different offset then 293 // the real time zone (1 January) try to combine the 2. 294 if (dstTimeZone != null && 295 dstTimeZone.getRawOffset() != timeZone.getRawOffset()) 296 { 297 int dstSaving = Math.abs(dstTimeZone.getRawOffset() - timeZone.getRawOffset()); 298 String[] availableIDs = TimeZone.getAvailableIDs(dstTimeZone.getRawOffset() < timeZone.getRawOffset() ? dstTimeZone.getRawOffset() : timeZone.getRawOffset()); 299 for (String availableID : availableIDs) 300 { 301 TimeZone zone = TimeZone.getTimeZone(availableID); 302 if (zone.getDSTSavings() == dstSaving) 303 { 304 // this is a best guess... still the start and end of the DST should 305 // be needed to know to be completely correct, or better yet 306 // not just the GMT offset but the TimeZone ID should be transfered 307 // from the browser. 308 timeZone = zone; 309 break; 310 } 311 } 312 } 313 } 314 } 315 } 316 317 return timeZone; 318 } 319 320 /** 321 * @return The client's time DST offset from UTC in hours (note: if you do this yourself, use 322 * 'new Date(new Date().getFullYear(), 0, 6, 0, 0, 0, 0).getTimezoneOffset() / -60' 323 * (note the -)). 324 */ 325 public String getUtcDSTOffset() 326 { 327 return utcDSTOffset; 328 } 329 330 331 /** 332 * @return The client's time offset from UTC in hours (note: if you do this yourself, use 'new 333 * Date(new Date().getFullYear(), 0, 1, 0, 0, 0, 0).getTimezoneOffset() / -60' (note the 334 * -)). 335 */ 336 public String getUtcOffset() 337 { 338 return utcOffset; 339 } 340 341 /** 342 * Flag indicating support of JavaScript in the browser. 343 * 344 * @return True if JavaScript is enabled 345 */ 346 public boolean isJavaScriptEnabled() { 347 return javaScriptEnabled; 348 } 349 350 /** 351 * 352 * 353 * @return The client's navigator.cookieEnabled property. 354 */ 355 public boolean isNavigatorCookieEnabled() 356 { 357 if (!navigatorCookieEnabled && RequestCycle.get() != null) 358 { 359 Collection<Cookie> cookies = ((WebRequest)RequestCycle.get().getRequest()).getCookies(); 360 navigatorCookieEnabled = cookies != null && cookies.size() > 0; 361 } 362 return navigatorCookieEnabled; 363 } 364 365 /** 366 * @return The client's navigator.javaEnabled property. 367 */ 368 public boolean isNavigatorJavaEnabled() 369 { 370 return navigatorJavaEnabled; 371 } 372 373 /** 374 * @param browserHeight 375 * The height of the browser 376 */ 377 public void setBrowserHeight(int browserHeight) 378 { 379 this.browserHeight = browserHeight; 380 } 381 382 /** 383 * @param browserWidth 384 * The browser width 385 */ 386 public void setBrowserWidth(int browserWidth) 387 { 388 this.browserWidth = browserWidth; 389 } 390 391 /** 392 * @param cookiesEnabled 393 * The client's navigator.cookieEnabled property. 394 */ 395 public void setNavigatorCookieEnabled(boolean cookiesEnabled) 396 { 397 this.navigatorCookieEnabled = cookiesEnabled; 398 } 399 400 /** 401 * @param navigatorJavaEnabled 402 * The client's navigator.javaEnabled property. 403 */ 404 public void setNavigatorJavaEnabled(boolean navigatorJavaEnabled) 405 { 406 this.navigatorJavaEnabled = navigatorJavaEnabled; 407 } 408 409 /** 410 * @param navigatorAppCodeName 411 * The client's navigator.appCodeName property. 412 */ 413 public void setNavigatorAppCodeName(String navigatorAppCodeName) 414 { 415 this.navigatorAppCodeName = navigatorAppCodeName; 416 } 417 418 /** 419 * @param navigatorAppName 420 * The client's navigator.appName property. 421 */ 422 public void setNavigatorAppName(String navigatorAppName) 423 { 424 this.navigatorAppName = navigatorAppName; 425 } 426 427 /** 428 * @param navigatorAppVersion 429 * The client's navigator.appVersion property. 430 */ 431 public void setNavigatorAppVersion(String navigatorAppVersion) 432 { 433 this.navigatorAppVersion = navigatorAppVersion; 434 } 435 436 /** 437 * @param navigatorLanguage 438 * The client's navigator.language (or navigator.userLanguage) property. 439 */ 440 public void setNavigatorLanguage(String navigatorLanguage) 441 { 442 this.navigatorLanguage = navigatorLanguage; 443 } 444 445 /** 446 * @param navigatorPlatform 447 * The client's navigator.platform property. 448 */ 449 public void setNavigatorPlatform(String navigatorPlatform) 450 { 451 this.navigatorPlatform = navigatorPlatform; 452 } 453 454 /** 455 * @param navigatorUserAgent 456 * The client's navigator.userAgent property. 457 */ 458 public void setNavigatorUserAgent(String navigatorUserAgent) 459 { 460 this.navigatorUserAgent = navigatorUserAgent; 461 } 462 463 /** 464 * @param remoteAddress 465 * The client's remote/ip address. 466 */ 467 public void setRemoteAddress(String remoteAddress) 468 { 469 this.remoteAddress = remoteAddress; 470 } 471 472 /** 473 * @param hostname 474 * the hostname shown in the browser. 475 */ 476 public void setHostname(String hostname) 477 { 478 this.hostname = hostname; 479 } 480 481 /** 482 * @param screenColorDepth 483 * Color depth of the screen in bits (integer). 484 */ 485 public void setScreenColorDepth(int screenColorDepth) 486 { 487 this.screenColorDepth = screenColorDepth; 488 } 489 490 /** 491 * @param screenHeight 492 * Height of the screen in pixels (integer). 493 */ 494 public void setScreenHeight(int screenHeight) 495 { 496 this.screenHeight = screenHeight; 497 } 498 499 /** 500 * @param screenWidth 501 * Height of the screen in pixels (integer). 502 */ 503 public void setScreenWidth(int screenWidth) 504 { 505 this.screenWidth = screenWidth; 506 } 507 508 /** 509 * Sets time zone. 510 * 511 * @param timeZone 512 */ 513 public void setTimeZone(TimeZone timeZone) 514 { 515 this.timeZone = timeZone; 516 } 517 518 /** 519 * @param utcDSTOffset 520 */ 521 public void setUtcDSTOffset(String utcDSTOffset) 522 { 523 this.utcDSTOffset = utcDSTOffset; 524 } 525 526 /** 527 * @param utcOffset 528 * The client's time offset from UTC in minutes (note: if you do this yourself, use 529 * 'new Date().getTimezoneOffset() / -60' (note the -)). 530 */ 531 public void setUtcOffset(String utcOffset) 532 { 533 this.utcOffset = utcOffset; 534 } 535 536 /** 537 * @param jsTimeZone 538 */ 539 public void setJsTimeZone(String jsTimeZone) 540 { 541 this.jsTimeZone = jsTimeZone; 542 } 543 544 /** 545 * @param javaScriptEnabled 546 * is JavaScript supported in the browser 547 */ 548 public void setJavaScriptEnabled(boolean javaScriptEnabled) { 549 this.javaScriptEnabled = javaScriptEnabled; 550 } 551 552 @Override 553 public String toString() 554 { 555 StringBuilder b = new StringBuilder(); 556 557 Class<?> clazz = getClass(); 558 while (clazz != Object.class) { 559 Field[] fields = clazz.getDeclaredFields(); 560 561 for (Field field : fields) 562 { 563 // Ignore these fields 564 if (Modifier.isStatic(field.getModifiers()) || 565 Modifier.isTransient(field.getModifiers()) || 566 field.isSynthetic()) 567 { 568 continue; 569 } 570 571 field.setAccessible(true); 572 573 Object value; 574 try 575 { 576 value = field.get(this); 577 } 578 catch (IllegalArgumentException e) 579 { 580 throw new RuntimeException(e); 581 } 582 catch (IllegalAccessException e) 583 { 584 throw new RuntimeException(e); 585 } 586 587 if (field.getType().equals(Integer.TYPE)) 588 { 589 if (Integer.valueOf(-1).equals(value)) 590 { 591 value = null; 592 } 593 } 594 595 if (value != null) 596 { 597 b.append(field.getName()); 598 b.append('='); 599 b.append(value); 600 b.append('\n'); 601 } 602 } 603 604 clazz = clazz.getSuperclass(); 605 } 606 return b.toString(); 607 } 608 609 /** 610 * Read parameters. 611 * 612 * @param parameters 613 * parameters sent from browser 614 */ 615 public void read(IRequestParameters parameters) 616 { 617 setNavigatorAppCodeName(parameters.getParameterValue("navigatorAppCodeName").toString("N/A")); 618 setNavigatorAppName(parameters.getParameterValue("navigatorAppName").toString("N/A")); 619 setNavigatorAppVersion(parameters.getParameterValue("navigatorAppVersion").toString("N/A")); 620 setNavigatorCookieEnabled(parameters.getParameterValue("navigatorCookieEnabled").toBoolean(false)); 621 setNavigatorJavaEnabled(parameters.getParameterValue("navigatorJavaEnabled").toBoolean(false)); 622 setNavigatorLanguage(parameters.getParameterValue("navigatorLanguage").toString("N/A")); 623 setNavigatorPlatform(parameters.getParameterValue("navigatorPlatform").toString("N/A")); 624 setNavigatorUserAgent(parameters.getParameterValue("navigatorUserAgent").toString("N/A")); 625 setScreenWidth(parameters.getParameterValue("screenWidth").toInt(-1)); 626 setScreenHeight(parameters.getParameterValue("screenHeight").toInt(-1)); 627 setScreenColorDepth(parameters.getParameterValue("screenColorDepth").toInt(-1)); 628 setUtcOffset(parameters.getParameterValue("utcOffset").toString(null)); 629 setUtcDSTOffset(parameters.getParameterValue("utcDSTOffset").toString(null)); 630 setJsTimeZone(parameters.getParameterValue("jsTimeZone").toString(null)); 631 setBrowserWidth(parameters.getParameterValue("browserWidth").toInt(-1)); 632 setBrowserHeight(parameters.getParameterValue("browserHeight").toInt(-1)); 633 setHostname(parameters.getParameterValue("hostname").toString("N/A")); 634 } 635}