選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

VaadinServlet.java 55KB

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