Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

VaadinServlet.java 53KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436
  1. /*
  2. * Copyright 2000-2014 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.server;
  17. import java.io.BufferedWriter;
  18. import java.io.File;
  19. import java.io.FileNotFoundException;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.io.OutputStream;
  23. import java.io.OutputStreamWriter;
  24. import java.io.PrintWriter;
  25. import java.io.Serializable;
  26. import java.lang.reflect.Method;
  27. import java.net.MalformedURLException;
  28. import java.net.URL;
  29. import java.net.URLConnection;
  30. import java.net.URLDecoder;
  31. import java.net.URISyntaxException;
  32. import java.util.ArrayList;
  33. import java.util.Arrays;
  34. import java.util.Collection;
  35. import java.util.Enumeration;
  36. import java.util.HashMap;
  37. import java.util.HashSet;
  38. import java.util.List;
  39. import java.util.Map;
  40. import java.util.Properties;
  41. import java.util.logging.Level;
  42. import java.util.logging.Logger;
  43. import javax.servlet.ServletContext;
  44. import javax.servlet.ServletException;
  45. import javax.servlet.http.HttpServlet;
  46. import javax.servlet.http.HttpServletRequest;
  47. import javax.servlet.http.HttpServletResponse;
  48. import com.google.gwt.thirdparty.guava.common.base.Charsets;
  49. import com.google.gwt.thirdparty.guava.common.io.Files;
  50. import com.vaadin.annotations.VaadinServletConfiguration;
  51. import com.vaadin.annotations.VaadinServletConfiguration.InitParameterName;
  52. import com.vaadin.sass.internal.ScssStylesheet;
  53. import com.vaadin.server.communication.ServletUIInitHandler;
  54. import com.vaadin.shared.JsonConstants;
  55. import com.vaadin.shared.Version;
  56. import com.vaadin.ui.UI;
  57. import com.vaadin.util.CurrentInstance;
  58. import elemental.json.Json;
  59. import elemental.json.JsonArray;
  60. import elemental.json.JsonObject;
  61. @SuppressWarnings("serial")
  62. public class VaadinServlet extends HttpServlet implements Constants {
  63. private class ScssCacheEntry implements Serializable {
  64. private final String css;
  65. private final List<String> sourceUris;
  66. private final long timestamp;
  67. private final String scssFileName;
  68. public ScssCacheEntry(String scssFileName, String css,
  69. List<String> sourceUris) {
  70. this.scssFileName = scssFileName;
  71. this.css = css;
  72. this.sourceUris = sourceUris;
  73. timestamp = getLastModified();
  74. }
  75. public ScssCacheEntry(JsonObject json) {
  76. css = json.getString("css");
  77. timestamp = Long.parseLong(json.getString("timestamp"));
  78. sourceUris = new ArrayList<String>();
  79. JsonArray uris = json.getArray("uris");
  80. for (int i = 0; i < uris.length(); i++) {
  81. sourceUris.add(uris.getString(i));
  82. }
  83. // Not set for cache entries read from disk
  84. scssFileName = null;
  85. }
  86. public String asJson() {
  87. JsonArray uris = Json.createArray();
  88. for (String uri : sourceUris) {
  89. uris.set(uris.length(), uri);
  90. }
  91. JsonObject object = Json.createObject();
  92. object.put("version", Version.getFullVersion());
  93. object.put("timestamp", Long.toString(timestamp));
  94. object.put("uris", uris);
  95. object.put("css", css);
  96. return object.toJson();
  97. }
  98. public String getCss() {
  99. return css;
  100. }
  101. private long getLastModified() {
  102. long newest = 0;
  103. for (String uri : sourceUris) {
  104. File file = new File(uri);
  105. URL resource = getService().getClassLoader().getResource(uri);
  106. long lastModified = -1L;
  107. if (file.exists()) {
  108. lastModified = file.lastModified();
  109. } else if (resource != null && resource.getProtocol().equals("file")) {
  110. try {
  111. file = new File(resource.toURI());
  112. if (file.exists()) {
  113. lastModified = file.lastModified();
  114. }
  115. } catch (URISyntaxException e) {
  116. getLogger().log(Level.WARNING, "Could not resolve timestamp for " + resource, e);
  117. }
  118. }
  119. if (lastModified == -1L && resource == null) {
  120. /*
  121. * Ignore missing files found in the classpath,
  122. * report problem and abort for other files.
  123. */
  124. getLogger()
  125. .log(Level.WARNING,
  126. "Could not resolve timestamp for {0}, Scss on the fly caching will be disabled",
  127. uri);
  128. // -1 means this cache entry will never be valid
  129. return -1;
  130. }
  131. newest = Math.max(newest, lastModified);
  132. }
  133. return newest;
  134. }
  135. public boolean isStillValid() {
  136. if (timestamp == -1) {
  137. /*
  138. * Don't ever bother checking anything if files used during the
  139. * compilation were gone before the cache entry was created.
  140. */
  141. return false;
  142. } else if (timestamp != getLastModified()) {
  143. /*
  144. * Would in theory still be valid if the last modification is
  145. * before the recorded timestamp, but that would still mean that
  146. * something has changed since we last checked, so let's
  147. * invalidate in that case as well to be on the safe side.
  148. */
  149. return false;
  150. } else {
  151. return true;
  152. }
  153. }
  154. public String getScssFileName() {
  155. return scssFileName;
  156. }
  157. }
  158. private VaadinServletService servletService;
  159. /**
  160. * Called by the servlet container to indicate to a servlet that the servlet
  161. * is being placed into service.
  162. *
  163. * @param servletConfig
  164. * the object containing the servlet's configuration and
  165. * initialization parameters
  166. * @throws ServletException
  167. * if an exception has occurred that interferes with the
  168. * servlet's normal operation.
  169. */
  170. @Override
  171. public void init(javax.servlet.ServletConfig servletConfig)
  172. throws ServletException {
  173. CurrentInstance.clearAll();
  174. super.init(servletConfig);
  175. Properties initParameters = new Properties();
  176. readUiFromEnclosingClass(initParameters);
  177. readConfigurationAnnotation(initParameters);
  178. // Read default parameters from server.xml
  179. final ServletContext context = servletConfig.getServletContext();
  180. for (final Enumeration<String> e = context.getInitParameterNames(); e
  181. .hasMoreElements();) {
  182. final String name = e.nextElement();
  183. initParameters.setProperty(name, context.getInitParameter(name));
  184. }
  185. // Override with application config from web.xml
  186. for (final Enumeration<String> e = servletConfig
  187. .getInitParameterNames(); e.hasMoreElements();) {
  188. final String name = e.nextElement();
  189. initParameters.setProperty(name,
  190. servletConfig.getInitParameter(name));
  191. }
  192. DeploymentConfiguration deploymentConfiguration = createDeploymentConfiguration(initParameters);
  193. try {
  194. servletService = createServletService(deploymentConfiguration);
  195. } catch (ServiceException e) {
  196. throw new ServletException("Could not initialize VaadinServlet", e);
  197. }
  198. // Sets current service even though there are no request and response
  199. servletService.setCurrentInstances(null, null);
  200. servletInitialized();
  201. CurrentInstance.clearAll();
  202. }
  203. private void readUiFromEnclosingClass(Properties initParameters) {
  204. Class<?> enclosingClass = getClass().getEnclosingClass();
  205. if (enclosingClass != null && UI.class.isAssignableFrom(enclosingClass)) {
  206. initParameters.put(VaadinSession.UI_PARAMETER,
  207. enclosingClass.getName());
  208. }
  209. }
  210. private void readConfigurationAnnotation(Properties initParameters)
  211. throws ServletException {
  212. VaadinServletConfiguration configAnnotation = UIProvider
  213. .getAnnotationFor(getClass(), VaadinServletConfiguration.class);
  214. if (configAnnotation != null) {
  215. Method[] methods = VaadinServletConfiguration.class
  216. .getDeclaredMethods();
  217. for (Method method : methods) {
  218. InitParameterName name = method
  219. .getAnnotation(InitParameterName.class);
  220. assert name != null : "All methods declared in VaadinServletConfiguration should have a @InitParameterName annotation";
  221. try {
  222. Object value = method.invoke(configAnnotation);
  223. String stringValue;
  224. if (value instanceof Class<?>) {
  225. stringValue = ((Class<?>) value).getName();
  226. } else {
  227. stringValue = value.toString();
  228. }
  229. initParameters.setProperty(name.value(), stringValue);
  230. } catch (Exception e) {
  231. // This should never happen
  232. throw new ServletException(
  233. "Could not read @VaadinServletConfiguration value "
  234. + method.getName(), e);
  235. }
  236. }
  237. }
  238. }
  239. protected void servletInitialized() throws ServletException {
  240. // Empty by default
  241. }
  242. /**
  243. * Gets the currently used Vaadin servlet. The current servlet is
  244. * automatically defined when initializing the servlet and when processing
  245. * requests to the server and in threads started at a point when the current
  246. * servlet is defined (see {@link InheritableThreadLocal}). In other cases,
  247. * (e.g. from background threads started in some other way), the current
  248. * servlet is not automatically defined.
  249. * <p>
  250. * The current servlet is derived from the current service using
  251. * {@link VaadinService#getCurrent()}
  252. *
  253. * @return the current Vaadin servlet instance if available, otherwise
  254. * <code>null</code>
  255. *
  256. * @since 7.0
  257. */
  258. public static VaadinServlet getCurrent() {
  259. VaadinService vaadinService = CurrentInstance.get(VaadinService.class);
  260. if (vaadinService instanceof VaadinServletService) {
  261. VaadinServletService vss = (VaadinServletService) vaadinService;
  262. return vss.getServlet();
  263. } else {
  264. return null;
  265. }
  266. }
  267. protected DeploymentConfiguration createDeploymentConfiguration(
  268. Properties initParameters) {
  269. return new DefaultDeploymentConfiguration(getClass(), initParameters);
  270. }
  271. protected VaadinServletService createServletService(
  272. DeploymentConfiguration deploymentConfiguration)
  273. throws ServiceException {
  274. VaadinServletService service = new VaadinServletService(this,
  275. deploymentConfiguration);
  276. service.init();
  277. return service;
  278. }
  279. /**
  280. * Receives standard HTTP requests from the public service method and
  281. * dispatches them.
  282. *
  283. * @param request
  284. * the object that contains the request the client made of the
  285. * servlet.
  286. * @param response
  287. * the object that contains the response the servlet returns to
  288. * the client.
  289. * @throws ServletException
  290. * if an input or output error occurs while the servlet is
  291. * handling the TRACE request.
  292. * @throws IOException
  293. * if the request for the TRACE cannot be handled.
  294. */
  295. @Override
  296. protected void service(HttpServletRequest request,
  297. HttpServletResponse response) throws ServletException, IOException {
  298. // Handle context root request without trailing slash, see #9921
  299. if (handleContextRootWithoutSlash(request, response)) {
  300. return;
  301. }
  302. CurrentInstance.clearAll();
  303. VaadinServletRequest vaadinRequest = createVaadinRequest(request);
  304. VaadinServletResponse vaadinResponse = createVaadinResponse(response);
  305. if (!ensureCookiesEnabled(vaadinRequest, vaadinResponse)) {
  306. return;
  307. }
  308. if (isStaticResourceRequest(vaadinRequest)) {
  309. // Define current servlet and service, but no request and response
  310. getService().setCurrentInstances(null, null);
  311. try {
  312. serveStaticResources(vaadinRequest, vaadinResponse);
  313. return;
  314. } finally {
  315. CurrentInstance.clearAll();
  316. }
  317. }
  318. try {
  319. getService().handleRequest(vaadinRequest, vaadinResponse);
  320. } catch (ServiceException e) {
  321. throw new ServletException(e);
  322. }
  323. }
  324. /**
  325. * Invoked for every request to this servlet to potentially send a redirect
  326. * to avoid problems with requests to the context root with no trailing
  327. * slash.
  328. *
  329. * @param request
  330. * the processed request
  331. * @param response
  332. * the processed response
  333. * @return <code>true</code> if a redirect has been sent and the request
  334. * should not be processed further; <code>false</code> if the
  335. * request should be processed as usual
  336. * @throws IOException
  337. * If an input or output exception occurs
  338. */
  339. protected boolean handleContextRootWithoutSlash(HttpServletRequest request,
  340. HttpServletResponse response) throws IOException {
  341. // Query parameters like "?a=b" are handled by the servlet container but
  342. // path parameter (e.g. ;jsessionid=) needs to be handled here
  343. String location = request.getRequestURI();
  344. String lastPathParameter = getLastPathParameter(location);
  345. location = location.substring(0,
  346. location.length() - lastPathParameter.length());
  347. if ((request.getPathInfo() == null || "/".equals(request.getPathInfo()))
  348. && "".equals(request.getServletPath())
  349. && !location.endsWith("/")) {
  350. /*
  351. * Path info is for the root but request URI doesn't end with a
  352. * slash -> redirect to the same URI but with an ending slash.
  353. */
  354. location = location + "/" + lastPathParameter;
  355. String queryString = request.getQueryString();
  356. if (queryString != null) {
  357. // Prevent HTTP Response splitting in case the server doesn't
  358. queryString = queryString.replaceAll("[\\r\\n]", "");
  359. location += '?' + queryString;
  360. }
  361. response.sendRedirect(location);
  362. return true;
  363. } else {
  364. return false;
  365. }
  366. }
  367. /**
  368. * Finds any path parameter added to the last part of the uri. A path
  369. * parameter is any string separated by ";" from the path and ends in / or
  370. * at the end of the string.
  371. * <p>
  372. * For example the uri http://myhost.com/foo;a=1/bar;b=1 contains two path
  373. * parameters, {@literal a=1} related to {@literal /foo} and {@literal b=1}
  374. * related to /bar.
  375. * <p>
  376. * For http://myhost.com/foo;a=1/bar;b=1 this method will return ;b=1
  377. *
  378. * @since 7.2
  379. * @param uri
  380. * a URI
  381. * @return the last path parameter of the uri including the semicolon or an
  382. * empty string. Never null.
  383. */
  384. protected static String getLastPathParameter(String uri) {
  385. int lastPathStart = uri.lastIndexOf('/');
  386. if (lastPathStart == -1) {
  387. return "";
  388. }
  389. int semicolonPos = uri.indexOf(';', lastPathStart);
  390. if (semicolonPos < 0) {
  391. // No path parameter for the last part
  392. return "";
  393. } else {
  394. // This includes the semicolon.
  395. String semicolonString = uri.substring(semicolonPos);
  396. return semicolonString;
  397. }
  398. }
  399. private VaadinServletResponse createVaadinResponse(
  400. HttpServletResponse response) {
  401. return new VaadinServletResponse(response, getService());
  402. }
  403. /**
  404. * Create a Vaadin request for a http servlet request. This method can be
  405. * overridden if the Vaadin request should have special properties.
  406. *
  407. * @param request
  408. * the original http servlet request
  409. * @return a Vaadin request for the original request
  410. */
  411. protected VaadinServletRequest createVaadinRequest(
  412. HttpServletRequest request) {
  413. return new VaadinServletRequest(request, getService());
  414. }
  415. /**
  416. * Gets a the vaadin service for this servlet.
  417. *
  418. * @return the vaadin service
  419. */
  420. protected VaadinServletService getService() {
  421. return servletService;
  422. }
  423. /**
  424. * Check that cookie support is enabled in the browser. Only checks UIDL
  425. * requests.
  426. *
  427. * @param requestType
  428. * Type of the request as returned by
  429. * {@link #getRequestType(HttpServletRequest)}
  430. * @param request
  431. * The request from the browser
  432. * @param response
  433. * The response to which an error can be written
  434. * @return false if cookies are disabled, true otherwise
  435. * @throws IOException
  436. */
  437. private boolean ensureCookiesEnabled(VaadinServletRequest request,
  438. VaadinServletResponse response) throws IOException {
  439. if (ServletPortletHelper.isUIDLRequest(request)) {
  440. // In all other but the first UIDL request a cookie should be
  441. // returned by the browser.
  442. // This can be removed if cookieless mode (#3228) is supported
  443. if (request.getRequestedSessionId() == null) {
  444. // User has cookies disabled
  445. SystemMessages systemMessages = getService().getSystemMessages(
  446. ServletPortletHelper.findLocale(null, null, request),
  447. request);
  448. getService().writeStringResponse(
  449. response,
  450. JsonConstants.JSON_CONTENT_TYPE,
  451. VaadinService.createCriticalNotificationJSON(
  452. systemMessages.getCookiesDisabledCaption(),
  453. systemMessages.getCookiesDisabledMessage(),
  454. null, systemMessages.getCookiesDisabledURL()));
  455. return false;
  456. }
  457. }
  458. return true;
  459. }
  460. /**
  461. * Send a notification to client-side widgetset. Used to notify client of
  462. * critical errors, session expiration and more. Server has no knowledge of
  463. * what UI client refers to.
  464. *
  465. * @param request
  466. * the HTTP request instance.
  467. * @param response
  468. * the HTTP response to write to.
  469. * @param caption
  470. * the notification caption
  471. * @param message
  472. * to notification body
  473. * @param details
  474. * a detail message to show in addition to the message. Currently
  475. * shown directly below the message but could be hidden behind a
  476. * details drop down in the future. Mainly used to give
  477. * additional information not necessarily useful to the end user.
  478. * @param url
  479. * url to load when the message is dismissed. Null will reload
  480. * the current page.
  481. * @throws IOException
  482. * if the writing failed due to input/output error.
  483. *
  484. * @deprecated As of 7.0. This method is retained only for backwards
  485. * compatibility and for {@link GAEVaadinServlet}.
  486. */
  487. @Deprecated
  488. protected void criticalNotification(VaadinServletRequest request,
  489. VaadinServletResponse response, String caption, String message,
  490. String details, String url) throws IOException {
  491. if (ServletPortletHelper.isUIDLRequest(request)) {
  492. String output = VaadinService.createCriticalNotificationJSON(
  493. caption, message, details, url);
  494. getService().writeStringResponse(response,
  495. JsonConstants.JSON_CONTENT_TYPE, output);
  496. } else {
  497. // Create an HTML reponse with the error
  498. String output = "";
  499. if (url != null) {
  500. output += "<a href=\"" + url + "\">";
  501. }
  502. if (caption != null) {
  503. output += "<b>" + caption + "</b><br/>";
  504. }
  505. if (message != null) {
  506. output += message;
  507. output += "<br/><br/>";
  508. }
  509. if (details != null) {
  510. output += details;
  511. output += "<br/><br/>";
  512. }
  513. if (url != null) {
  514. output += "</a>";
  515. }
  516. getService().writeStringResponse(response,
  517. "text/html; charset=UTF-8", output);
  518. }
  519. }
  520. /**
  521. * Writes the response in {@code output} using the contentType given in
  522. * {@code contentType} to the provided {@link HttpServletResponse}
  523. *
  524. * @param response
  525. * @param contentType
  526. * @param output
  527. * Output to write (UTF-8 encoded)
  528. * @throws IOException
  529. */
  530. private void writeResponse(HttpServletResponse response,
  531. String contentType, String output) throws IOException {
  532. response.setContentType(contentType);
  533. final OutputStream out = response.getOutputStream();
  534. // Set the response type
  535. final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
  536. new OutputStreamWriter(out, "UTF-8")));
  537. outWriter.print(output);
  538. outWriter.flush();
  539. outWriter.close();
  540. }
  541. /**
  542. * Gets resource path using different implementations. Required to
  543. * supporting different servlet container implementations (application
  544. * servers).
  545. *
  546. * @param servletContext
  547. * @param path
  548. * the resource path.
  549. * @return the resource path.
  550. *
  551. * @deprecated As of 7.0. Will likely change or be removed in a future
  552. * version
  553. */
  554. @Deprecated
  555. protected static String getResourcePath(ServletContext servletContext,
  556. String path) {
  557. String resultPath = null;
  558. resultPath = servletContext.getRealPath(path);
  559. if (resultPath != null) {
  560. return resultPath;
  561. } else {
  562. try {
  563. final URL url = servletContext.getResource(path);
  564. resultPath = url.getFile();
  565. } catch (final Exception e) {
  566. // FIXME: Handle exception
  567. getLogger().log(Level.INFO,
  568. "Could not find resource path " + path, e);
  569. }
  570. }
  571. return resultPath;
  572. }
  573. /**
  574. * A helper method to strip away characters that might somehow be used for
  575. * XSS attacks. Leaves at least alphanumeric characters intact. Also removes
  576. * e.g. '(' and ')', so values should be safe in javascript too.
  577. *
  578. * @param themeName
  579. * @return
  580. *
  581. * @deprecated As of 7.0. Will likely change or be removed in a future
  582. * version
  583. */
  584. @Deprecated
  585. public static String stripSpecialChars(String themeName) {
  586. StringBuilder sb = new StringBuilder();
  587. char[] charArray = themeName.toCharArray();
  588. for (int i = 0; i < charArray.length; i++) {
  589. char c = charArray[i];
  590. if (!CHAR_BLACKLIST.contains(c)) {
  591. sb.append(c);
  592. }
  593. }
  594. return sb.toString();
  595. }
  596. private static final Collection<Character> CHAR_BLACKLIST = new HashSet<Character>(
  597. Arrays.asList(new Character[] { '&', '"', '\'', '<', '>', '(', ')',
  598. ';' }));
  599. /**
  600. * Mutex for preventing to scss compilations to take place simultaneously.
  601. * This is a workaround needed as the scss compiler currently is not thread
  602. * safe (#10292).
  603. * <p>
  604. * In addition, this is also used to protect the cached compilation results.
  605. */
  606. private static final Object SCSS_MUTEX = new Object();
  607. /**
  608. * Global cache of scss compilation results. This map is protected from
  609. * concurrent access by {@link #SCSS_MUTEX}.
  610. */
  611. private final Map<String, ScssCacheEntry> scssCache = new HashMap<String, ScssCacheEntry>();
  612. /**
  613. * Keeps track of whether a warning about not being able to persist cache
  614. * files has already been printed. The flag is protected from concurrent
  615. * access by {@link #SCSS_MUTEX}.
  616. */
  617. private static boolean scssCompileWarWarningEmitted = false;
  618. /**
  619. * Returns the default theme. Must never return null.
  620. *
  621. * @return
  622. */
  623. public static String getDefaultTheme() {
  624. return DEFAULT_THEME_NAME;
  625. }
  626. /**
  627. * Check if this is a request for a static resource and, if it is, serve the
  628. * resource to the client.
  629. *
  630. * @param request
  631. * @param response
  632. * @return true if a file was served and the request has been handled, false
  633. * otherwise.
  634. * @throws IOException
  635. * @throws ServletException
  636. */
  637. private boolean serveStaticResources(HttpServletRequest request,
  638. HttpServletResponse response) throws IOException, ServletException {
  639. String pathInfo = request.getPathInfo();
  640. if (pathInfo == null) {
  641. return false;
  642. }
  643. String decodedRequestURI = URLDecoder.decode(request.getRequestURI(),
  644. "UTF-8");
  645. if ((request.getContextPath() != null)
  646. && (decodedRequestURI.startsWith("/VAADIN/"))) {
  647. serveStaticResourcesInVAADIN(decodedRequestURI, request, response);
  648. return true;
  649. }
  650. String decodedContextPath = URLDecoder.decode(request.getContextPath(),
  651. "UTF-8");
  652. if (decodedRequestURI.startsWith(decodedContextPath + "/VAADIN/")) {
  653. serveStaticResourcesInVAADIN(
  654. decodedRequestURI.substring(decodedContextPath.length()),
  655. request, response);
  656. return true;
  657. }
  658. return false;
  659. }
  660. /**
  661. * Serve resources from VAADIN directory.
  662. *
  663. * @param filename
  664. * The filename to serve. Should always start with /VAADIN/.
  665. * @param request
  666. * @param response
  667. * @throws IOException
  668. * @throws ServletException
  669. */
  670. private void serveStaticResourcesInVAADIN(String filename,
  671. HttpServletRequest request, HttpServletResponse response)
  672. throws IOException, ServletException {
  673. final ServletContext sc = getServletContext();
  674. URL resourceUrl = findResourceURL(filename, sc);
  675. if (resourceUrl == null) {
  676. // File not found, if this was a css request we still look for a
  677. // scss file with the same name
  678. if (serveOnTheFlyCompiledScss(filename, request, response, sc)) {
  679. return;
  680. } else {
  681. // cannot serve requested file
  682. getLogger()
  683. .log(Level.INFO,
  684. "Requested resource [{0}] not found from filesystem or through class loader."
  685. + " Add widgetset and/or theme JAR to your classpath or add files to WebContent/VAADIN folder.",
  686. filename);
  687. response.setStatus(HttpServletResponse.SC_NOT_FOUND);
  688. }
  689. return;
  690. }
  691. // security check: do not permit navigation out of the VAADIN
  692. // directory
  693. if (!isAllowedVAADINResourceUrl(request, resourceUrl)) {
  694. getLogger()
  695. .log(Level.INFO,
  696. "Requested resource [{0}] not accessible in the VAADIN directory or access to it is forbidden.",
  697. filename);
  698. response.setStatus(HttpServletResponse.SC_FORBIDDEN);
  699. return;
  700. }
  701. String cacheControl = "public, max-age=0, must-revalidate";
  702. int resourceCacheTime = getCacheTime(filename);
  703. if (resourceCacheTime > 0) {
  704. cacheControl = "max-age=" + String.valueOf(resourceCacheTime);
  705. }
  706. response.setHeader("Cache-Control", cacheControl);
  707. response.setDateHeader("Expires", System.currentTimeMillis()
  708. + (resourceCacheTime * 1000));
  709. // Find the modification timestamp
  710. long lastModifiedTime = 0;
  711. URLConnection connection = null;
  712. try {
  713. connection = resourceUrl.openConnection();
  714. lastModifiedTime = connection.getLastModified();
  715. // Remove milliseconds to avoid comparison problems (milliseconds
  716. // are not returned by the browser in the "If-Modified-Since"
  717. // header).
  718. lastModifiedTime = lastModifiedTime - lastModifiedTime % 1000;
  719. response.setDateHeader("Last-Modified", lastModifiedTime);
  720. if (browserHasNewestVersion(request, lastModifiedTime)) {
  721. response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
  722. return;
  723. }
  724. } catch (Exception e) {
  725. // Failed to find out last modified timestamp. Continue without it.
  726. getLogger()
  727. .log(Level.FINEST,
  728. "Failed to find out last modified timestamp. Continuing without it.",
  729. e);
  730. } finally {
  731. try {
  732. // Explicitly close the input stream to prevent it
  733. // from remaining hanging
  734. // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4257700
  735. InputStream is = connection.getInputStream();
  736. if (is != null) {
  737. is.close();
  738. }
  739. } catch (FileNotFoundException e) {
  740. // Not logging when the file does not exist.
  741. } catch (IOException e) {
  742. getLogger().log(Level.INFO,
  743. "Error closing URLConnection input stream", e);
  744. }
  745. }
  746. // Set type mime type if we can determine it based on the filename
  747. final String mimetype = sc.getMimeType(filename);
  748. if (mimetype != null) {
  749. response.setContentType(mimetype);
  750. }
  751. writeStaticResourceResponse(request, response, resourceUrl);
  752. }
  753. /**
  754. * Calculates the cache lifetime for the given filename in seconds. By
  755. * default filenames containing ".nocache." return 0, filenames containing
  756. * ".cache." return one year, all other return the value defined in the
  757. * web.xml using resourceCacheTime (defaults to 1 hour).
  758. *
  759. * @param filename
  760. * @return cache lifetime for the given filename in seconds
  761. */
  762. protected int getCacheTime(String filename) {
  763. /*
  764. * GWT conventions:
  765. *
  766. * - files containing .nocache. will not be cached.
  767. *
  768. * - files containing .cache. will be cached for one year.
  769. *
  770. * https://developers.google.com/web-toolkit/doc/latest/
  771. * DevGuideCompilingAndDebugging#perfect_caching
  772. */
  773. if (filename.contains(".nocache.")) {
  774. return 0;
  775. }
  776. if (filename.contains(".cache.")) {
  777. return 60 * 60 * 24 * 365;
  778. }
  779. /*
  780. * For all other files, the browser is allowed to cache for 1 hour
  781. * without checking if the file has changed. This forces browsers to
  782. * fetch a new version when the Vaadin version is updated. This will
  783. * cause more requests to the servlet than without this but for high
  784. * volume sites the static files should never be served through the
  785. * servlet.
  786. */
  787. return getService().getDeploymentConfiguration().getResourceCacheTime();
  788. }
  789. /**
  790. * Writes the contents of the given resourceUrl in the response. Can be
  791. * overridden to add/modify response headers and similar.
  792. *
  793. * @param request
  794. * The request for the resource
  795. * @param response
  796. * The response
  797. * @param resourceUrl
  798. * The url to send
  799. * @throws IOException
  800. */
  801. protected void writeStaticResourceResponse(HttpServletRequest request,
  802. HttpServletResponse response, URL resourceUrl) throws IOException {
  803. URLConnection connection = null;
  804. InputStream is = null;
  805. String urlStr = resourceUrl.toExternalForm();
  806. if (allowServePrecompressedResource(request, urlStr)) {
  807. // try to serve a precompressed version if available
  808. try {
  809. connection = new URL(urlStr + ".gz").openConnection();
  810. is = connection.getInputStream();
  811. // set gzip headers
  812. response.setHeader("Content-Encoding", "gzip");
  813. } catch (IOException e) {
  814. // NOP: will be still tried with non gzipped version
  815. } catch (Exception e) {
  816. getLogger().log(
  817. Level.FINE,
  818. "Unexpected exception looking for gzipped version of resource "
  819. + urlStr, e);
  820. }
  821. }
  822. if (is == null) {
  823. // precompressed resource not available, get non compressed
  824. connection = resourceUrl.openConnection();
  825. try {
  826. is = connection.getInputStream();
  827. } catch (FileNotFoundException e) {
  828. response.setStatus(HttpServletResponse.SC_NOT_FOUND);
  829. return;
  830. }
  831. }
  832. try {
  833. int length = connection.getContentLength();
  834. if (length >= 0) {
  835. response.setContentLength(length);
  836. }
  837. } catch (Throwable e) {
  838. // This can be ignored, content length header is not required.
  839. // Need to close the input stream because of
  840. // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4257700 to
  841. // prevent it from hanging, but that is done below.
  842. }
  843. try {
  844. streamContent(response, is);
  845. } finally {
  846. is.close();
  847. }
  848. }
  849. /**
  850. * Returns whether this servlet should attempt to serve a precompressed
  851. * version of the given static resource. If this method returns true, the
  852. * suffix {@code .gz} is appended to the URL and the corresponding resource
  853. * is served if it exists. It is assumed that the compression method used is
  854. * gzip. If this method returns false or a compressed version is not found,
  855. * the original URL is used.
  856. *
  857. * The base implementation of this method returns true if and only if the
  858. * request indicates that the client accepts gzip compressed responses and
  859. * the filename extension of the requested resource is .js, .css, or .html.
  860. *
  861. * @since 7.5.0
  862. *
  863. * @param request
  864. * the request for the resource
  865. * @param url
  866. * the URL of the requested resource
  867. * @return true if the servlet should attempt to serve a precompressed
  868. * version of the resource, false otherwise
  869. */
  870. protected boolean allowServePrecompressedResource(
  871. HttpServletRequest request, String url) {
  872. String accept = request.getHeader("Accept-Encoding");
  873. return accept != null
  874. && accept.contains("gzip")
  875. && (url.endsWith(".js") || url.endsWith(".css") || url
  876. .endsWith(".html"));
  877. }
  878. private void streamContent(HttpServletResponse response, InputStream is)
  879. throws IOException {
  880. final OutputStream os = response.getOutputStream();
  881. final byte buffer[] = new byte[DEFAULT_BUFFER_SIZE];
  882. int bytes;
  883. while ((bytes = is.read(buffer)) >= 0) {
  884. os.write(buffer, 0, bytes);
  885. }
  886. }
  887. private URL findResourceURL(String filename, ServletContext sc)
  888. throws MalformedURLException {
  889. URL resourceUrl = sc.getResource(filename);
  890. if (resourceUrl == null) {
  891. // try if requested file is found from classloader
  892. // strip leading "/" otherwise stream from JAR wont work
  893. if (filename.startsWith("/")) {
  894. filename = filename.substring(1);
  895. }
  896. resourceUrl = getService().getClassLoader().getResource(filename);
  897. }
  898. return resourceUrl;
  899. }
  900. private boolean serveOnTheFlyCompiledScss(String filename,
  901. HttpServletRequest request, HttpServletResponse response,
  902. ServletContext sc) throws IOException {
  903. if (!filename.endsWith(".css")) {
  904. return false;
  905. }
  906. String scssFilename = filename.substring(0, filename.length() - 4)
  907. + ".scss";
  908. URL scssUrl = findResourceURL(scssFilename, sc);
  909. if (scssUrl == null) {
  910. // Is a css request but no scss file was found
  911. return false;
  912. }
  913. // security check: do not permit navigation out of the VAADIN
  914. // directory
  915. if (!isAllowedVAADINResourceUrl(request, scssUrl)) {
  916. getLogger()
  917. .log(Level.INFO,
  918. "Requested resource [{0}] not accessible in the VAADIN directory or access to it is forbidden.",
  919. filename);
  920. response.setStatus(HttpServletResponse.SC_FORBIDDEN);
  921. // Handled, return true so no further processing is done
  922. return true;
  923. }
  924. if (getService().getDeploymentConfiguration().isProductionMode()) {
  925. // This is not meant for production mode.
  926. getLogger()
  927. .log(Level.INFO,
  928. "Request for {0} not handled by sass compiler while in production mode",
  929. filename);
  930. response.setStatus(HttpServletResponse.SC_NOT_FOUND);
  931. // Handled, return true so no further processing is done
  932. return true;
  933. }
  934. synchronized (SCSS_MUTEX) {
  935. ScssCacheEntry cacheEntry = scssCache.get(scssFilename);
  936. if (cacheEntry == null) {
  937. try {
  938. cacheEntry = loadPersistedScssCache(scssFilename, sc);
  939. } catch (Exception e) {
  940. getLogger().log(Level.WARNING,
  941. "Could not read persisted scss cache", e);
  942. }
  943. }
  944. if (cacheEntry == null || !cacheEntry.isStillValid()) {
  945. cacheEntry = compileScssOnTheFly(filename, scssFilename, sc);
  946. persistCacheEntry(cacheEntry);
  947. }
  948. scssCache.put(scssFilename, cacheEntry);
  949. if (cacheEntry == null) {
  950. // compilation did not produce any result, but logged a message
  951. return false;
  952. }
  953. // This is for development mode only so instruct the browser to
  954. // never cache it
  955. response.setHeader("Cache-Control", "no-cache");
  956. final String mimetype = getService().getMimeType(filename);
  957. writeResponse(response, mimetype, cacheEntry.getCss());
  958. return true;
  959. }
  960. }
  961. private ScssCacheEntry loadPersistedScssCache(String scssFilename,
  962. ServletContext sc) throws IOException {
  963. String realFilename = sc.getRealPath(scssFilename);
  964. File scssCacheFile = getScssCacheFile(new File(realFilename));
  965. if (!scssCacheFile.exists()) {
  966. return null;
  967. }
  968. String jsonString = Files.toString(scssCacheFile, Charsets.UTF_8);
  969. JsonObject entryJson = Json.parse(jsonString);
  970. String cacheVersion = entryJson.getString("version");
  971. if (!Version.getFullVersion().equals(cacheVersion)) {
  972. // Compiled for some other Vaadin version, discard cache
  973. scssCacheFile.delete();
  974. return null;
  975. }
  976. return new ScssCacheEntry(entryJson);
  977. }
  978. private ScssCacheEntry compileScssOnTheFly(String filename,
  979. String scssFilename, ServletContext sc) throws IOException {
  980. String realFilename = sc.getRealPath(scssFilename);
  981. ScssStylesheet scss = ScssStylesheet.get(realFilename);
  982. if (scss == null) {
  983. // Not a file in the file system (WebContent directory). Use the
  984. // identifier directly (VAADIN/themes/.../styles.css) so
  985. // ScssStylesheet will try using the class loader.
  986. if (scssFilename.startsWith("/")) {
  987. scssFilename = scssFilename.substring(1);
  988. }
  989. scss = ScssStylesheet.get(scssFilename);
  990. }
  991. if (scss == null) {
  992. getLogger()
  993. .log(Level.WARNING,
  994. "Scss file {0} exists but ScssStylesheet was not able to find it",
  995. scssFilename);
  996. return null;
  997. }
  998. try {
  999. getLogger().log(Level.FINE, "Compiling {0} for request to {1}",
  1000. new Object[] { realFilename, filename });
  1001. scss.compile();
  1002. } catch (Exception e) {
  1003. getLogger().log(Level.WARNING, "Scss compilation failed", e);
  1004. return null;
  1005. }
  1006. return new ScssCacheEntry(realFilename, scss.printState(),
  1007. scss.getSourceUris());
  1008. }
  1009. /**
  1010. * Check whether a URL obtained from a classloader refers to a valid static
  1011. * resource in the directory VAADIN.
  1012. *
  1013. * Warning: Overriding of this method is not recommended, but is possible to
  1014. * support non-default classloaders or servers that may produce URLs
  1015. * different from the normal ones. The method prototype may change in the
  1016. * future. Care should be taken not to expose class files or other resources
  1017. * outside the VAADIN directory if the method is overridden.
  1018. *
  1019. * @param request
  1020. * @param resourceUrl
  1021. * @return
  1022. *
  1023. * @since 6.6.7
  1024. *
  1025. * @deprecated As of 7.0. Will likely change or be removed in a future
  1026. * version
  1027. */
  1028. @Deprecated
  1029. protected boolean isAllowedVAADINResourceUrl(HttpServletRequest request,
  1030. URL resourceUrl) {
  1031. if ("jar".equals(resourceUrl.getProtocol())) {
  1032. // This branch is used for accessing resources directly from the
  1033. // Vaadin JAR in development environments and in similar cases.
  1034. // Inside a JAR, a ".." would mean a real directory named ".." so
  1035. // using it in paths should just result in the file not being found.
  1036. // However, performing a check in case some servers or class loaders
  1037. // try to normalize the path by collapsing ".." before the class
  1038. // loader sees it.
  1039. if (!resourceUrl.getPath().contains("!/VAADIN/")) {
  1040. getLogger()
  1041. .log(Level.INFO,
  1042. "Blocked attempt to access a JAR entry not starting with /VAADIN/: {0}",
  1043. resourceUrl);
  1044. return false;
  1045. }
  1046. getLogger().log(Level.FINE,
  1047. "Accepted access to a JAR entry using a class loader: {0}",
  1048. resourceUrl);
  1049. return true;
  1050. } else {
  1051. // Some servers such as GlassFish extract files from JARs (file:)
  1052. // and e.g. JBoss 5+ use protocols vsf: and vfsfile: .
  1053. // Check that the URL is in a VAADIN directory and does not contain
  1054. // "/../"
  1055. if (!resourceUrl.getPath().contains("/VAADIN/")
  1056. || resourceUrl.getPath().contains("/../")) {
  1057. getLogger().log(Level.INFO,
  1058. "Blocked attempt to access file: {0}", resourceUrl);
  1059. return false;
  1060. }
  1061. getLogger().log(Level.FINE,
  1062. "Accepted access to a file using a class loader: {0}",
  1063. resourceUrl);
  1064. return true;
  1065. }
  1066. }
  1067. /**
  1068. * Checks if the browser has an up to date cached version of requested
  1069. * resource. Currently the check is performed using the "If-Modified-Since"
  1070. * header. Could be expanded if needed.
  1071. *
  1072. * @param request
  1073. * The HttpServletRequest from the browser.
  1074. * @param resourceLastModifiedTimestamp
  1075. * The timestamp when the resource was last modified. 0 if the
  1076. * last modification time is unknown.
  1077. * @return true if the If-Modified-Since header tells the cached version in
  1078. * the browser is up to date, false otherwise
  1079. */
  1080. private boolean browserHasNewestVersion(HttpServletRequest request,
  1081. long resourceLastModifiedTimestamp) {
  1082. if (resourceLastModifiedTimestamp < 1) {
  1083. // We do not know when it was modified so the browser cannot have an
  1084. // up-to-date version
  1085. return false;
  1086. }
  1087. /*
  1088. * The browser can request the resource conditionally using an
  1089. * If-Modified-Since header. Check this against the last modification
  1090. * time.
  1091. */
  1092. try {
  1093. // If-Modified-Since represents the timestamp of the version cached
  1094. // in the browser
  1095. long headerIfModifiedSince = request
  1096. .getDateHeader("If-Modified-Since");
  1097. if (headerIfModifiedSince >= resourceLastModifiedTimestamp) {
  1098. // Browser has this an up-to-date version of the resource
  1099. return true;
  1100. }
  1101. } catch (Exception e) {
  1102. // Failed to parse header. Fail silently - the browser does not have
  1103. // an up-to-date version in its cache.
  1104. }
  1105. return false;
  1106. }
  1107. /**
  1108. *
  1109. * @author Vaadin Ltd
  1110. * @since 7.0
  1111. *
  1112. * @deprecated As of 7.0. This is no longer used and only provided for
  1113. * backwards compatibility. Each {@link RequestHandler} can
  1114. * individually decide whether it wants to handle a request or
  1115. * not.
  1116. */
  1117. @Deprecated
  1118. protected enum RequestType {
  1119. FILE_UPLOAD, BROWSER_DETAILS, UIDL, OTHER, STATIC_FILE, APP, PUBLISHED_FILE, HEARTBEAT;
  1120. }
  1121. /**
  1122. * @param request
  1123. * @return
  1124. *
  1125. * @deprecated As of 7.0. This is no longer used and only provided for
  1126. * backwards compatibility. Each {@link RequestHandler} can
  1127. * individually decide whether it wants to handle a request or
  1128. * not.
  1129. */
  1130. @Deprecated
  1131. protected RequestType getRequestType(VaadinServletRequest request) {
  1132. if (ServletPortletHelper.isFileUploadRequest(request)) {
  1133. return RequestType.FILE_UPLOAD;
  1134. } else if (ServletPortletHelper.isPublishedFileRequest(request)) {
  1135. return RequestType.PUBLISHED_FILE;
  1136. } else if (ServletUIInitHandler.isUIInitRequest(request)) {
  1137. return RequestType.BROWSER_DETAILS;
  1138. } else if (ServletPortletHelper.isUIDLRequest(request)) {
  1139. return RequestType.UIDL;
  1140. } else if (isStaticResourceRequest(request)) {
  1141. return RequestType.STATIC_FILE;
  1142. } else if (ServletPortletHelper.isAppRequest(request)) {
  1143. return RequestType.APP;
  1144. } else if (ServletPortletHelper.isHeartbeatRequest(request)) {
  1145. return RequestType.HEARTBEAT;
  1146. }
  1147. return RequestType.OTHER;
  1148. }
  1149. protected boolean isStaticResourceRequest(HttpServletRequest request) {
  1150. return request.getRequestURI().startsWith(
  1151. request.getContextPath() + "/VAADIN/");
  1152. }
  1153. /**
  1154. * Remove any heading or trailing "what" from the "string".
  1155. *
  1156. * @param string
  1157. * @param what
  1158. * @return
  1159. */
  1160. static String removeHeadingOrTrailing(String string, String what) {
  1161. while (string.startsWith(what)) {
  1162. string = string.substring(1);
  1163. }
  1164. while (string.endsWith(what)) {
  1165. string = string.substring(0, string.length() - 1);
  1166. }
  1167. return string;
  1168. }
  1169. /**
  1170. * Write a redirect response to the main page of the application.
  1171. *
  1172. * @param request
  1173. * @param response
  1174. * @throws IOException
  1175. * if sending the redirect fails due to an input/output error or
  1176. * a bad application URL
  1177. */
  1178. private void redirectToApplication(HttpServletRequest request,
  1179. HttpServletResponse response) throws IOException {
  1180. String applicationUrl = getApplicationUrl(request).toExternalForm();
  1181. response.sendRedirect(response.encodeRedirectURL(applicationUrl));
  1182. }
  1183. /**
  1184. * Gets the current application URL from request.
  1185. *
  1186. * @param request
  1187. * the HTTP request.
  1188. * @throws MalformedURLException
  1189. * if the application is denied access to the persistent data
  1190. * store represented by the given URL.
  1191. *
  1192. * @deprecated As of 7.0. Will likely change or be removed in a future
  1193. * version
  1194. */
  1195. @Deprecated
  1196. protected URL getApplicationUrl(HttpServletRequest request)
  1197. throws MalformedURLException {
  1198. final URL reqURL = new URL(
  1199. (request.isSecure() ? "https://" : "http://")
  1200. + request.getServerName()
  1201. + ((request.isSecure() && request.getServerPort() == 443)
  1202. || (!request.isSecure() && request
  1203. .getServerPort() == 80) ? "" : ":"
  1204. + request.getServerPort())
  1205. + request.getRequestURI());
  1206. String servletPath = "";
  1207. if (request.getAttribute("javax.servlet.include.servlet_path") != null) {
  1208. // this is an include request
  1209. servletPath = request.getAttribute(
  1210. "javax.servlet.include.context_path").toString()
  1211. + request
  1212. .getAttribute("javax.servlet.include.servlet_path");
  1213. } else {
  1214. servletPath = request.getContextPath() + request.getServletPath();
  1215. }
  1216. if (servletPath.length() == 0
  1217. || servletPath.charAt(servletPath.length() - 1) != '/') {
  1218. servletPath = servletPath + "/";
  1219. }
  1220. URL u = new URL(reqURL, servletPath);
  1221. return u;
  1222. }
  1223. /*
  1224. * (non-Javadoc)
  1225. *
  1226. * @see javax.servlet.GenericServlet#destroy()
  1227. */
  1228. @Override
  1229. public void destroy() {
  1230. super.destroy();
  1231. getService().destroy();
  1232. }
  1233. private static void persistCacheEntry(ScssCacheEntry cacheEntry) {
  1234. String scssFileName = cacheEntry.getScssFileName();
  1235. if (scssFileName == null) {
  1236. if (!scssCompileWarWarningEmitted) {
  1237. getLogger()
  1238. .warning(
  1239. "Could not persist scss cache because no real file was found for the compiled scss file. "
  1240. + "This might happen e.g. if serving the scss file directly from a .war file.");
  1241. scssCompileWarWarningEmitted = true;
  1242. }
  1243. return;
  1244. }
  1245. File scssFile = new File(scssFileName);
  1246. File cacheFile = getScssCacheFile(scssFile);
  1247. String cacheEntryJsonString = cacheEntry.asJson();
  1248. try {
  1249. Files.write(cacheEntryJsonString, cacheFile, Charsets.UTF_8);
  1250. } catch (IOException e) {
  1251. getLogger().log(Level.WARNING,
  1252. "Error persisting scss cache " + cacheFile, e);
  1253. }
  1254. }
  1255. private static File getScssCacheFile(File scssFile) {
  1256. return new File(scssFile.getParentFile(), scssFile.getName() + ".cache");
  1257. }
  1258. /**
  1259. * Escapes characters to html entities. An exception is made for some
  1260. * "safe characters" to keep the text somewhat readable.
  1261. *
  1262. * @param unsafe
  1263. * @return a safe string to be added inside an html tag
  1264. *
  1265. * @deprecated As of 7.0. Will likely change or be removed in a future
  1266. * version
  1267. */
  1268. @Deprecated
  1269. public static final String safeEscapeForHtml(String unsafe) {
  1270. if (null == unsafe) {
  1271. return null;
  1272. }
  1273. StringBuilder safe = new StringBuilder();
  1274. char[] charArray = unsafe.toCharArray();
  1275. for (int i = 0; i < charArray.length; i++) {
  1276. char c = charArray[i];
  1277. if (isSafe(c)) {
  1278. safe.append(c);
  1279. } else {
  1280. safe.append("&#");
  1281. safe.append((int) c);
  1282. safe.append(";");
  1283. }
  1284. }
  1285. return safe.toString();
  1286. }
  1287. private static boolean isSafe(char c) {
  1288. return //
  1289. c > 47 && c < 58 || // alphanum
  1290. c > 64 && c < 91 || // A-Z
  1291. c > 96 && c < 123 // a-z
  1292. ;
  1293. }
  1294. private static final Logger getLogger() {
  1295. return Logger.getLogger(VaadinServlet.class.getName());
  1296. }
  1297. }