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