1 /* 2 * Copyright (C) 2005-2008 Jive Software. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package org.jivesoftware.openfire.http; 17 18 import org.jivesoftware.util.*; 19 import org.jivesoftware.util.cache.Cache; 20 import org.jivesoftware.util.cache.CacheFactory; 21 import org.slf4j.Logger; 22 import org.slf4j.LoggerFactory; 23 24 import javax.servlet.http.HttpServlet; 25 import javax.servlet.http.HttpServletRequest; 26 import javax.servlet.http.HttpServletResponse; 27 import javax.servlet.ServletConfig; 28 import javax.servlet.ServletException; 29 import java.nio.charset.StandardCharsets; 30 import java.text.DateFormat; 31 import java.text.SimpleDateFormat; 32 import java.time.Duration; 33 import java.time.Instant; 34 import java.util.*; 35 import java.util.zip.GZIPOutputStream; 36 import java.io.*; 37 38 /** 39 * Combines and serves resources, such as javascript or css files. 40 */ 41 public class ResourceServlet extends HttpServlet { 42 43 private static final Logger Log = LoggerFactory.getLogger(ResourceServlet.class); 44 45 // private static String suffix = ""; // Set to "_src" to use source version 46 private static final Duration expiresOffset = Duration.ofDays(10); // This long until client cache expires 47 private boolean debug = false; 48 private boolean disableCompression = false; 49 private static final Cache<String, byte[]> cache = CacheFactory.createCache("Javascript Cache"); 50 51 @Override init(ServletConfig config)52 public void init(ServletConfig config) throws ServletException { 53 super.init(config); 54 55 debug = Boolean.parseBoolean(config.getInitParameter("debug")); 56 disableCompression = Boolean.parseBoolean(config.getInitParameter("disableCompression")); 57 } 58 59 @Override service(HttpServletRequest request, HttpServletResponse response)60 public void service(HttpServletRequest request, HttpServletResponse response) { 61 boolean compress = false; 62 63 boolean javascript = request.getRequestURI().endsWith("scripts/"); 64 65 if (!disableCompression) { 66 if (request.getHeader("accept-encoding") != null && 67 request.getHeader("accept-encoding").contains("gzip")) { 68 compress = true; 69 } 70 else if (request.getHeader("---------------") != null) { 71 // norton internet security 72 compress = true; 73 } 74 } 75 76 if(javascript) { 77 response.setHeader("Content-type", "text/javascript"); 78 } 79 else { 80 response.setHeader("Content-type", "text/css"); 81 } 82 response.setHeader("Vary", "Accept-Encoding"); // Handle proxies 83 84 if (!debug) { 85 DateFormat formatter = new SimpleDateFormat("d MMM yyyy HH:mm:ss 'GMT'", Locale.US); 86 formatter.setTimeZone(TimeZone.getTimeZone("GMT")); 87 response.setHeader("Expires", formatter.format(Instant.now().plus(expiresOffset))); 88 response.setHeader("Cache-Control", "max-age=" + expiresOffset.getSeconds()); // TODO: replace with 'toSeconds' after Openfire drops support for Java 8. 89 } 90 else { 91 response.setHeader("Expires", "1"); 92 compress = false; 93 } 94 95 try { 96 byte[] content; 97 String cacheKey = compress + " " + javascript; 98 content = cache.get(cacheKey); 99 if (javascript && (debug || content == null)) { 100 content = getJavaScriptContent(compress); 101 cache.put(cacheKey, content); 102 } 103 // else if(!javascript && content == null) { 104 // 105 // } 106 107 response.setContentLength(content == null ? 0 : content.length); 108 if (compress) { 109 response.setHeader("Content-Encoding", "gzip"); 110 } 111 112 // Write the content out 113 if (content != null) { 114 try (ByteArrayInputStream in = new ByteArrayInputStream(content)) { 115 try (OutputStream out = response.getOutputStream()) { 116 117 // Use a 128K buffer. 118 byte[] buf = new byte[128 * 1024]; 119 int len; 120 while ((len = in.read(buf)) != -1) { 121 out.write(buf, 0, len); 122 } 123 out.flush(); 124 } 125 } 126 } 127 } 128 catch (IOException e) { 129 Log.error(e.getMessage(), e); 130 } 131 } 132 getJavaScriptContent(boolean compress)133 private static byte[] getJavaScriptContent(boolean compress) throws IOException 134 { 135 StringWriter writer = new StringWriter(); 136 for(String file : getJavascriptFiles()) { 137 writer.write(getJavaScriptFile(file)); 138 } 139 140 if (compress) { 141 try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { 142 try (GZIPOutputStream gzos = new GZIPOutputStream(baos)) { 143 gzos.write(writer.toString().getBytes()); 144 gzos.finish(); 145 gzos.flush(); 146 return baos.toByteArray(); 147 } 148 } 149 } 150 else { 151 return writer.toString().getBytes(); 152 } 153 } 154 getJavascriptFiles()155 private static Collection<String> getJavascriptFiles() { 156 return Arrays.asList("prototype.js", "getelementsbyselector.js", "sarissa.js", 157 "connection.js", "yahoo-min.js", "dom-min.js", "event-min.js", "dragdrop-min.js", 158 "yui-ext.js", "spank.js"); 159 } 160 getJavaScriptFile(String path)161 private static String getJavaScriptFile(String path) { 162 StringBuilder sb = new StringBuilder(); 163 try (InputStream in = getResourceAsStream(path)) { 164 if (in == null) { 165 Log.error("Unable to find javascript file: '" + path + "' in classpath"); 166 return ""; 167 } 168 169 try (BufferedReader br = new BufferedReader(new InputStreamReader(in, StandardCharsets.ISO_8859_1))) { 170 String line; 171 while ((line = br.readLine()) != null) { 172 sb.append(line.trim()).append('\n'); 173 } 174 } 175 } catch (Exception e) { 176 Log.error("Error loading JavaScript file: '" + path + "'.", e); 177 } 178 179 return sb.toString(); 180 } 181 getResourceAsStream(String resourceName)182 private static InputStream getResourceAsStream(String resourceName) { 183 File file = new File(JiveGlobals.getHomeDirectory() + File.separator + 184 "resources" + File.separator + "spank" + File.separator + "scripts", resourceName); 185 try { 186 return new FileInputStream(file); 187 } 188 catch (FileNotFoundException e) { 189 return null; 190 } 191 } 192 } 193