001: package org.apache.velocity.tools.struts;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.util.Iterator;
023:
024: import javax.servlet.ServletContext;
025: import javax.servlet.http.HttpServletRequest;
026:
027: import org.apache.struts.action.SecurePlugInInterface;
028: import org.apache.struts.config.ModuleConfig;
029: import org.apache.struts.config.SecureActionConfig;
030: import org.apache.velocity.tools.view.tools.LinkTool;
031:
032: /**
033: * Tool to be able to use Struts SSL Extensions with Velocity.
034: * <p>It has the same interface as StrutsLinkTool and can function as a
035: * substitute if Struts 1.x and SSL Ext are installed. </p>
036: * <p>Usage:
037: * <pre>
038: * Template example:
039: * <!-- Use just like a regular StrutsLinkTool -->
040: * $link.action.nameOfAction
041: * $link.action.nameOfForward
042: *
043: * If the action or forward is marked as secure, or not,
044: * in your struts-config then the link will be rendered
045: * with https or http accordingly.
046: *
047: * Toolbox configuration:
048: * <tool>
049: * <key>link</key>
050: * <scope>request</scope>
051: * <class>org.apache.velocity.tools.struts.SecureLinkTool</class>
052: * </tool>
053: * </pre>
054: * </p>
055: * @since VelocityTools 1.1
056: * @author <a href="mailto:marinoj@centrum.is">Marino A. Jonsson</a>
057: * @version $Revision: 487322 $ $Date: 2006-12-14 11:49:10 -0800 (Thu, 14 Dec 2006) $
058: */
059: public class SecureLinkTool extends LinkTool {
060:
061: private static final String HTTP = "http";
062: private static final String HTTPS = "https";
063: private static final String STD_HTTP_PORT = "80";
064: private static final String STD_HTTPS_PORT = "443";
065:
066: /**
067: * <p>Returns a copy of the link with the given action name
068: * converted into a server-relative URI reference. This method
069: * does not check if the specified action really is defined.
070: * This method will overwrite any previous URI reference settings
071: * but will copy the query string.</p>
072: *
073: * @param action an action path as defined in struts-config.xml
074: *
075: * @return a new instance of StrutsLinkTool
076: */
077: public SecureLinkTool setAction(String action) {
078: String link = StrutsUtils.getActionMappingURL(application,
079: request, action);
080: return (SecureLinkTool) copyWith(computeURL(request,
081: application, link));
082: }
083:
084: /**
085: * <p>Returns a copy of the link with the given global forward name
086: * converted into a server-relative URI reference. If the parameter
087: * does not map to an existing global forward name, <code>null</code>
088: * is returned. This method will overwrite any previous URI reference
089: * settings but will copy the query string.</p>
090: *
091: * @param forward a global forward name as defined in struts-config.xml
092: *
093: * @return a new instance of StrutsLinkTool
094: */
095: public SecureLinkTool setForward(String forward) {
096: String url = StrutsUtils.getForwardURL(request, application,
097: forward);
098: if (url == null) {
099: return null;
100: }
101: return (SecureLinkTool) copyWith(url);
102: }
103:
104: /**
105: * Compute a hyperlink URL based on the specified action link.
106: * The returned URL will have already been passed to
107: * <code>response.encodeURL()</code> for adding a session identifier.
108: *
109: * @param request the current request.
110: * @param app the current ServletContext.
111: * @param link the action that is to be converted to a hyperlink URL
112: * @return the computed hyperlink URL
113: */
114: public String computeURL(HttpServletRequest request,
115: ServletContext app, String link) {
116: StringBuffer url = new StringBuffer(link);
117:
118: String contextPath = request.getContextPath();
119:
120: SecurePlugInInterface securePlugin = (SecurePlugInInterface) app
121: .getAttribute(SecurePlugInInterface.SECURE_PLUGIN);
122:
123: if (securePlugin.getSslExtEnable()
124: && url.toString().startsWith(contextPath)) {
125: // Initialize the scheme and ports we are using
126: String usingScheme = request.getScheme();
127: String usingPort = String.valueOf(request.getServerPort());
128:
129: // Get the servlet context relative link URL
130: String linkString = url.toString().substring(
131: contextPath.length());
132:
133: // See if link references an action somewhere in our app
134: SecureActionConfig secureConfig = getActionConfig(request,
135: app, linkString);
136:
137: // If link is an action, find the desired port and scheme
138: if (secureConfig != null
139: && !SecureActionConfig.ANY
140: .equalsIgnoreCase(secureConfig.getSecure())) {
141: String desiredScheme = Boolean.valueOf(
142: secureConfig.getSecure()).booleanValue() ? HTTPS
143: : HTTP;
144: String desiredPort = Boolean.valueOf(
145: secureConfig.getSecure()).booleanValue() ? securePlugin
146: .getHttpsPort()
147: : securePlugin.getHttpPort();
148:
149: // If scheme and port we are using do not match the ones we want
150: if (!desiredScheme.equals(usingScheme)
151: || !desiredPort.equals(usingPort)) {
152: url.insert(0, startNewUrlString(request,
153: desiredScheme, desiredPort));
154:
155: // This is a hack to help us overcome the problem that some
156: // older browsers do not share sessions between http & https
157: // If this feature is diabled, session ID could still be added
158: // the previous call to the RequestUtils.computeURL() method,
159: // but only if needed due to cookies disabled, etc.
160: if (securePlugin.getSslExtAddSession()
161: && url.toString().indexOf(";jsessionid=") < 0) {
162: // Add the session identifier
163: url = new StringBuffer(toEncoded(
164: url.toString(), request.getSession()
165: .getId()));
166: }
167: }
168: }
169: }
170: return url.toString();
171: }
172:
173: /**
174: * Finds the configuration definition for the specified action link
175: *
176: * @param request the current request.
177: * @param app the current ServletContext.
178: * @param linkString The action we are searching for, specified as a
179: * link. (i.e. may include "..")
180: * @return The SecureActionConfig object entry for this action,
181: * or null if not found
182: */
183: private static SecureActionConfig getActionConfig(
184: HttpServletRequest request, ServletContext app,
185: String linkString) {
186: ModuleConfig moduleConfig = StrutsUtils.selectModule(
187: linkString, app);
188:
189: // Strip off the module path, if any
190: linkString = linkString.substring(moduleConfig.getPrefix()
191: .length());
192:
193: // Use our servlet mapping, if one is specified
194: //String servletMapping = (String)app.getAttribute(Globals.SERVLET_KEY);
195:
196: SecurePlugInInterface spi = (SecurePlugInInterface) app
197: .getAttribute(SecurePlugInInterface.SECURE_PLUGIN);
198: Iterator mappingItr = spi.getServletMappings().iterator();
199: while (mappingItr.hasNext()) {
200: String servletMapping = (String) mappingItr.next();
201:
202: int starIndex = servletMapping != null ? servletMapping
203: .indexOf('*') : -1;
204: if (starIndex == -1) {
205: continue;
206: } // No servlet mapping or no usable pattern defined, short circuit
207:
208: String prefix = servletMapping.substring(0, starIndex);
209: String suffix = servletMapping.substring(starIndex + 1);
210:
211: // Strip off the jsessionid, if any
212: int jsession = linkString.indexOf(";jsessionid=");
213: if (jsession >= 0) {
214: linkString = linkString.substring(0, jsession);
215: }
216:
217: // Strip off the query string, if any
218: // (differs from the SSL Ext. version - query string before anchor)
219: int question = linkString.indexOf("?");
220: if (question >= 0) {
221: linkString = linkString.substring(0, question);
222: }
223:
224: // Strip off the anchor, if any
225: int anchor = linkString.indexOf("#");
226: if (anchor >= 0) {
227: linkString = linkString.substring(0, anchor);
228: }
229:
230: // Unable to establish this link as an action, short circuit
231: if (!(linkString.startsWith(prefix) && linkString
232: .endsWith(suffix))) {
233: continue;
234: }
235:
236: // Chop off prefix and suffix
237: linkString = linkString.substring(prefix.length());
238: linkString = linkString.substring(0, linkString.length()
239: - suffix.length());
240: if (!linkString.startsWith("/")) {
241: linkString = "/" + linkString;
242: }
243:
244: SecureActionConfig secureConfig = (SecureActionConfig) moduleConfig
245: .findActionConfig(linkString);
246:
247: return secureConfig;
248: }
249: return null;
250:
251: }
252:
253: /**
254: * Builds the protocol, server name, and port portion of the new URL
255: * @param request The current request
256: * @param desiredScheme The scheme (http or https) to be used in the new URL
257: * @param desiredPort The port number to be used in th enew URL
258: * @return The new URL as a StringBuffer
259: */
260: private static StringBuffer startNewUrlString(
261: HttpServletRequest request, String desiredScheme,
262: String desiredPort) {
263: StringBuffer url = new StringBuffer();
264: String serverName = request.getServerName();
265: url.append(desiredScheme).append("://").append(serverName);
266:
267: if ((HTTP.equals(desiredScheme) && !STD_HTTP_PORT
268: .equals(desiredPort))
269: || (HTTPS.equals(desiredScheme) && !STD_HTTPS_PORT
270: .equals(desiredPort))) {
271: url.append(":").append(desiredPort);
272: }
273: return url;
274: }
275:
276: /**
277: * Return the specified URL with the specified session identifier
278: * suitably encoded.
279: *
280: * @param url URL to be encoded with the session id
281: * @param sessionId Session id to be included in the encoded URL
282: * @return the specified URL with the specified session identifier suitably encoded
283: */
284: public String toEncoded(String url, String sessionId) {
285: if (url == null || sessionId == null) {
286: return (url);
287: }
288:
289: String path = url;
290: String query = "";
291: String anchor = "";
292:
293: // (differs from the SSL Ext. version - anchor before query string)
294: int pound = url.indexOf('#');
295: if (pound >= 0) {
296: path = url.substring(0, pound);
297: anchor = url.substring(pound);
298: }
299: int question = path.indexOf('?');
300: if (question >= 0) {
301: query = path.substring(question);
302: path = path.substring(0, question);
303: }
304: StringBuffer sb = new StringBuffer(path);
305: // jsessionid can't be first.
306: if (sb.length() > 0) {
307: sb.append(";jsessionid=");
308: sb.append(sessionId);
309: }
310: sb.append(query);
311: sb.append(anchor);
312: return sb.toString();
313: }
314:
315: }
|