mirror of
https://github.com/apache/struts.git
synced 2026-08-08 08:07:17 +00:00
Compare commits
191 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dbcca3512e | |||
| 6ec2bdc197 | |||
| 9b9aa10059 | |||
| 6c216de38e | |||
| a89b7c11af | |||
| 918182344c | |||
| 52e8d46364 | |||
| c59f294fb9 | |||
| 86e5bd7f52 | |||
| bea6fb599d | |||
| 4a3917176d | |||
| a746fc872a | |||
| 949e069f53 | |||
| 4db5cdc473 | |||
| afc8f20440 | |||
| 75d7bf481c | |||
| d86539ed07 | |||
| f0b3a1d213 | |||
| 2306f5f7fa | |||
| ed7f9dd4cb | |||
| cf53b59647 | |||
| 6dd6e5cfb7 | |||
| b9a0c497d3 | |||
| 2f95b9a130 | |||
| 103e3b174e | |||
| 2776b34b44 | |||
| 287e3bc8cb | |||
| 677ccebe83 | |||
| f11ac901d3 | |||
| 3fddfb6eb5 | |||
| 744c1f409d | |||
| 54a4dce7bf | |||
| 16eb7fa768 | |||
| 631ce98d17 | |||
| 086b637355 | |||
| ae56301979 | |||
| 73da12e723 | |||
| 583da3d5df | |||
| 9e8627ca66 | |||
| c349dfa5ab | |||
| 4d888a5ab6 | |||
| 1ed29d508f | |||
| 3523064939 | |||
| 54dff362f5 | |||
| b12864dd38 | |||
| de90290354 | |||
| ba0563183b | |||
| b348fc552c | |||
| 81371e09c5 | |||
| 431e50e922 | |||
| 156db8e5df | |||
| 6f5ddca471 | |||
| cc0d52f216 | |||
| 23e0181328 | |||
| 030ffa3354 | |||
| 8e67b9144a | |||
| 77017cd677 | |||
| 7d8c3598ef | |||
| 83bb64f4c2 | |||
| ae2840f183 | |||
| e8b48f8bb4 | |||
| 3243448084 | |||
| 5c61c9a575 | |||
| e40dcaa3f4 | |||
| ab6acd2382 | |||
| bf0a01d390 | |||
| 00c47b7ce6 | |||
| c84810ff82 | |||
| 2ea59b31b4 | |||
| fae1e0e799 | |||
| e4ce3ce839 | |||
| 730441b7a7 | |||
| bb22c585b5 | |||
| eccc31ebce | |||
| 1c92a8b2c9 | |||
| b28b78c062 | |||
| 8d172b8e9b | |||
| 88b885339a | |||
| 3f526d630a | |||
| 34025eafec | |||
| 54e8bf1f75 | |||
| 9f2cae2f1e | |||
| 9bcd1bc9f6 | |||
| f052398cd2 | |||
| 237432512d | |||
| ca25476120 | |||
| 731fde544c | |||
| 76eb8f38a3 | |||
| deefeffd11 | |||
| 98d2692e43 | |||
| 46517afb14 | |||
| 5190b53673 | |||
| d36f31b3e1 | |||
| cbae13a6a8 | |||
| 865f1f2784 | |||
| 5ce807aaea | |||
| 17dab897d9 | |||
| 45fa8d0320 | |||
| 6fee2eb1a4 | |||
| 0ac8932aa3 | |||
| 6b497ef8f7 | |||
| 519c767114 | |||
| 8a59ed02c9 | |||
| f48c30eaa7 | |||
| 94c4462658 | |||
| 013077abcb | |||
| fbb91d1bb9 | |||
| a67ac45258 | |||
| 8ca0f2fc46 | |||
| 37842193ab | |||
| 609cb0bb97 | |||
| b155532314 | |||
| 9de0cf4c8b | |||
| 5cf4aacd50 | |||
| d82289a1be | |||
| 6ac2a58da7 | |||
| c714d2674a | |||
| ffa6d5cb5b | |||
| 9860ef829a | |||
| d028886cd1 | |||
| 1b4868df3e | |||
| 5cfe05075b | |||
| 5efc595011 | |||
| 9c7b833668 | |||
| 87979d2821 | |||
| 8a9e01727c | |||
| 5b2b244a66 | |||
| 473403ca17 | |||
| 1bbcd4bf70 | |||
| 469b25f8e1 | |||
| 27260685e1 | |||
| bd5e64e598 | |||
| 9a8f36dee2 | |||
| a6345e79c9 | |||
| 85373951b7 | |||
| c44566bd24 | |||
| 2a6acabe5d | |||
| 377c6cdda0 | |||
| 6438396ef8 | |||
| 69bc2947c1 | |||
| 15c79e1398 | |||
| dd36946573 | |||
| 18bdc141ea | |||
| 2ec3890dc7 | |||
| 10a612c9da | |||
| 1d160f4d5f | |||
| a398793177 | |||
| ff4cdd9670 | |||
| 7c4bb7e7bd | |||
| 5421930b49 | |||
| c6750c110c | |||
| 997966829a | |||
| bd1a121b82 | |||
| 6177cf3387 | |||
| b448d79959 | |||
| 9a0a0a16e9 | |||
| bf46145baf | |||
| 1158b0adda | |||
| 5ed7b36567 | |||
| 2d70ea2f7d | |||
| 9136521d27 | |||
| 5be1590a60 | |||
| f721d5490c | |||
| db318c2116 | |||
| f295852482 | |||
| 8bbfb461c4 | |||
| 38e8f86a75 | |||
| 696e566279 | |||
| 27aa7e1e27 | |||
| f19e4be947 | |||
| 913944b8ee | |||
| ddeb7f0f7c | |||
| 5d30eb1ad2 | |||
| efcea81b67 | |||
| 9862580bbe | |||
| 1dc545e34c | |||
| 90dbf54bf4 | |||
| b4a18d07e4 | |||
| 5d26c6c1e5 | |||
| 22cb8c7e7d | |||
| 0958077762 | |||
| 7848534e2f | |||
| c708876339 | |||
| f65e38bb81 | |||
| 412684bd22 | |||
| 3e2d5da12c | |||
| d15424ee5f | |||
| 30d30e07de | |||
| 755ad3fadb | |||
| f5d455da12 | |||
| 4f817dc7a9 |
+1
-1
@@ -26,7 +26,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-apps</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-blank</artifactId>
|
||||
|
||||
@@ -25,7 +25,6 @@ import org.apache.struts2.interceptor.validation.SkipValidation;
|
||||
|
||||
public class Login extends ExampleSupport {
|
||||
|
||||
@Override
|
||||
public String execute() throws Exception {
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-apps</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-jboss-blank</artifactId>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-apps</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-mailreader</artifactId>
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.35</version>
|
||||
</parent>
|
||||
<artifactId>struts2-apps</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-apps</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-portlet</artifactId>
|
||||
|
||||
@@ -5,11 +5,11 @@ Here you'll find examples of what is possible with the Struts Portlet integratio
|
||||
<ul>
|
||||
<li><a href="<s:url action="formExample"/>">A simple form</a></li>
|
||||
<li><a href="<s:url action="formExamplePrg" method="input"/>">Form example with proper PRG</a></li>
|
||||
<li><a href="<s:url action="formExampleModelDriven" method="input"/>">Model driven example</li>
|
||||
<li><a href="<s:url action="formExampleModelDriven" method="input"/>">Model driven example</a>/li>
|
||||
<li><a href="<s:url action="validationExample"/>">Validation</a></li>
|
||||
<li><a href="<s:url action="tokenExample"/>">Token</a></li>
|
||||
<li><a href="<s:url action="springExample"/>">Spring integration</a></li>
|
||||
<li><a href="<s:url action="fileUpload" method="input"/>">File upload</li>
|
||||
<li><a href="<s:url action="fileUpload" method="input"/>">File upload</a></li>
|
||||
<li><a href="<s:url action="freeMarkerExample"/>">FreeMarker</a></li>
|
||||
<li><a href="<s:url action="velocityHelloWorld"/>">Velocity</a></li>
|
||||
<li><a href="<s:url action="index" namespace="/tiles"/>">Form Example Validation with Tiles</a></li>
|
||||
|
||||
@@ -26,12 +26,12 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-apps</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-rest-showcase</artifactId>
|
||||
<packaging>war</packaging>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.35</version>
|
||||
<name>Struts 2 Rest Showcase Webapp</name>
|
||||
<description>Struts 2 Rest Showcase Example</description>
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.demo.rest.example;
|
||||
|
||||
import org.apache.struts2.convention.annotation.Action;
|
||||
import org.apache.struts2.convention.annotation.Result;
|
||||
import org.apache.struts2.convention.annotation.Results;
|
||||
|
||||
@Results({
|
||||
@Result(name="success", type="redirectAction", params = {"actionName" , "orders"})
|
||||
})
|
||||
public class IndexController {
|
||||
|
||||
@Action("/")
|
||||
public String index() {
|
||||
return "success";
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
|
||||
<% response.sendRedirect("orders"); %>
|
||||
@@ -26,7 +26,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-apps</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-showcase</artifactId>
|
||||
|
||||
+2
-1
@@ -36,7 +36,8 @@ public class SaveGangsterAction extends Action {
|
||||
// Some code to save the gangster to the db as necessary
|
||||
GangsterForm gform = (GangsterForm) form;
|
||||
ActionMessages messages = new ActionMessages();
|
||||
messages.add("msg", new ActionMessage("Gangster " + gform.getName() + " added successfully"));
|
||||
System.out.println(gform.getName());
|
||||
messages.add("msg", new ActionMessage("struts1.gangsterAdded", gform.getName()));
|
||||
addMessages(request, messages);
|
||||
|
||||
return mapping.findForward("success");
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.struts2.showcase.tiles;
|
||||
|
||||
import org.apache.struts2.convention.annotation.Namespace;
|
||||
import org.apache.struts2.convention.annotation.ParentPackage;
|
||||
import org.apache.struts2.convention.annotation.Result;
|
||||
import org.apache.struts2.tiles.annotation.TilesDefinition;
|
||||
import org.apache.struts2.tiles.annotation.TilesPutAttribute;
|
||||
|
||||
import com.opensymphony.xwork2.ActionSupport;
|
||||
|
||||
@Namespace("/tiles")
|
||||
@ParentPackage("tiles")
|
||||
@Result(name = "success", type="tiles")
|
||||
@TilesDefinition(extend = "showcase.annotations", putAttributes = {
|
||||
@TilesPutAttribute(name = "header", value = "/WEB-INF/tiles/header.jsp"),
|
||||
@TilesPutAttribute(name = "body", value = "/WEB-INF/tiles/body.ftl")
|
||||
})
|
||||
public class TilesAnnotationsAction extends ActionSupport {
|
||||
|
||||
private static final long serialVersionUID = 2900509995064928866L;
|
||||
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
package org.apache.struts2.showcase.validation;
|
||||
|
||||
import java.sql.Date;
|
||||
|
||||
import com.opensymphony.xwork2.validator.annotations.DateRangeFieldValidator;
|
||||
import com.opensymphony.xwork2.validator.annotations.EmailValidator;
|
||||
import com.opensymphony.xwork2.validator.annotations.FieldExpressionValidator;
|
||||
import com.opensymphony.xwork2.validator.annotations.IntRangeFieldValidator;
|
||||
import com.opensymphony.xwork2.validator.annotations.RegexFieldValidator;
|
||||
import com.opensymphony.xwork2.validator.annotations.RequiredFieldValidator;
|
||||
import com.opensymphony.xwork2.validator.annotations.RequiredStringValidator;
|
||||
import com.opensymphony.xwork2.validator.annotations.StringLengthFieldValidator;
|
||||
import com.opensymphony.xwork2.validator.annotations.UrlValidator;
|
||||
|
||||
/**
|
||||
* <!-- START SNIPPET: ajaxFormSubmit -->
|
||||
*/
|
||||
/**
|
||||
* Example Action that shows how forms can be validated and submitted via AJAX
|
||||
* only. Form-submit-and-page-reload functionality of browsers is not used for
|
||||
* this action.
|
||||
* <p>Some things to note:
|
||||
* <ul>
|
||||
* <li>Depends on <code>json-plugin</code>.</li>
|
||||
* <li>Requires <code>jsonValidationInterceptor</code> to be on stack.</li>
|
||||
* <li>Uses result type <code>jsonActionRedirect</code>.</li>
|
||||
* <li>Uses http parameters <code>struts.enableJSONValidation=true</code> and <code>struts.validateOnly=false</code>.</li>
|
||||
* <li>Uses a customized theme to make sure html elements required as error containers are always present and easily selectable in JS.</li>
|
||||
* <li>Uses some custom JS code depending on jQuery to issue AJAX request and to render errors in html.</li>
|
||||
* <li>Shows visual feedback while waiting for AJAX response.</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
public class AjaxFormSubmitAction extends AbstractValidationActionSupport {
|
||||
|
||||
private String requiredValidatorField = null;
|
||||
private String requiredStringValidatorField = null;
|
||||
private Integer integerValidatorField = null;
|
||||
private Date dateValidatorField = null;
|
||||
private String emailValidatorField = null;
|
||||
private String urlValidatorField = null;
|
||||
private String stringLengthValidatorField = null;
|
||||
private String regexValidatorField = null;
|
||||
private String fieldExpressionValidatorField = null;
|
||||
|
||||
@Override
|
||||
public void validate() {
|
||||
if (hasFieldErrors()) {
|
||||
addActionError("Errors present!");
|
||||
}
|
||||
}
|
||||
|
||||
public Date getDateValidatorField() {
|
||||
return dateValidatorField;
|
||||
}
|
||||
|
||||
@DateRangeFieldValidator(
|
||||
min="01/01/1990",
|
||||
max="01/01/2000",
|
||||
message="must be a min 01-01-1990 max 01-01-2000 if supplied")
|
||||
public void setDateValidatorField(Date dateValidatorField) {
|
||||
this.dateValidatorField = dateValidatorField;
|
||||
}
|
||||
|
||||
public String getEmailValidatorField() {
|
||||
return emailValidatorField;
|
||||
}
|
||||
|
||||
@EmailValidator(message="must be a valid email if supplied")
|
||||
public void setEmailValidatorField(String emailValidatorField) {
|
||||
this.emailValidatorField = emailValidatorField;
|
||||
}
|
||||
|
||||
public Integer getIntegerValidatorField() {
|
||||
return integerValidatorField;
|
||||
}
|
||||
|
||||
@IntRangeFieldValidator(min="1", max="10", message="must be integer min 1 max 10 if supplied")
|
||||
public void setIntegerValidatorField(Integer integerValidatorField) {
|
||||
this.integerValidatorField = integerValidatorField;
|
||||
}
|
||||
|
||||
public String getRegexValidatorField() {
|
||||
return regexValidatorField;
|
||||
}
|
||||
|
||||
@RegexFieldValidator(
|
||||
regex="[^<>]+",
|
||||
message="regexValidatorField must match a regexp (.*\\.txt) if specified")
|
||||
public void setRegexValidatorField(String regexValidatorField) {
|
||||
this.regexValidatorField = regexValidatorField;
|
||||
}
|
||||
|
||||
public String getRequiredStringValidatorField() {
|
||||
return requiredStringValidatorField;
|
||||
}
|
||||
|
||||
@RequiredStringValidator(trim=true, message="required and must be string")
|
||||
public void setRequiredStringValidatorField(String requiredStringValidatorField) {
|
||||
this.requiredStringValidatorField = requiredStringValidatorField;
|
||||
}
|
||||
|
||||
public String getRequiredValidatorField() {
|
||||
return requiredValidatorField;
|
||||
}
|
||||
|
||||
@RequiredFieldValidator(message="required")
|
||||
public void setRequiredValidatorField(String requiredValidatorField) {
|
||||
this.requiredValidatorField = requiredValidatorField;
|
||||
}
|
||||
|
||||
public String getStringLengthValidatorField() {
|
||||
return stringLengthValidatorField;
|
||||
}
|
||||
|
||||
@StringLengthFieldValidator(
|
||||
minLength="2",
|
||||
maxLength="4",
|
||||
trim=true,
|
||||
message="must be a String of a specific greater than 1 less than 5 if specified")
|
||||
public void setStringLengthValidatorField(String stringLengthValidatorField) {
|
||||
this.stringLengthValidatorField = stringLengthValidatorField;
|
||||
}
|
||||
|
||||
public String getFieldExpressionValidatorField() {
|
||||
return fieldExpressionValidatorField;
|
||||
}
|
||||
|
||||
@FieldExpressionValidator(
|
||||
expression = "(fieldExpressionValidatorField == requiredValidatorField)",
|
||||
message = "must be the same as the Required Validator Field if specified")
|
||||
public void setFieldExpressionValidatorField(
|
||||
String fieldExpressionValidatorField) {
|
||||
this.fieldExpressionValidatorField = fieldExpressionValidatorField;
|
||||
}
|
||||
|
||||
public String getUrlValidatorField() {
|
||||
return urlValidatorField;
|
||||
}
|
||||
|
||||
@UrlValidator(message="must be a valid url if supplied")
|
||||
public void setUrlValidatorField(String urlValidatorField) {
|
||||
this.urlValidatorField = urlValidatorField;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <!-- END SNIPPET: ajaxFormSubmit -->
|
||||
*/
|
||||
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package org.apache.struts2.showcase.validation;
|
||||
|
||||
public class AjaxFormSubmitSuccessAction {
|
||||
public String execute() {
|
||||
return "success";
|
||||
}
|
||||
}
|
||||
@@ -7,3 +7,5 @@ item.create=Create {0}
|
||||
item.list={0} List
|
||||
|
||||
token.transfer.time=The bank transfer was executed at {0,date,HH:mm:ss MM-dd-yyyy}
|
||||
|
||||
struts1.gangsterAdded=Gangster {0} added successfully
|
||||
@@ -5,3 +5,5 @@ item.create={0} neu anlegen
|
||||
item.list={0}-Liste
|
||||
|
||||
token.transfer.time=Die \u00dcberweisung wurde am {0,date,HH:mm:ss MM-dd-yyyy} durchgef\u00fchrt
|
||||
|
||||
struts1.gangsterAdded=Gangster {0} wurde erfolgreich hinzugefugt
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
struts1.gangsterAdded=Gangster {0} anadido con exito
|
||||
@@ -20,6 +20,11 @@
|
||||
<result type="tiles">showcase.freemarkerLayout</result>
|
||||
</action>
|
||||
|
||||
<action name="tiles-annotations" class="org.apache.struts2.showcase.tiles.TilesAnnotationsAction">
|
||||
<!-- note: this is not necessary when using convention-plugin -->
|
||||
<result type="tiles"></result>
|
||||
</action>
|
||||
|
||||
<action name="sanity">
|
||||
<result type="redirect">/WEB-INF/tiles/layout.jsp</result>
|
||||
<result type="redirect" name="success">/tiles/layout.jsp</result>
|
||||
|
||||
@@ -29,6 +29,19 @@
|
||||
<result name="input">quiz-ajax.jsp</result>
|
||||
<result>quiz-success.jsp</result>
|
||||
</action>
|
||||
|
||||
<!-- =========================================== -->
|
||||
<!-- === ajax form submit === -->
|
||||
<!-- =========================================== -->
|
||||
<action name="ajaxFormSubmit" class="org.apache.struts2.showcase.validation.AjaxFormSubmitAction">
|
||||
<interceptor-ref name="jsonValidationWorkflowStack" />
|
||||
<result name="input">/WEB-INF/validation/ajaxFormSubmit.jsp</result>
|
||||
<result type="jsonActionRedirect">ajaxFormSubmitSuccess</result>
|
||||
</action>
|
||||
<action name="ajaxFormSubmitSuccess" class="org.apache.struts2.showcase.validation.AjaxFormSubmitSuccessAction">
|
||||
<result>/WEB-INF/validation/ajaxFormSubmitSuccess.jsp</result>
|
||||
</action>
|
||||
|
||||
</package>
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<#--
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
-->
|
||||
<#--
|
||||
Make sure element is always present. To be filled later via JS.
|
||||
-->
|
||||
<ul<#rt/>
|
||||
<#if parameters.id??>
|
||||
id="${parameters.id?html}"<#rt/>
|
||||
</#if>
|
||||
<#if parameters.cssClass??>
|
||||
class="${parameters.cssClass?html}"<#rt/>
|
||||
<#else>
|
||||
class="errorMessage"<#rt/>
|
||||
</#if>
|
||||
<#if parameters.cssStyle??>
|
||||
style="${parameters.cssStyle?html}"<#rt/>
|
||||
</#if>
|
||||
>
|
||||
<#if (actionErrors?? && actionErrors?size > 0)>
|
||||
<#list actionErrors as error>
|
||||
<#if error??>
|
||||
<li><span><#if parameters.escape>${error!?html}<#else>${error!}</#if></span><#rt/></li><#rt/>
|
||||
</#if>
|
||||
</#list>
|
||||
</#if>
|
||||
</ul>
|
||||
@@ -0,0 +1,39 @@
|
||||
<#--
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
-->
|
||||
${parameters.after!}<#t/>
|
||||
</td><#lt/>
|
||||
</tr>
|
||||
<#if (parameters.errorposition!"top") == 'bottom'>
|
||||
<#assign hasFieldErrors = parameters.name?? && fieldErrors?? && fieldErrors[parameters.name]??/>
|
||||
<#if hasFieldErrors>
|
||||
<tr errorFor="${parameters.id}">
|
||||
<td class="tdErrorMessage" colspan="2"><#rt/>
|
||||
<#if hasFieldErrors>
|
||||
<#list fieldErrors[parameters.name] as error>
|
||||
<div class="errorMessage">${error?html}</div><#t/>
|
||||
</#list>
|
||||
</#if>
|
||||
</td><#lt/>
|
||||
</tr>
|
||||
</#if>
|
||||
</#if>
|
||||
@@ -0,0 +1,80 @@
|
||||
<#--
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
-->
|
||||
<#--
|
||||
Always include elements to show errors. They may be filled later via AJAX.
|
||||
-->
|
||||
<#assign hasFieldErrors = parameters.name?? && fieldErrors?? && fieldErrors[parameters.name]??/>
|
||||
<#if (parameters.errorposition!"top") == 'top'>
|
||||
<tr errorFor="${parameters.id}">
|
||||
<td class="tdErrorMessage" colspan="2" data-error-for-fieldname="${parameters.name}"><#rt/>
|
||||
<#if hasFieldErrors>
|
||||
<#list fieldErrors[parameters.name] as error>
|
||||
<div class="errorMessage">${error?html}</div><#t/>
|
||||
</#list>
|
||||
</#if>
|
||||
</td><#lt/>
|
||||
</tr>
|
||||
</#if>
|
||||
<#if !parameters.labelposition?? && (parameters.form.labelposition)??>
|
||||
<#assign labelpos = parameters.form.labelposition/>
|
||||
<#elseif parameters.labelposition??>
|
||||
<#assign labelpos = parameters.labelposition/>
|
||||
</#if>
|
||||
<#--
|
||||
if the label position is top,
|
||||
then give the label it's own row in the table
|
||||
-->
|
||||
<tr>
|
||||
<#if (labelpos!"") == 'top'>
|
||||
<td class="tdLabelTop" colspan="2"><#rt/>
|
||||
<#else>
|
||||
<td class="tdLabel"><#rt/>
|
||||
</#if>
|
||||
<#if parameters.label??>
|
||||
<label <#t/>
|
||||
<#if parameters.id??>
|
||||
for="${parameters.id?html}" <#t/>
|
||||
</#if>
|
||||
<#if hasFieldErrors>
|
||||
class="errorLabel"<#t/>
|
||||
<#else>
|
||||
class="label"<#t/>
|
||||
</#if>
|
||||
><#t/>
|
||||
<#if parameters.required!false && parameters.requiredPosition!"right" != 'right'>
|
||||
<span class="required">*</span><#t/>
|
||||
</#if>
|
||||
${parameters.label?html}<#t/>
|
||||
<#if parameters.required!false && parameters.requiredPosition!"right" == 'right'>
|
||||
<span class="required">*</span><#t/>
|
||||
</#if>
|
||||
${parameters.labelseparator!":"?html}<#t/>
|
||||
<#include "/${parameters.templateDir}/${parameters.expandTheme}/tooltip.ftl" />
|
||||
</label><#t/>
|
||||
</#if>
|
||||
</td><#lt/>
|
||||
<#-- add the extra row -->
|
||||
<#if (labelpos!"") == 'top'>
|
||||
</tr>
|
||||
<tr>
|
||||
</#if>
|
||||
@@ -0,0 +1,21 @@
|
||||
#
|
||||
# $Id$
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
parent = xhtml
|
||||
@@ -246,7 +246,6 @@
|
||||
</li>
|
||||
<li><s:a value="/interactive/index.action">Interactive Demo</s:a></li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav pull-right">
|
||||
<li class="dropdown last">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="icon-flag"></i> Help<b
|
||||
|
||||
@@ -45,4 +45,10 @@
|
||||
<put-attribute name="body" value="/WEB-INF/tiles/body.ftl"/>
|
||||
</definition>
|
||||
|
||||
<definition name="showcase.annotations" template="/WEB-INF/tiles/layout-annotations.jsp">
|
||||
<put-attribute name="title" value="Tiles Annotations Showcase"/>
|
||||
<put-attribute name="header" value=".header"/>
|
||||
<put-attribute name="body" value=".body"/>
|
||||
</definition>
|
||||
|
||||
</tiles-definitions>
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
<li>
|
||||
<a href="freemarkerLayout.action">View Example with a FreeMarker Layout</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="tiles-annotations.action">View Example with tiles configuration by annotating action</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
|
||||
<%@ taglib prefix="s" uri="/struts-tags" %>
|
||||
|
||||
<%-- Show usage; Used in Header --%>
|
||||
<tiles:importAttribute name="title" scope="request"/>
|
||||
<html>
|
||||
<head><title>Struts2 Showcase - <tiles:getAsString name="title"/></title></head>
|
||||
<body>
|
||||
<tiles:insertAttribute name="header"/>
|
||||
<tiles:insertAttribute name="body"/>
|
||||
<p>Notice that this is a layout made in JSP</p>
|
||||
<p>It is configured with <strong>annotations!</strong></p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,176 @@
|
||||
<%@taglib prefix="s" uri="/struts-tags" %>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Struts2 Showcase - Validation - AJAX Form Submit</title>
|
||||
<s:head theme="xhtml"/>
|
||||
|
||||
<style type="text/css">
|
||||
/* see comment of script element below! */
|
||||
.ajaxVisualFeedback {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-image: url('../images/indicator.gif');
|
||||
background-repeat: no-repeat;
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="page-header">
|
||||
<h1>AJAX Form Submit</h1>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<!-- START SNIPPET: ajaxFormSubmit -->
|
||||
|
||||
<h3>Action Errors Will Appear Here</h3>
|
||||
<s:actionerror theme="ajaxErrorContainers"/>
|
||||
|
||||
<hr/>
|
||||
|
||||
<s:form method="POST" theme="xhtml">
|
||||
<s:textfield label="Required Validator Field" name="requiredValidatorField" theme="ajaxErrorContainers"/>
|
||||
<s:textfield label="Required String Validator Field" name="requiredStringValidatorField" theme="ajaxErrorContainers"/>
|
||||
<s:textfield label="Integer Validator Field" name="integerValidatorField" theme="ajaxErrorContainers"/>
|
||||
<s:textfield label="Date Validator Field" name="dateValidatorField" theme="ajaxErrorContainers"/>
|
||||
<s:textfield label="Email Validator Field" name="emailValidatorField" theme="ajaxErrorContainers"/>
|
||||
<s:textfield label="URL Validator Field" name="urlValidatorField" theme="ajaxErrorContainers"/>
|
||||
<s:textfield label="String Length Validator Field" name="stringLengthValidatorField" theme="ajaxErrorContainers"/>
|
||||
<s:textfield label="Regex Validator Field" name="regexValidatorField" theme="ajaxErrorContainers"/>
|
||||
<s:textfield label="Field Expression Validator Field" name="fieldExpressionValidatorField" theme="ajaxErrorContainers"/>
|
||||
<s:submit label="Submit" cssClass="btn btn-primary"/>
|
||||
</s:form>
|
||||
|
||||
<!-- END SNIPPET: ajaxFormSubmit -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
/********************************************************************
|
||||
* JS just used on this page.
|
||||
* Usually this would be placed in a JS file
|
||||
* but as this showcase app is already hard to follow
|
||||
* I place it here so it is easier to find.
|
||||
*
|
||||
* note: this requires jQuery.
|
||||
*******************************************************************/
|
||||
|
||||
/**
|
||||
* Validates form per AJAX. To be called as onSubmit handler.
|
||||
*
|
||||
* @param event onSubmit event
|
||||
*/
|
||||
function ajaxFormValidation(event) {
|
||||
event.preventDefault();
|
||||
_removeValidationErrors();
|
||||
|
||||
var _form = $(event.target);
|
||||
var _formData = _form.serialize(true);
|
||||
|
||||
// prepare visual feedback
|
||||
// you may want to use other elements here
|
||||
var originalButton = _form.find('.btn-primary');
|
||||
// note: jQuery returns an array-like object
|
||||
if (originalButton && originalButton.length && originalButton.length > 0) {
|
||||
originalButton.hide();
|
||||
var feedbackElement = $('<div class="ajaxVisualFeedback"></div>').insertAfter(originalButton);
|
||||
var restoreFunction = function() {
|
||||
originalButton.show();
|
||||
feedbackElement.remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var options = {
|
||||
data: 'struts.enableJSONValidation=true&struts.validateOnly=false&' + _formData,
|
||||
async: true,
|
||||
processData: false,
|
||||
type: 'POST',
|
||||
success: function (response, statusText, xhr) {
|
||||
if (response.location) {
|
||||
// no validation errors
|
||||
// action has been executed and sent a redirect URL wrapped as JSON
|
||||
// cannot use a normal http-redirect (status-code 3xx) as this would be followed by browsers and would not be available here
|
||||
|
||||
// follow JSON-redirect
|
||||
window.location.href = response.location;
|
||||
} else {
|
||||
if (restoreFunction) {
|
||||
restoreFunction();
|
||||
}
|
||||
_handleValidationResult(_form, response);
|
||||
}
|
||||
},
|
||||
error: function(xhr, textStatus, errorThrown) {
|
||||
if (restoreFunction) {
|
||||
restoreFunction();
|
||||
}
|
||||
// show user an error message
|
||||
_handleValidationResult(_form, {errors: ['Network or server error!']})
|
||||
}
|
||||
}
|
||||
|
||||
// send request, after delay to make sure everybody notices the visual feedback :)
|
||||
window.setTimeout(function() {
|
||||
var url = _form[0].action;
|
||||
jQuery.ajax(url, options);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes validation errors from HTML DOM.
|
||||
*/
|
||||
function _removeValidationErrors() {
|
||||
// action errors
|
||||
// you might want to use a custom ID here
|
||||
$('ul.errorMessage li').remove();
|
||||
|
||||
// field errors
|
||||
$('div.errorMessage').remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Incorporates validation errors in HTML DOM.
|
||||
*
|
||||
* @param form Form containing errors.
|
||||
* @param errors Errors from server.
|
||||
*/
|
||||
function _handleValidationResult(form, errors) {
|
||||
// action errors
|
||||
if (errors.errors) {
|
||||
// you might want to use a custom ID here
|
||||
var errorContainer = $('ul.errorMessage');
|
||||
$.each(errors.errors, function(index, errorMsg) {
|
||||
var li = $('<li><span></span></li>');
|
||||
li.text(errorMsg); // use text() for security reasons
|
||||
errorContainer.append(li);
|
||||
});
|
||||
}
|
||||
|
||||
// field errors
|
||||
if (errors.fieldErrors) {
|
||||
$.each(errors.fieldErrors, function(fieldName, errorMsg) {
|
||||
var td = $('td[data-error-for-fieldname="' + fieldName + '"]');
|
||||
if (td) {
|
||||
var div = $('<div class="errorMessage"></div>');
|
||||
div.text(errorMsg); // use text() for security reasons
|
||||
td.append(div);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// register onSubmit handler
|
||||
$(window).bind('load', function() {
|
||||
$('form').bind('submit', ajaxFormValidation);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,17 @@
|
||||
<%@taglib prefix="s" uri="/struts-tags" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>Struts2 Showcase - Validation - Success Field Validators Example</title>
|
||||
<s:head/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="page-header">
|
||||
<h1>Success !</h1>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid">
|
||||
Form has been submitted.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -31,6 +31,7 @@
|
||||
<s:url var="clientSideValidationUrl" action="clientSideValidationExample" namespace="/validation"/>
|
||||
<s:url var="backToShowcase" action="showcase" namespace="/"/>
|
||||
<s:url var="storeMessageAcrossRequestExample" namespace="/validation" action="storeErrorsAcrossRequestExample"/>
|
||||
<s:url var="ajaxFormSubmitAction" namespace="/validation" action="ajaxFormSubmit!input"/>
|
||||
|
||||
<ul>
|
||||
<li><s:a href="%{fieldValidatorUrl}">Field Validators</s:a></li>
|
||||
@@ -42,6 +43,7 @@
|
||||
<li><s:a href="%{quizClient}">Validation (client)</s:a></li>
|
||||
<li><s:a href="%{quizClientCss}">Validation (client using css_xhtml theme)</s:a></li>
|
||||
<li><s:a href="%{visitorValidatorUrl}">Visitor Validator</s:a></li>
|
||||
<li><s:a href="%{ajaxFormSubmitAction}">AJAX Form Submit</s:a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-archetypes</artifactId>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-archetypes</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.35</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-archetypes</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.35</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-archetypes</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.35</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-archetypes</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.35</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-archetypes</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.35</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-archetypes</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.35</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-archetypes</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.35</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+20
-13
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-assembly</artifactId>
|
||||
@@ -82,33 +82,29 @@
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>cwiki-docs</id>
|
||||
<id>docs</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<tasks>
|
||||
<mkdir dir="${project.build.directory}/cwiki/" />
|
||||
<target>
|
||||
<mkdir dir="${project.build.directory}/docs/" />
|
||||
<exec executable="wget">
|
||||
<arg value="-erobots=off" />
|
||||
<arg value="-nH" />
|
||||
<arg value="--cut-dirs=3" />
|
||||
<arg value="-x" />
|
||||
<arg value="-nv" />
|
||||
<arg value="-E" />
|
||||
<arg value="-L" />
|
||||
<arg value="-l 0" />
|
||||
<arg value="-np" />
|
||||
<arg value="--directory-prefix=${project.build.directory}/cwiki" />
|
||||
<arg value="--directory-prefix=${project.build.directory}/docs" />
|
||||
<arg value="--no-check-certificate" />
|
||||
<arg value="-r" />
|
||||
<arg value="http://struts.apache.org/docs/" />
|
||||
<arg value="-i" />
|
||||
<arg value="src/main/resources/docs-urls.txt" />
|
||||
</exec>
|
||||
<delete>
|
||||
<fileset dir="${project.build.directory}/cwiki/" includes="**/index.*" />
|
||||
</delete>
|
||||
<copy file="${project.build.directory}/cwiki/home.html" tofile="${project.build.directory}/cwiki/index.html" />
|
||||
</tasks>
|
||||
</target>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
@@ -129,6 +125,7 @@
|
||||
<descriptors>
|
||||
<descriptor>src/main/assembly/all.xml</descriptor>
|
||||
<descriptor>src/main/assembly/lib.xml</descriptor>
|
||||
<descriptor>src/main/assembly/min-lib.xml</descriptor>
|
||||
<descriptor>src/main/assembly/apps.xml</descriptor>
|
||||
<descriptor>src/main/assembly/src.xml</descriptor>
|
||||
<descriptor>src/main/assembly/docs.xml</descriptor>
|
||||
@@ -143,6 +140,16 @@
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.struts.xwork</groupId>
|
||||
<artifactId>xwork-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-cdi-plugin</artifactId>
|
||||
|
||||
@@ -21,7 +21,10 @@
|
||||
*/
|
||||
-->
|
||||
|
||||
<assembly>
|
||||
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
|
||||
|
||||
<id>all</id>
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
|
||||
@@ -20,7 +20,10 @@
|
||||
* under the License.
|
||||
*/
|
||||
-->
|
||||
<assembly>
|
||||
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
|
||||
|
||||
<id>apps</id>
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
*/
|
||||
-->
|
||||
|
||||
<assembly>
|
||||
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
|
||||
|
||||
<id>docs</id>
|
||||
<formats>
|
||||
@@ -47,7 +49,7 @@
|
||||
|
||||
<!-- Include the Confluence docs in the assembly -->
|
||||
<fileSet>
|
||||
<directory>target/cwiki</directory>
|
||||
<directory>target/docs</directory>
|
||||
<outputDirectory>docs/docs</outputDirectory>
|
||||
</fileSet>
|
||||
|
||||
|
||||
@@ -21,7 +21,10 @@
|
||||
*/
|
||||
-->
|
||||
|
||||
<assembly>
|
||||
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
|
||||
|
||||
<id>lib</id>
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<!--
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
-->
|
||||
|
||||
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
|
||||
|
||||
<id>min-lib</id>
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
</formats>
|
||||
<dependencySets>
|
||||
<dependencySet>
|
||||
<useProjectArtifact>true</useProjectArtifact>
|
||||
<useTransitiveDependencies>true</useTransitiveDependencies>
|
||||
<outputDirectory>lib</outputDirectory>
|
||||
<includes>
|
||||
<include>org.apache.struts.xwork:xwork-core</include>
|
||||
<include>org.apache.struts:struts2-core</include>
|
||||
<include>org.freemarker:freemarker</include>
|
||||
<include>org.apache.commons:commons-lang3</include>
|
||||
<include>ognl:ognl</include>
|
||||
<include>javassist:javassist</include>
|
||||
<include>commons-fileupload:commons-fileupload</include>
|
||||
<include>commons-io:commons-io</include>
|
||||
<include>commons-logging:commons-logging</include>
|
||||
</includes>
|
||||
</dependencySet>
|
||||
</dependencySets>
|
||||
</assembly>
|
||||
@@ -21,7 +21,10 @@
|
||||
*/
|
||||
-->
|
||||
|
||||
<assembly>
|
||||
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
|
||||
|
||||
<id>src</id>
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
http://struts.apache.org/getting-started/
|
||||
http://struts.apache.org/security/
|
||||
http://struts.apache.org/core-developers/
|
||||
http://struts.apache.org/tag-developers/
|
||||
http://struts.apache.org/maven-archetypes/
|
||||
http://struts.apache.org/plugins/
|
||||
+3
-3
@@ -10,7 +10,7 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-bom</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.35</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>Struts 2 Bill of Materials</name>
|
||||
@@ -25,7 +25,7 @@
|
||||
</licenses>
|
||||
|
||||
<properties>
|
||||
<struts-version.version>2.3.24.1</struts-version.version>
|
||||
<struts-version.version>2.3.35</struts-version.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
@@ -197,6 +197,6 @@
|
||||
</dependencyManagement>
|
||||
|
||||
<scm>
|
||||
<tag>STRUTS_2_3_24_1</tag>
|
||||
<tag>STRUTS_2_3_35</tag>
|
||||
</scm>
|
||||
</project>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-osgi-bundles</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-osgi-admin-bundle</artifactId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-osgi-bundles</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-osgi-demo-bundle</artifactId>
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-osgi-bundles</artifactId>
|
||||
|
||||
+3
-7
@@ -26,7 +26,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.35</version>
|
||||
</parent>
|
||||
<artifactId>struts2-core</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
@@ -46,15 +46,11 @@
|
||||
<configuration>
|
||||
<A>uri=/struts-tags,tlibVersion=${tlib.version},jspVersion=2.0,shortName=s,displayName=Struts Tags,
|
||||
outFile=${basedir}/target/classes/META-INF/struts-tags.tld,
|
||||
description="To make it easier to access dynamic data;
|
||||
the Apache Struts framework includes a library of custom tags.
|
||||
The tags interact with the framework's validation and internationalization features;
|
||||
to ensure that input is correct and output is localized.
|
||||
The Struts Tags can be used with JSP FreeMarker or Velocity.",
|
||||
description='To make it easier to access dynamic data the Apache Struts framework includes a library of custom tags. The tags interact with the framework validation and internationalization features to ensure that input is correct and output is localized. The Struts Tags can be used with JSP FreeMarker or Velocity.',
|
||||
outTemplatesDir=${basedir}/src/site/resources/tags
|
||||
</A>
|
||||
<resourceTargetPath>target</resourceTargetPath>
|
||||
<fork>false</fork>
|
||||
<fork>true</fork>
|
||||
<force>true</force>
|
||||
<nocompile>true</nocompile>
|
||||
<showWarnings>true</showWarnings>
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2002-2006,2009 The Apache Software Foundation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.opensymphony.xwork2.config;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
|
||||
/**
|
||||
* Dedicated interface to allow inject {@link ServletContext} and perform initialisation based in it
|
||||
*/
|
||||
public interface ServletContextAwareConfigurationProvider extends ConfigurationProvider {
|
||||
|
||||
/**
|
||||
* This is a lazy constructor, called just after class was initiated
|
||||
*
|
||||
* @param servletContext current instance of {@link ServletContext}
|
||||
*/
|
||||
void initWithContext(ServletContext servletContext);
|
||||
|
||||
}
|
||||
@@ -270,8 +270,20 @@ public final class StrutsConstants {
|
||||
|
||||
public static final String STRUTS_EXPRESSION_PARSER = "struts.expression.parser";
|
||||
|
||||
/** namespaces names' whitelist **/
|
||||
public static final String STRUTS_ALLOWED_NAMESPACE_NAMES = "struts.allowed.namespace.names";
|
||||
/** default namespace name to use when namespace didn't match the whitelist **/
|
||||
public static final String STRUTS_DEFAULT_NAMESPACE_NAME = "struts.default.namespace.name";
|
||||
|
||||
/** actions names' whitelist **/
|
||||
public static final String STRUTS_ALLOWED_ACTION_NAMES = "struts.allowed.action.names";
|
||||
/** default action name to use when action didn't match the whitelist **/
|
||||
public static final String STRUTS_DEFAULT_ACTION_NAME = "struts.default.action.name";
|
||||
|
||||
/** methods names' whitelist **/
|
||||
public static final String STRUTS_ALLOWED_METHOD_NAMES = "struts.allowed.method.names";
|
||||
/** default method name to use when method didn't match the whitelist **/
|
||||
public static final String STRUTS_DEFAULT_METHOD_NAME = "struts.default.method.name";
|
||||
|
||||
/** enables action: prefix **/
|
||||
public static final String STRUTS_MAPPER_ACTION_PREFIX_ENABLED = "struts.mapper.action.prefix.enabled";
|
||||
@@ -287,6 +299,7 @@ public final class StrutsConstants {
|
||||
/** Comma delimited set of excluded classes and package names which cannot be accessed via expressions **/
|
||||
public static final String STRUTS_EXCLUDED_CLASSES = "struts.excludedClasses";
|
||||
public static final String STRUTS_EXCLUDED_PACKAGE_NAME_PATTERNS = "struts.excludedPackageNamePatterns";
|
||||
public static final String STRUTS_EXCLUDED_PACKAGE_NAMES = "struts.excludedPackageNames";
|
||||
|
||||
/** Dedicated services to check if passed string is excluded/accepted **/
|
||||
public static final String STRUTS_EXCLUDED_PATTERNS_CHECKER = "struts.excludedPatterns.checker";
|
||||
|
||||
@@ -373,7 +373,11 @@ public class Component {
|
||||
*/
|
||||
protected Object findValue(String expr, Class toType) {
|
||||
if (altSyntax() && toType == String.class) {
|
||||
return TextParseUtil.translateVariables('%', expr, stack);
|
||||
if (ComponentUtils.containsExpression(expr)) {
|
||||
return TextParseUtil.translateVariables('%', expr, stack);
|
||||
} else {
|
||||
return expr;
|
||||
}
|
||||
} else {
|
||||
expr = stripExpressionIfAltSyntax(expr);
|
||||
|
||||
|
||||
@@ -22,14 +22,12 @@
|
||||
package org.apache.struts2.components;
|
||||
|
||||
import java.io.Writer;
|
||||
import java.util.Locale;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import org.apache.struts2.views.annotations.StrutsTag;
|
||||
import org.apache.struts2.views.annotations.StrutsTagAttribute;
|
||||
import org.apache.struts2.StrutsException;
|
||||
|
||||
import com.opensymphony.xwork2.ActionContext;
|
||||
import com.opensymphony.xwork2.LocaleProvider;
|
||||
import com.opensymphony.xwork2.TextProviderFactory;
|
||||
import com.opensymphony.xwork2.TextProvider;
|
||||
@@ -95,6 +93,8 @@ public class I18n extends Component {
|
||||
protected String name;
|
||||
protected Container container;
|
||||
private TextProvider textProvider;
|
||||
private TextProvider defaultTextProvider;
|
||||
private LocaleProvider localeProvider;
|
||||
|
||||
public I18n(ValueStack stack) {
|
||||
super(stack);
|
||||
@@ -105,26 +105,31 @@ public class I18n extends Component {
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setTextProvider(TextProvider textProvider) {
|
||||
this.defaultTextProvider = textProvider;
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setLocaleProvider(LocaleProvider localeProvider) {
|
||||
this.localeProvider = localeProvider;
|
||||
}
|
||||
|
||||
public boolean start(Writer writer) {
|
||||
boolean result = super.start(writer);
|
||||
|
||||
try {
|
||||
String name = this.findString(this.name, "name", "Resource bundle name is required. Example: foo or foo_en");
|
||||
ResourceBundle bundle = (ResourceBundle) findValue("getTexts('" + name + "')");
|
||||
ResourceBundle bundle = defaultTextProvider.getTexts(name);
|
||||
|
||||
if (bundle == null) {
|
||||
bundle = LocalizedTextUtil.findResourceBundle(name, (Locale) getStack().getContext().get(ActionContext.LOCALE));
|
||||
bundle = LocalizedTextUtil.findResourceBundle(name, localeProvider.getLocale());
|
||||
}
|
||||
|
||||
if (bundle != null) {
|
||||
final Locale locale = (Locale) getStack().getContext().get(ActionContext.LOCALE);
|
||||
TextProviderFactory tpf = new TextProviderFactory();
|
||||
container.inject(tpf);
|
||||
textProvider = tpf.createInstance(bundle, new LocaleProvider() {
|
||||
public Locale getLocale() {
|
||||
return locale;
|
||||
}
|
||||
});
|
||||
textProvider = tpf.createInstance(bundle, localeProvider);
|
||||
getStack().push(textProvider);
|
||||
pushed = true;
|
||||
}
|
||||
|
||||
@@ -422,6 +422,7 @@ public class DefaultBeanSelectionProvider extends AbstractBeanSelectionProvider
|
||||
|
||||
convertIfExist(props, StrutsConstants.STRUTS_EXCLUDED_CLASSES, XWorkConstants.OGNL_EXCLUDED_CLASSES);
|
||||
convertIfExist(props, StrutsConstants.STRUTS_EXCLUDED_PACKAGE_NAME_PATTERNS, XWorkConstants.OGNL_EXCLUDED_PACKAGE_NAME_PATTERNS);
|
||||
convertIfExist(props, StrutsConstants.STRUTS_EXCLUDED_PACKAGE_NAMES, XWorkConstants.OGNL_EXCLUDED_PACKAGE_NAMES);
|
||||
|
||||
convertIfExist(props, StrutsConstants.STRUTS_ADDITIONAL_EXCLUDED_PATTERNS, XWorkConstants.ADDITIONAL_EXCLUDED_PATTERNS);
|
||||
convertIfExist(props, StrutsConstants.STRUTS_ADDITIONAL_ACCEPTED_PATTERNS, XWorkConstants.ADDITIONAL_ACCEPTED_PATTERNS);
|
||||
|
||||
@@ -22,6 +22,8 @@ package org.apache.struts2.dispatcher;
|
||||
|
||||
import com.opensymphony.xwork2.inject.Inject;
|
||||
import com.opensymphony.xwork2.util.ClassLoaderUtil;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import com.opensymphony.xwork2.util.logging.Logger;
|
||||
import com.opensymphony.xwork2.util.logging.LoggerFactory;
|
||||
import org.apache.struts2.StrutsConstants;
|
||||
@@ -39,6 +41,7 @@ import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
@@ -93,6 +96,7 @@ public class DefaultStaticContentLoader implements StaticContentLoader {
|
||||
*/
|
||||
protected String encoding;
|
||||
|
||||
protected boolean devMode;
|
||||
|
||||
/**
|
||||
* Modify state of StrutsConstants.STRUTS_SERVE_STATIC_CONTENT setting.
|
||||
@@ -101,29 +105,34 @@ public class DefaultStaticContentLoader implements StaticContentLoader {
|
||||
* New setting
|
||||
*/
|
||||
@Inject(StrutsConstants.STRUTS_SERVE_STATIC_CONTENT)
|
||||
public void setServeStaticContent(String val) {
|
||||
serveStatic = "true".equals(val);
|
||||
public void setServeStaticContent(String serveStaticContent) {
|
||||
this.serveStatic = BooleanUtils.toBoolean(serveStaticContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify state of StrutsConstants.STRUTS_SERVE_STATIC_BROWSER_CACHE
|
||||
* setting.
|
||||
*
|
||||
* @param val
|
||||
* @param serveStaticBrowserCache
|
||||
* New setting
|
||||
*/
|
||||
@Inject(StrutsConstants.STRUTS_SERVE_STATIC_BROWSER_CACHE)
|
||||
public void setServeStaticBrowserCache(String val) {
|
||||
serveStaticBrowserCache = "true".equals(val);
|
||||
public void setServeStaticBrowserCache(String serveStaticBrowserCache) {
|
||||
this.serveStaticBrowserCache = BooleanUtils.toBoolean(serveStaticBrowserCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify state of StrutsConstants.STRUTS_I18N_ENCODING setting.
|
||||
* @param val New setting
|
||||
* @param encoding New setting
|
||||
*/
|
||||
@Inject(StrutsConstants.STRUTS_I18N_ENCODING)
|
||||
public void setEncoding(String val) {
|
||||
encoding = val;
|
||||
public void setEncoding(String encoding) {
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
@Inject(StrutsConstants.STRUTS_DEVMODE)
|
||||
public void setDevMode(String devMode) {
|
||||
this.devMode = Boolean.parseBoolean(devMode);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -141,7 +150,16 @@ public class DefaultStaticContentLoader implements StaticContentLoader {
|
||||
}
|
||||
|
||||
protected String getAdditionalPackages() {
|
||||
return "org.apache.struts2.static template org.apache.struts2.interceptor.debugging static";
|
||||
List<String> packages = new LinkedList<String>();
|
||||
packages.add("org.apache.struts2.static");
|
||||
packages.add("template");
|
||||
packages.add("static");
|
||||
|
||||
if (devMode) {
|
||||
packages.add("org.apache.struts2.interceptor.debugging");
|
||||
}
|
||||
|
||||
return StringUtils.join(packages.iterator(), ' ');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -273,7 +291,7 @@ public class DefaultStaticContentLoader implements StaticContentLoader {
|
||||
* @param name resource name
|
||||
* @param packagePrefix The package prefix to use to locate the resource
|
||||
* @return full path
|
||||
* @throws UnsupportedEncodingException
|
||||
* @throws UnsupportedEncodingException If there is a encoding problem
|
||||
*/
|
||||
protected String buildPath(String name, String packagePrefix) throws UnsupportedEncodingException {
|
||||
String resourcePath;
|
||||
|
||||
@@ -211,7 +211,11 @@ public class Dispatcher {
|
||||
*/
|
||||
@Inject(StrutsConstants.STRUTS_DEVMODE)
|
||||
public void setDevMode(String mode) {
|
||||
devMode = "true".equals(mode);
|
||||
devMode = Boolean.parseBoolean(mode);
|
||||
}
|
||||
|
||||
public boolean isDevMode() {
|
||||
return devMode;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -265,6 +269,10 @@ public class Dispatcher {
|
||||
this.handleException = Boolean.parseBoolean(handleException);
|
||||
}
|
||||
|
||||
public boolean isHandleException() {
|
||||
return handleException;
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setDispatcherErrorHandler(DispatcherErrorHandler errorHandler) {
|
||||
this.errorHandler = errorHandler;
|
||||
@@ -394,6 +402,9 @@ public class Dispatcher {
|
||||
try {
|
||||
Class cls = ClassLoaderUtil.loadClass(cname, this.getClass());
|
||||
ConfigurationProvider prov = (ConfigurationProvider)cls.newInstance();
|
||||
if (prov instanceof ServletContextAwareConfigurationProvider) {
|
||||
((ServletContextAwareConfigurationProvider)prov).initWithContext(servletContext);
|
||||
}
|
||||
configurationManager.addContainerProvider(prov);
|
||||
} catch (InstantiationException e) {
|
||||
throw new ConfigurationException("Unable to instantiate provider: "+cname, e);
|
||||
|
||||
@@ -127,6 +127,8 @@ public class PostbackResult extends StrutsResultSupport {
|
||||
|
||||
if (actionName != null) {
|
||||
actionName = conditionalParse(actionName, invocation);
|
||||
parseLocation = false;
|
||||
|
||||
if (namespace == null) {
|
||||
namespace = invocation.getProxy().getNamespace();
|
||||
} else {
|
||||
|
||||
@@ -164,6 +164,8 @@ public class ServletActionRedirectResult extends ServletRedirectResult implement
|
||||
*/
|
||||
public void execute(ActionInvocation invocation) throws Exception {
|
||||
actionName = conditionalParse(actionName, invocation);
|
||||
parseLocation = false;
|
||||
|
||||
if (namespace == null) {
|
||||
namespace = invocation.getProxy().getNamespace();
|
||||
} else {
|
||||
|
||||
@@ -118,6 +118,8 @@ public abstract class StrutsResultSupport implements Result, StrutsStatics {
|
||||
private String location;
|
||||
private String lastFinalLocation;
|
||||
|
||||
protected boolean parseLocation = true;
|
||||
|
||||
public StrutsResultSupport() {
|
||||
this(null, true, false);
|
||||
}
|
||||
@@ -187,7 +189,7 @@ public abstract class StrutsResultSupport implements Result, StrutsStatics {
|
||||
* @throws Exception if an error occurs while executing the result.
|
||||
*/
|
||||
public void execute(ActionInvocation invocation) throws Exception {
|
||||
lastFinalLocation = conditionalParse(location, invocation);
|
||||
lastFinalLocation = parseLocation ? conditionalParse(location, invocation) : location;
|
||||
doExecute(lastFinalLocation, invocation);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.struts2.RequestUtils;
|
||||
import org.apache.struts2.ServletActionContext;
|
||||
import org.apache.struts2.StrutsConstants;
|
||||
import org.apache.struts2.StrutsException;
|
||||
import org.apache.struts2.util.PrefixTrie;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -119,7 +120,16 @@ public class DefaultActionMapper implements ActionMapper {
|
||||
protected boolean allowSlashesInActionNames = false;
|
||||
protected boolean alwaysSelectFullNamespace = false;
|
||||
protected PrefixTrie prefixTrie = null;
|
||||
|
||||
protected Pattern allowedNamespaceNames = Pattern.compile("[a-zA-Z0-9._/\\-]*");
|
||||
protected String defaultNamespaceName = "/";
|
||||
|
||||
protected Pattern allowedActionNames = Pattern.compile("[a-zA-Z0-9._!/\\-]*");
|
||||
protected String defaultActionName = "index";
|
||||
|
||||
protected Pattern allowedMethodNames = Pattern.compile("[a-zA-Z_]*[0-9]*");
|
||||
protected String defaultMethodName = "execute";
|
||||
|
||||
private boolean allowActionPrefix = false;
|
||||
private boolean allowActionCrossNamespaceAccess = false;
|
||||
|
||||
@@ -136,7 +146,7 @@ public class DefaultActionMapper implements ActionMapper {
|
||||
put(METHOD_PREFIX, new ParameterAction() {
|
||||
public void execute(String key, ActionMapping mapping) {
|
||||
if (allowDynamicMethodCalls) {
|
||||
mapping.setMethod(key.substring(METHOD_PREFIX.length()));
|
||||
mapping.setMethod(cleanupMethodName(key.substring(METHOD_PREFIX.length())));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -148,7 +158,7 @@ public class DefaultActionMapper implements ActionMapper {
|
||||
if (allowDynamicMethodCalls) {
|
||||
int bang = name.indexOf('!');
|
||||
if (bang != -1) {
|
||||
String method = name.substring(bang + 1);
|
||||
String method = cleanupMethodName(name.substring(bang + 1));
|
||||
mapping.setMethod(method);
|
||||
name = name.substring(0, bang);
|
||||
}
|
||||
@@ -160,8 +170,8 @@ public class DefaultActionMapper implements ActionMapper {
|
||||
}
|
||||
}
|
||||
if (!allowSlashesInActionNames && !allowActionCrossNamespaceAccess) {
|
||||
if (actionName.lastIndexOf("/") != -1) {
|
||||
actionName = actionName.substring(actionName.lastIndexOf("/") + 1);
|
||||
if (actionName.lastIndexOf('/') != -1) {
|
||||
actionName = actionName.substring(actionName.lastIndexOf('/') + 1);
|
||||
}
|
||||
}
|
||||
mapping.setName(actionName);
|
||||
@@ -199,11 +209,36 @@ public class DefaultActionMapper implements ActionMapper {
|
||||
this.alwaysSelectFullNamespace = "true".equals(val);
|
||||
}
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_ALLOWED_NAMESPACE_NAMES, required = false)
|
||||
public void setAllowedNamespaceNames(String allowedNamespaceNames) {
|
||||
this.allowedNamespaceNames = Pattern.compile(allowedNamespaceNames);
|
||||
}
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_DEFAULT_NAMESPACE_NAME, required = false)
|
||||
public void setDefaultNamespaceName(String defaultNamespaceName) {
|
||||
this.defaultNamespaceName = defaultNamespaceName;
|
||||
}
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_ALLOWED_ACTION_NAMES, required = false)
|
||||
public void setAllowedActionNames(String allowedActionNames) {
|
||||
this.allowedActionNames = Pattern.compile(allowedActionNames);
|
||||
}
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_DEFAULT_ACTION_NAME, required = false)
|
||||
public void setDefaultActionName(String defaultActionName) {
|
||||
this.defaultActionName = defaultActionName;
|
||||
}
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_ALLOWED_METHOD_NAMES, required = false)
|
||||
public void setAllowedMethodNames(String allowedMethodNames) {
|
||||
this.allowedMethodNames = Pattern.compile(allowedMethodNames);
|
||||
}
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_DEFAULT_METHOD_NAME, required = false)
|
||||
public void setDefaultMethodName(String defaultMethodName) {
|
||||
this.defaultMethodName = defaultMethodName;
|
||||
}
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_MAPPER_ACTION_PREFIX_ENABLED)
|
||||
public void setAllowActionPrefix(String allowActionPrefix) {
|
||||
this.allowActionPrefix = "true".equalsIgnoreCase(allowActionPrefix);
|
||||
@@ -253,7 +288,7 @@ public class DefaultActionMapper implements ActionMapper {
|
||||
ActionMapping mapping = new ActionMapping();
|
||||
String uri = RequestUtils.getUri(request);
|
||||
|
||||
int indexOfSemicolon = uri.indexOf(";");
|
||||
int indexOfSemicolon = uri.indexOf(';');
|
||||
uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri;
|
||||
|
||||
uri = dropExtension(uri, mapping);
|
||||
@@ -273,7 +308,7 @@ public class DefaultActionMapper implements ActionMapper {
|
||||
if (allowDynamicMethodCalls) {
|
||||
// handle "name!method" convention.
|
||||
String name = mapping.getName();
|
||||
int exclamation = name.lastIndexOf("!");
|
||||
int exclamation = name.lastIndexOf('!');
|
||||
if (exclamation != -1) {
|
||||
mapping.setName(name.substring(0, exclamation));
|
||||
|
||||
@@ -322,7 +357,7 @@ public class DefaultActionMapper implements ActionMapper {
|
||||
*/
|
||||
protected void parseNameAndNamespace(String uri, ActionMapping mapping, ConfigurationManager configManager) {
|
||||
String namespace, name;
|
||||
int lastSlash = uri.lastIndexOf("/");
|
||||
int lastSlash = uri.lastIndexOf('/');
|
||||
if (lastSlash == -1) {
|
||||
namespace = "";
|
||||
name = uri;
|
||||
@@ -370,12 +405,30 @@ public class DefaultActionMapper implements ActionMapper {
|
||||
}
|
||||
}
|
||||
|
||||
mapping.setNamespace(namespace);
|
||||
mapping.setNamespace(cleanupNamespaceName(namespace));
|
||||
mapping.setName(cleanupActionName(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up action name from suspicious characters
|
||||
* Checks namespace name against allowed pattern if not matched returns default namespace
|
||||
*
|
||||
* @param rawNamespace name extracted from URI
|
||||
* @return safe namespace name
|
||||
*/
|
||||
protected String cleanupNamespaceName(final String rawNamespace) {
|
||||
if (allowedNamespaceNames.matcher(rawNamespace).matches()) {
|
||||
return rawNamespace;
|
||||
} else {
|
||||
LOG.warn(
|
||||
"{} did not match allowed namespace names {} - default namespace {} will be used!",
|
||||
rawNamespace, allowedActionNames, defaultActionName
|
||||
);
|
||||
return defaultNamespaceName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks action name against allowed pattern if not matched returns default action name
|
||||
*
|
||||
* @param rawActionName action name extracted from URI
|
||||
* @return safe action name
|
||||
@@ -384,18 +437,23 @@ public class DefaultActionMapper implements ActionMapper {
|
||||
if (allowedActionNames.matcher(rawActionName).matches()) {
|
||||
return rawActionName;
|
||||
} else {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("Action [#0] does not match allowed action names pattern [#1], cleaning it up!",
|
||||
rawActionName, allowedActionNames);
|
||||
}
|
||||
String cleanActionName = rawActionName;
|
||||
for (String chunk : allowedActionNames.split(rawActionName)) {
|
||||
cleanActionName = cleanActionName.replace(chunk, "");
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Cleaned action name [#0]", cleanActionName);
|
||||
}
|
||||
return cleanActionName;
|
||||
LOG.warn("{} did not match allowed action names {} - default action {} will be used!", rawActionName, allowedActionNames, defaultActionName);
|
||||
return defaultActionName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks method name (when DMI is enabled) against allowed pattern if not matched returns default action name
|
||||
*
|
||||
* @param rawMethodName method name extracted from URI
|
||||
* @return safe method name
|
||||
*/
|
||||
protected String cleanupMethodName(final String rawMethodName) {
|
||||
if (allowedMethodNames.matcher(rawMethodName).matches()) {
|
||||
return rawMethodName;
|
||||
} else {
|
||||
LOG.warn("{} did not match allowed method names {} - default method {} will be used!", rawMethodName, allowedMethodNames, defaultMethodName);
|
||||
return defaultMethodName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@ import com.opensymphony.xwork2.inject.Inject;
|
||||
import com.opensymphony.xwork2.util.logging.Logger;
|
||||
import com.opensymphony.xwork2.util.logging.LoggerFactory;
|
||||
import org.apache.struts2.StrutsConstants;
|
||||
import org.apache.struts2.util.URLDecoderUtil;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.HashMap;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
@@ -133,10 +133,10 @@ public class Restful2ActionMapper extends DefaultActionMapper {
|
||||
|
||||
while (st.hasMoreTokens()) {
|
||||
if (isNameTok) {
|
||||
paramName = URLDecoder.decode(st.nextToken(), "UTF-8");
|
||||
paramName = URLDecoderUtil.decode(st.nextToken(), "UTF-8");
|
||||
isNameTok = false;
|
||||
} else {
|
||||
paramValue = URLDecoder.decode(st.nextToken(), "UTF-8");
|
||||
paramValue = URLDecoderUtil.decode(st.nextToken(), "UTF-8");
|
||||
|
||||
if ((paramName != null) && (paramName.length() > 0)) {
|
||||
parameters.put(paramName, paramValue);
|
||||
|
||||
@@ -25,9 +25,9 @@ import com.opensymphony.xwork2.config.ConfigurationManager;
|
||||
import com.opensymphony.xwork2.util.logging.Logger;
|
||||
import com.opensymphony.xwork2.util.logging.LoggerFactory;
|
||||
import org.apache.struts2.RequestUtils;
|
||||
import org.apache.struts2.util.URLDecoderUtil;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
@@ -67,10 +67,10 @@ public class RestfulActionMapper implements ActionMapper {
|
||||
|
||||
while (st.hasMoreTokens()) {
|
||||
if (isNameTok) {
|
||||
paramName = URLDecoder.decode(st.nextToken(), "UTF-8");
|
||||
paramName = URLDecoderUtil.decode(st.nextToken(), "UTF-8");
|
||||
isNameTok = false;
|
||||
} else {
|
||||
paramValue = URLDecoder.decode(st.nextToken(), "UTF-8");
|
||||
paramValue = URLDecoderUtil.decode(st.nextToken(), "UTF-8");
|
||||
|
||||
if ((paramName != null) && (paramName.length() > 0)) {
|
||||
parameters.put(paramName, paramValue);
|
||||
|
||||
+5
-1
@@ -120,7 +120,11 @@ public class JakartaMultiPartRequest implements MultiPartRequest {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Preparing error message for key: [#0]", errorKey);
|
||||
}
|
||||
return LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale, e.getMessage(), args);
|
||||
if (LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale, null, new Object[0]) == null) {
|
||||
return LocalizedTextUtil.findText(this.getClass(), "struts.messages.error.uploading", defaultLocale, null, new Object[] { e.getMessage() });
|
||||
} else {
|
||||
return LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale, null, args);
|
||||
}
|
||||
}
|
||||
|
||||
protected void processUpload(HttpServletRequest request, String saveDir) throws FileUploadException, UnsupportedEncodingException {
|
||||
|
||||
+16
-1
@@ -28,6 +28,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Multi-part form data request adapter for Jakarta Commons FileUpload package that
|
||||
@@ -392,6 +393,12 @@ public class JakartaStreamMultiPartRequest implements MultiPartRequest {
|
||||
* @param location
|
||||
*/
|
||||
private void processFileItemStreamAsFileField(FileItemStream itemStream, String location) {
|
||||
// Skip file uploads that don't have a file name - meaning that no file was selected.
|
||||
if (itemStream.getName() == null || itemStream.getName().trim().length() < 1) {
|
||||
LOG.debug("No file has been uploaded for the field: {}", itemStream.getFieldName());
|
||||
return;
|
||||
}
|
||||
|
||||
File file = null;
|
||||
try {
|
||||
// Create the temporary upload file.
|
||||
@@ -433,6 +440,10 @@ public class JakartaStreamMultiPartRequest implements MultiPartRequest {
|
||||
suffix = name.substring(name.lastIndexOf('.'));
|
||||
}
|
||||
|
||||
if (prefix.length() < 3) {
|
||||
prefix = UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
File file = File.createTempFile(prefix + "_", suffix, new File(location));
|
||||
LOG.debug("Creating temporary file '#0' (originally '#1').", file.getName(), fileName);
|
||||
return file;
|
||||
@@ -528,7 +539,11 @@ public class JakartaStreamMultiPartRequest implements MultiPartRequest {
|
||||
String errorKey = "struts.message.upload.error." + e.getClass().getSimpleName();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Preparing error message for key: [#0]", errorKey);
|
||||
return LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale, e.getMessage(), args);
|
||||
if (LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale, null, new Object[0]) == null) {
|
||||
return LocalizedTextUtil.findText(this.getClass(), "struts.messages.error.uploading", defaultLocale, null, new Object[] { e.getMessage() });
|
||||
} else {
|
||||
return LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale, null, args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+5
-1
@@ -108,7 +108,11 @@ public class MultiPartRequestWrapper extends StrutsRequestWrapper {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Preparing error message for key: [#0]", errorKey);
|
||||
}
|
||||
return LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale, e.getMessage(), args);
|
||||
if (LocalizedTextUtil.findText(this.getClass(), errorKey, getLocale(), null, new Object[0]) == null) {
|
||||
return LocalizedTextUtil.findText(this.getClass(), "struts.messages.error.uploading", defaultLocale, null, new Object[] { e.getMessage() });
|
||||
} else {
|
||||
return LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale, null, args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,6 +26,7 @@ import com.opensymphony.xwork2.util.ValueStackFactory;
|
||||
import com.opensymphony.xwork2.util.logging.Logger;
|
||||
import com.opensymphony.xwork2.util.logging.LoggerFactory;
|
||||
import org.apache.struts2.RequestUtils;
|
||||
import org.apache.struts2.ServletActionContext;
|
||||
import org.apache.struts2.StrutsException;
|
||||
import org.apache.struts2.dispatcher.Dispatcher;
|
||||
import org.apache.struts2.dispatcher.mapper.ActionMapper;
|
||||
@@ -135,6 +136,7 @@ public class PrepareOperations {
|
||||
// Wrap request first, just in case it is multipart/form-data
|
||||
// parameters might not be accessible through before encoding (ww-1278)
|
||||
request = dispatcher.wrapRequest(request);
|
||||
ServletActionContext.setRequest(request);
|
||||
} catch (IOException e) {
|
||||
throw new ServletException("Could not wrap servlet request with MultipartRequestWrapper!", e);
|
||||
}
|
||||
@@ -166,7 +168,9 @@ public class PrepareOperations {
|
||||
request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
dispatcher.sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
|
||||
if (dispatcher.isHandleException() || dispatcher.isDevMode()) {
|
||||
dispatcher.sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ package org.apache.struts2.interceptor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.opensymphony.xwork2.ActionContext;
|
||||
import com.opensymphony.xwork2.ActionInvocation;
|
||||
|
||||
/**
|
||||
@@ -78,6 +79,7 @@ public class BackgroundProcess implements Serializable {
|
||||
* @throws Exception any exception thrown will be thrown, in turn, by the ExecuteAndWaitInterceptor
|
||||
*/
|
||||
protected void beforeInvocation() throws Exception {
|
||||
ActionContext.setContext(invocation.getInvocationContext());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,6 +90,7 @@ public class BackgroundProcess implements Serializable {
|
||||
* @throws Exception any exception thrown will be thrown, in turn, by the ExecuteAndWaitInterceptor
|
||||
*/
|
||||
protected void afterInvocation() throws Exception {
|
||||
ActionContext.setContext(null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -237,7 +237,7 @@ public class CookieInterceptor extends AbstractInterceptor {
|
||||
String name = cookie.getName();
|
||||
String value = cookie.getValue();
|
||||
|
||||
if (isAcceptableName(name) && isAcceptableValue(value)) {
|
||||
if (isAcceptableName(name)) {
|
||||
if (cookiesNameSet.contains("*")) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("contains cookie name [*] in configured cookies name set, cookie with name [" + name + "] with value [" + value + "] will be injected");
|
||||
@@ -258,16 +258,6 @@ public class CookieInterceptor extends AbstractInterceptor {
|
||||
return invocation.invoke();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if value of Cookie doesn't contain vulnerable code
|
||||
*
|
||||
* @param value of Cookie
|
||||
* @return true|false
|
||||
*/
|
||||
protected boolean isAcceptableValue(String value) {
|
||||
return !isExcluded(value) && isAccepted(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if name of Cookie doesn't contain vulnerable code
|
||||
*
|
||||
|
||||
@@ -192,12 +192,6 @@ public class ExecuteAndWaitInterceptor extends MethodFilterInterceptor {
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.opensymphony.xwork2.interceptor.Interceptor#init()
|
||||
*/
|
||||
public void init() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new background process
|
||||
*
|
||||
@@ -303,13 +297,6 @@ public class ExecuteAndWaitInterceptor extends MethodFilterInterceptor {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.opensymphony.xwork2.interceptor.Interceptor#destroy()
|
||||
*/
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the initial delay.
|
||||
* <p/>
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.struts2.ServletActionContext;
|
||||
import org.apache.struts2.dispatcher.ServletRedirectResult;
|
||||
|
||||
import com.opensymphony.xwork2.ActionContext;
|
||||
@@ -170,6 +171,7 @@ public class MessageStoreInterceptor extends AbstractInterceptor {
|
||||
public void setAllowRequestParameterSwitch(boolean allowRequestParameterSwitch) {
|
||||
this.allowRequestParameterSwitch = allowRequestParameterSwitch;
|
||||
}
|
||||
|
||||
public boolean getAllowRequestParameterSwitch() {
|
||||
return this.allowRequestParameterSwitch;
|
||||
}
|
||||
@@ -177,6 +179,7 @@ public class MessageStoreInterceptor extends AbstractInterceptor {
|
||||
public void setRequestParameterSwitch(String requestParameterSwitch) {
|
||||
this.requestParameterSwitch = requestParameterSwitch;
|
||||
}
|
||||
|
||||
public String getRequestParameterSwitch() {
|
||||
return this.requestParameterSwitch;
|
||||
}
|
||||
@@ -184,18 +187,23 @@ public class MessageStoreInterceptor extends AbstractInterceptor {
|
||||
public void setOperationMode(String operationMode) {
|
||||
this.operationMode = operationMode;
|
||||
}
|
||||
|
||||
public String getOperationModel() {
|
||||
return this.operationMode;
|
||||
}
|
||||
|
||||
public String intercept(ActionInvocation invocation) throws Exception {
|
||||
LOG.trace("entering MessageStoreInterceptor ...");
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("entering MessageStoreInterceptor ...");
|
||||
}
|
||||
|
||||
before(invocation);
|
||||
|
||||
LOG.trace("Registering listener to store messages before result will be executed");
|
||||
invocation.addPreResultListener(new MessageStorePreResultListener(this));
|
||||
|
||||
String result = invocation.invoke();
|
||||
after(invocation, result);
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("exit executing MessageStoreInterceptor");
|
||||
@@ -261,50 +269,6 @@ public class MessageStoreInterceptor extends AbstractInterceptor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the storing of field errors / action messages / field errors, which is
|
||||
* done after action invocation, and the <code>operationMode</code> is in 'STORE'.
|
||||
*
|
||||
* @param invocation
|
||||
* @param result
|
||||
* @throws Exception
|
||||
*/
|
||||
protected void after(ActionInvocation invocation, String result) throws Exception {
|
||||
|
||||
String reqOperationMode = getRequestOperationMode(invocation);
|
||||
boolean isRedirect = invocation.getResult() instanceof ServletRedirectResult;
|
||||
if (STORE_MODE.equalsIgnoreCase(reqOperationMode) ||
|
||||
STORE_MODE.equalsIgnoreCase(operationMode) ||
|
||||
(AUTOMATIC_MODE.equalsIgnoreCase(operationMode) && isRedirect)) {
|
||||
|
||||
Object action = invocation.getAction();
|
||||
if (action instanceof ValidationAware) {
|
||||
// store error / messages into session
|
||||
Map session = (Map) invocation.getInvocationContext().get(ActionContext.SESSION);
|
||||
|
||||
if (session == null) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Could not store action ["+action+"] error/messages into session, because session hasn't been opened yet.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("store action ["+action+"] error/messages into session ");
|
||||
}
|
||||
|
||||
ValidationAware validationAwareAction = (ValidationAware) action;
|
||||
session.put(actionErrorsSessionKey, validationAwareAction.getActionErrors());
|
||||
session.put(actionMessagesSessionKey, validationAwareAction.getActionMessages());
|
||||
session.put(fieldErrorsSessionKey, validationAwareAction.getFieldErrors());
|
||||
}
|
||||
else if(LOG.isDebugEnabled()) {
|
||||
LOG.debug("Action ["+action+"] is not ValidationAware, no message / error that are storeable");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the operationMode through request paramter, if <code>allowRequestParameterSwitch</code>
|
||||
* is 'true', else it simply returns 'NONE', meaning its neither in the 'STORE_MODE' nor
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.struts2.interceptor;
|
||||
|
||||
import com.opensymphony.xwork2.ActionInvocation;
|
||||
import com.opensymphony.xwork2.ValidationAware;
|
||||
import com.opensymphony.xwork2.config.entities.ResultConfig;
|
||||
import com.opensymphony.xwork2.interceptor.PreResultListener;
|
||||
import com.opensymphony.xwork2.util.logging.Logger;
|
||||
import com.opensymphony.xwork2.util.logging.LoggerFactory;
|
||||
import org.apache.struts2.ServletActionContext;
|
||||
import org.apache.struts2.dispatcher.ServletActionRedirectResult;
|
||||
import org.apache.struts2.dispatcher.ServletRedirectResult;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This listener is used by {@link MessageStoreInterceptor} to store messages in HttpSession
|
||||
* just before result will be executed. It must be done that way as after result will be executed
|
||||
* HttpSession cannot be modified (response was already sent to browser).
|
||||
*/
|
||||
class MessageStorePreResultListener implements PreResultListener {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MessageStorePreResultListener.class);
|
||||
|
||||
private MessageStoreInterceptor interceptor;
|
||||
|
||||
public MessageStorePreResultListener(MessageStoreInterceptor interceptor) {
|
||||
this.interceptor = interceptor;
|
||||
}
|
||||
|
||||
public void beforeResult(ActionInvocation invocation, String resultCode) {
|
||||
|
||||
boolean isCommitted = ServletActionContext.getResponse().isCommitted();
|
||||
if (isCommitted) {
|
||||
LOG.trace("Response was already committed, cannot store messages!");
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isInvalidated = ServletActionContext.getRequest().getSession(false) == null;
|
||||
if (isInvalidated) {
|
||||
LOG.trace("Session was invalidated or never created, cannot store messages!");
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> session = invocation.getInvocationContext().getSession();
|
||||
if (session == null) {
|
||||
LOG.trace("Could not store action [#0] error/messages into session, because session hasn't been opened yet.", invocation.getAction());
|
||||
return;
|
||||
}
|
||||
|
||||
String reqOperationMode = interceptor.getRequestOperationMode(invocation);
|
||||
|
||||
boolean isRedirect = false;
|
||||
try {
|
||||
ResultConfig resultConfig = invocation.getProxy().getConfig().getResults().get(resultCode);
|
||||
if (resultConfig != null) {
|
||||
isRedirect = ServletRedirectResult.class.isAssignableFrom(Class.forName(resultConfig.getClassName()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Cannot read result!", e);
|
||||
}
|
||||
|
||||
if (MessageStoreInterceptor.STORE_MODE.equalsIgnoreCase(reqOperationMode) ||
|
||||
MessageStoreInterceptor.STORE_MODE.equalsIgnoreCase(interceptor.getOperationModel()) ||
|
||||
(MessageStoreInterceptor.AUTOMATIC_MODE.equalsIgnoreCase(interceptor.getOperationModel()) && isRedirect)) {
|
||||
|
||||
Object action = invocation.getAction();
|
||||
if (action instanceof ValidationAware) {
|
||||
LOG.debug("Storing action [#0] error/messages into session ", action);
|
||||
|
||||
ValidationAware validationAwareAction = (ValidationAware) action;
|
||||
session.put(MessageStoreInterceptor.actionErrorsSessionKey, validationAwareAction.getActionErrors());
|
||||
session.put(MessageStoreInterceptor.actionMessagesSessionKey, validationAwareAction.getActionMessages());
|
||||
session.put(MessageStoreInterceptor.fieldErrorsSessionKey, validationAwareAction.getFieldErrors());
|
||||
|
||||
} else {
|
||||
LOG.debug("Action [#0] is not ValidationAware, no message / error that are storeable", action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,12 +40,15 @@ public class ComponentUtils {
|
||||
/**
|
||||
* Check if object is expression base on altSyntax
|
||||
*
|
||||
* @param value to treat as an expression
|
||||
* @param expr to treat as an expression
|
||||
* @return true if it is an expression
|
||||
*/
|
||||
public static boolean isExpression(Object value) {
|
||||
String expr = value.toString();
|
||||
return expr.startsWith("%{") && expr.endsWith("}");
|
||||
public static boolean isExpression(String expr) {
|
||||
return expr != null && expr.startsWith("%{") && expr.endsWith("}");
|
||||
}
|
||||
|
||||
public static boolean containsExpression(String expr) {
|
||||
return expr != null && expr.contains("%{") && expr.contains("}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.apache.struts2.util;
|
||||
|
||||
import org.apache.struts2.util.tomcat.buf.UDecoder;
|
||||
|
||||
/**
|
||||
* URLDecoderUtil serves as a facade for a correct URL decoding implementation.
|
||||
* As of Struts 2.3.25 it uses Tomcat URLDecoder functionality rather than the one found in java.io.
|
||||
*/
|
||||
public class URLDecoderUtil {
|
||||
|
||||
/**
|
||||
* Decodes a <code>x-www-form-urlencoded</code> string.
|
||||
* @param sequence the String to decode
|
||||
* @param charset The name of a supported character encoding.
|
||||
* @return the newly decoded <code>String</code>
|
||||
* @exception IllegalArgumentException If the encoding is not valid
|
||||
*/
|
||||
public static String decode(String sequence, String charset) {
|
||||
return UDecoder.URLDecode(sequence, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a <code>x-www-form-urlencoded</code> string.
|
||||
* @param sequence the String to decode
|
||||
* @param charset The name of a supported character encoding.
|
||||
* @param isQueryString whether input is a query string. If <code>true</code> other decoding rules apply.
|
||||
* @return the newly decoded <code>String</code>
|
||||
* @exception IllegalArgumentException If the encoding is not valid
|
||||
*/
|
||||
public static String decode(String sequence, String charset, boolean isQueryString) {
|
||||
return UDecoder.URLDecode(sequence, charset, isQueryString);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.struts2.util.tomcat.buf;
|
||||
|
||||
/**
|
||||
* This class implements some basic ASCII character handling functions.
|
||||
*
|
||||
* @author dac@eng.sun.com
|
||||
* @author James Todd [gonzo@eng.sun.com]
|
||||
*/
|
||||
public final class Ascii {
|
||||
/*
|
||||
* Character translation tables.
|
||||
*/
|
||||
|
||||
private static final byte[] toUpper = new byte[256];
|
||||
private static final byte[] toLower = new byte[256];
|
||||
|
||||
/*
|
||||
* Character type tables.
|
||||
*/
|
||||
|
||||
private static final boolean[] isAlpha = new boolean[256];
|
||||
private static final boolean[] isUpper = new boolean[256];
|
||||
private static final boolean[] isLower = new boolean[256];
|
||||
private static final boolean[] isWhite = new boolean[256];
|
||||
private static final boolean[] isDigit = new boolean[256];
|
||||
|
||||
private static final long OVERFLOW_LIMIT = Long.MAX_VALUE / 10;
|
||||
|
||||
/*
|
||||
* Initialize character translation and type tables.
|
||||
*/
|
||||
static {
|
||||
for (int i = 0; i < 256; i++) {
|
||||
toUpper[i] = (byte)i;
|
||||
toLower[i] = (byte)i;
|
||||
}
|
||||
|
||||
for (int lc = 'a'; lc <= 'z'; lc++) {
|
||||
int uc = lc + 'A' - 'a';
|
||||
|
||||
toUpper[lc] = (byte)uc;
|
||||
toLower[uc] = (byte)lc;
|
||||
isAlpha[lc] = true;
|
||||
isAlpha[uc] = true;
|
||||
isLower[lc] = true;
|
||||
isUpper[uc] = true;
|
||||
}
|
||||
|
||||
isWhite[ ' '] = true;
|
||||
isWhite['\t'] = true;
|
||||
isWhite['\r'] = true;
|
||||
isWhite['\n'] = true;
|
||||
isWhite['\f'] = true;
|
||||
isWhite['\b'] = true;
|
||||
|
||||
for (int d = '0'; d <= '9'; d++) {
|
||||
isDigit[d] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the upper case equivalent of the specified ASCII character.
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public static int toUpper(int c) {
|
||||
return toUpper[c & 0xff] & 0xff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the lower case equivalent of the specified ASCII character.
|
||||
*/
|
||||
|
||||
public static int toLower(int c) {
|
||||
return toLower[c & 0xff] & 0xff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified ASCII character is upper or lower case.
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean isAlpha(int c) {
|
||||
return isAlpha[c & 0xff];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified ASCII character is upper case.
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean isUpper(int c) {
|
||||
return isUpper[c & 0xff];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified ASCII character is lower case.
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean isLower(int c) {
|
||||
return isLower[c & 0xff];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified ASCII character is white space.
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean isWhite(int c) {
|
||||
return isWhite[c & 0xff];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified ASCII character is a digit.
|
||||
*/
|
||||
|
||||
public static boolean isDigit(int c) {
|
||||
return isDigit[c & 0xff];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an unsigned integer from the specified subarray of bytes.
|
||||
* @param b the bytes to parse
|
||||
* @param off the start offset of the bytes
|
||||
* @param len the length of the bytes
|
||||
* @exception NumberFormatException if the integer format was invalid
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public static int parseInt(byte[] b, int off, int len)
|
||||
throws NumberFormatException
|
||||
{
|
||||
int c;
|
||||
|
||||
if (b == null || len <= 0 || !isDigit(c = b[off++])) {
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
|
||||
int n = c - '0';
|
||||
|
||||
while (--len > 0) {
|
||||
if (!isDigit(c = b[off++])) {
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
n = n * 10 + c - '0';
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public static int parseInt(char[] b, int off, int len)
|
||||
throws NumberFormatException
|
||||
{
|
||||
int c;
|
||||
|
||||
if (b == null || len <= 0 || !isDigit(c = b[off++])) {
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
|
||||
int n = c - '0';
|
||||
|
||||
while (--len > 0) {
|
||||
if (!isDigit(c = b[off++])) {
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
n = n * 10 + c - '0';
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an unsigned long from the specified subarray of bytes.
|
||||
* @param b the bytes to parse
|
||||
* @param off the start offset of the bytes
|
||||
* @param len the length of the bytes
|
||||
* @exception NumberFormatException if the long format was invalid
|
||||
*/
|
||||
public static long parseLong(byte[] b, int off, int len)
|
||||
throws NumberFormatException
|
||||
{
|
||||
int c;
|
||||
|
||||
if (b == null || len <= 0 || !isDigit(c = b[off++])) {
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
|
||||
long n = c - '0';
|
||||
while (--len > 0) {
|
||||
if (isDigit(c = b[off++]) &&
|
||||
(n < OVERFLOW_LIMIT || (n == OVERFLOW_LIMIT && (c - '0') < 8))) {
|
||||
n = n * 10 + c - '0';
|
||||
} else {
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public static long parseLong(char[] b, int off, int len)
|
||||
throws NumberFormatException
|
||||
{
|
||||
int c;
|
||||
|
||||
if (b == null || len <= 0 || !isDigit(c = b[off++])) {
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
|
||||
long n = c - '0';
|
||||
long m;
|
||||
|
||||
while (--len > 0) {
|
||||
if (!isDigit(c = b[off++])) {
|
||||
throw new NumberFormatException();
|
||||
}
|
||||
m = n * 10 + c - '0';
|
||||
|
||||
if (m < n) {
|
||||
// Overflow
|
||||
throw new NumberFormatException();
|
||||
} else {
|
||||
n = m;
|
||||
}
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.struts2.util.tomcat.buf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CoderResult;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* NIO based character decoder.
|
||||
*/
|
||||
public class B2CConverter {
|
||||
|
||||
private static final Map<String, Charset> encodingToCharsetCache =
|
||||
new HashMap<String, Charset>();
|
||||
|
||||
public static final Charset ISO_8859_1;
|
||||
public static final Charset UTF_8;
|
||||
|
||||
// Protected so unit tests can use it
|
||||
protected static final int LEFTOVER_SIZE = 9;
|
||||
|
||||
static {
|
||||
for (Charset charset: Charset.availableCharsets().values()) {
|
||||
encodingToCharsetCache.put(
|
||||
charset.name().toLowerCase(Locale.ENGLISH), charset);
|
||||
for (String alias : charset.aliases()) {
|
||||
encodingToCharsetCache.put(
|
||||
alias.toLowerCase(Locale.ENGLISH), charset);
|
||||
}
|
||||
}
|
||||
Charset iso88591 = null;
|
||||
Charset utf8 = null;
|
||||
try {
|
||||
iso88591 = getCharset("ISO-8859-1");
|
||||
utf8 = getCharset("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// Impossible. All JVMs must support these.
|
||||
e.printStackTrace();
|
||||
}
|
||||
ISO_8859_1 = iso88591;
|
||||
UTF_8 = utf8;
|
||||
}
|
||||
|
||||
public static Charset getCharset(String enc)
|
||||
throws UnsupportedEncodingException {
|
||||
|
||||
// Encoding names should all be ASCII
|
||||
String lowerCaseEnc = enc.toLowerCase(Locale.ENGLISH);
|
||||
|
||||
return getCharsetLower(lowerCaseEnc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Only to be used when it is known that the encoding name is in lower case.
|
||||
*/
|
||||
public static Charset getCharsetLower(String lowerCaseEnc)
|
||||
throws UnsupportedEncodingException {
|
||||
|
||||
Charset charset = encodingToCharsetCache.get(lowerCaseEnc);
|
||||
|
||||
if (charset == null) {
|
||||
// Pre-population of the cache means this must be invalid
|
||||
throw new UnsupportedEncodingException("The character encoding " + lowerCaseEnc + " is not supported");
|
||||
}
|
||||
return charset;
|
||||
}
|
||||
|
||||
private final CharsetDecoder decoder;
|
||||
private ByteBuffer bb = null;
|
||||
private CharBuffer cb = null;
|
||||
|
||||
/**
|
||||
* Leftover buffer used for incomplete characters.
|
||||
*/
|
||||
private final ByteBuffer leftovers;
|
||||
|
||||
public B2CConverter(String encoding) throws IOException {
|
||||
this(encoding, false);
|
||||
}
|
||||
|
||||
public B2CConverter(String encoding, boolean replaceOnError)
|
||||
throws IOException {
|
||||
byte[] left = new byte[LEFTOVER_SIZE];
|
||||
leftovers = ByteBuffer.wrap(left);
|
||||
CodingErrorAction action;
|
||||
if (replaceOnError) {
|
||||
action = CodingErrorAction.REPLACE;
|
||||
} else {
|
||||
action = CodingErrorAction.REPORT;
|
||||
}
|
||||
Charset charset = getCharset(encoding);
|
||||
// Special case. Use the Apache Harmony based UTF-8 decoder because it
|
||||
// - a) rejects invalid sequences that the JVM decoder does not
|
||||
// - b) fails faster for some invalid sequences
|
||||
if (charset.equals(UTF_8)) {
|
||||
decoder = new Utf8Decoder();
|
||||
} else {
|
||||
decoder = charset.newDecoder();
|
||||
}
|
||||
decoder.onMalformedInput(action);
|
||||
decoder.onUnmappableCharacter(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the decoder state.
|
||||
*/
|
||||
public void recycle() {
|
||||
decoder.reset();
|
||||
leftovers.position(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given bytes to characters.
|
||||
*
|
||||
* @param bc byte input
|
||||
* @param cc char output
|
||||
* @param endOfInput Is this all of the available data
|
||||
*/
|
||||
public void convert(ByteChunk bc, CharChunk cc, boolean endOfInput)
|
||||
throws IOException {
|
||||
if ((bb == null) || (bb.array() != bc.getBuffer())) {
|
||||
// Create a new byte buffer if anything changed
|
||||
bb = ByteBuffer.wrap(bc.getBuffer(), bc.getStart(), bc.getLength());
|
||||
} else {
|
||||
// Initialize the byte buffer
|
||||
bb.limit(bc.getEnd());
|
||||
bb.position(bc.getStart());
|
||||
}
|
||||
if ((cb == null) || (cb.array() != cc.getBuffer())) {
|
||||
// Create a new char buffer if anything changed
|
||||
cb = CharBuffer.wrap(cc.getBuffer(), cc.getEnd(),
|
||||
cc.getBuffer().length - cc.getEnd());
|
||||
} else {
|
||||
// Initialize the char buffer
|
||||
cb.limit(cc.getBuffer().length);
|
||||
cb.position(cc.getEnd());
|
||||
}
|
||||
CoderResult result = null;
|
||||
// Parse leftover if any are present
|
||||
if (leftovers.position() > 0) {
|
||||
int pos = cb.position();
|
||||
// Loop until one char is decoded or there is a decoder error
|
||||
do {
|
||||
leftovers.put(bc.substractB());
|
||||
leftovers.flip();
|
||||
result = decoder.decode(leftovers, cb, endOfInput);
|
||||
leftovers.position(leftovers.limit());
|
||||
leftovers.limit(leftovers.array().length);
|
||||
} while (result.isUnderflow() && (cb.position() == pos));
|
||||
if (result.isError() || result.isMalformed()) {
|
||||
result.throwException();
|
||||
}
|
||||
bb.position(bc.getStart());
|
||||
leftovers.position(0);
|
||||
}
|
||||
// Do the decoding and get the results into the byte chunk and the char
|
||||
// chunk
|
||||
result = decoder.decode(bb, cb, endOfInput);
|
||||
if (result.isError() || result.isMalformed()) {
|
||||
result.throwException();
|
||||
} else if (result.isOverflow()) {
|
||||
// Propagate current positions to the byte chunk and char chunk, if
|
||||
// this continues the char buffer will get resized
|
||||
bc.setOffset(bb.position());
|
||||
cc.setEnd(cb.position());
|
||||
} else if (result.isUnderflow()) {
|
||||
// Propagate current positions to the byte chunk and char chunk
|
||||
bc.setOffset(bb.position());
|
||||
cc.setEnd(cb.position());
|
||||
// Put leftovers in the leftovers byte buffer
|
||||
if (bc.getLength() > 0) {
|
||||
leftovers.limit(leftovers.array().length);
|
||||
leftovers.position(bc.getLength());
|
||||
bc.substract(leftovers.array(), 0, bc.getLength());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,935 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.struts2.util.tomcat.buf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/*
|
||||
* In a server it is very important to be able to operate on
|
||||
* the original byte[] without converting everything to chars.
|
||||
* Some protocols are ASCII only, and some allow different
|
||||
* non-UNICODE encodings. The encoding is not known beforehand,
|
||||
* and can even change during the execution of the protocol.
|
||||
* ( for example a multipart message may have parts with different
|
||||
* encoding )
|
||||
*
|
||||
* For HTTP it is not very clear how the encoding of RequestURI
|
||||
* and mime values can be determined, but it is a great advantage
|
||||
* to be able to parse the request without converting to string.
|
||||
*/
|
||||
|
||||
// TODO: This class could either extend ByteBuffer, or better a ByteBuffer
|
||||
// inside this way it could provide the search/etc on ByteBuffer, as a helper.
|
||||
|
||||
/**
|
||||
* This class is used to represent a chunk of bytes, and
|
||||
* utilities to manipulate byte[].
|
||||
*
|
||||
* The buffer can be modified and used for both input and output.
|
||||
*
|
||||
* There are 2 modes: The chunk can be associated with a sink - ByteInputChannel
|
||||
* or ByteOutputChannel, which will be used when the buffer is empty (on input)
|
||||
* or filled (on output).
|
||||
* For output, it can also grow. This operating mode is selected by calling
|
||||
* setLimit() or allocate(initial, limit) with limit != -1.
|
||||
*
|
||||
* Various search and append method are defined - similar with String and
|
||||
* StringBuffer, but operating on bytes.
|
||||
*
|
||||
* This is important because it allows processing the http headers directly on
|
||||
* the received bytes, without converting to chars and Strings until the strings
|
||||
* are needed. In addition, the charset is determined later, from headers or
|
||||
* user code.
|
||||
*
|
||||
* @author dac@sun.com
|
||||
* @author James Todd [gonzo@sun.com]
|
||||
* @author Costin Manolache
|
||||
* @author Remy Maucherat
|
||||
*/
|
||||
public final class ByteChunk implements Cloneable, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** Input interface, used when the buffer is empty
|
||||
*
|
||||
* Same as java.nio.channel.ReadableByteChannel
|
||||
*/
|
||||
public static interface ByteInputChannel {
|
||||
/**
|
||||
* Read new bytes ( usually the internal conversion buffer ).
|
||||
* The implementation is allowed to ignore the parameters,
|
||||
* and mutate the chunk if it wishes to implement its own buffering.
|
||||
*/
|
||||
public int realReadBytes(byte cbuf[], int off, int len)
|
||||
throws IOException;
|
||||
}
|
||||
|
||||
/** Same as java.nio.channel.WrittableByteChannel.
|
||||
*/
|
||||
public static interface ByteOutputChannel {
|
||||
/**
|
||||
* Send the bytes ( usually the internal conversion buffer ).
|
||||
* Expect 8k output if the buffer is full.
|
||||
*/
|
||||
public void realWriteBytes(byte cbuf[], int off, int len)
|
||||
throws IOException;
|
||||
}
|
||||
|
||||
// --------------------
|
||||
|
||||
/** Default encoding used to convert to strings. It should be UTF8,
|
||||
as most standards seem to converge, but the servlet API requires
|
||||
8859_1, and this object is used mostly for servlets.
|
||||
*/
|
||||
public static final Charset DEFAULT_CHARSET = B2CConverter.ISO_8859_1;
|
||||
|
||||
// byte[]
|
||||
private byte[] buff;
|
||||
|
||||
private int start=0;
|
||||
private int end;
|
||||
|
||||
private Charset charset;
|
||||
|
||||
private boolean isSet=false; // XXX
|
||||
|
||||
// How much can it grow, when data is added
|
||||
private int limit=-1;
|
||||
|
||||
private ByteInputChannel in = null;
|
||||
private ByteOutputChannel out = null;
|
||||
|
||||
private boolean optimizedWrite=true;
|
||||
|
||||
/**
|
||||
* Creates a new, uninitialized ByteChunk object.
|
||||
*/
|
||||
public ByteChunk() {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
public ByteChunk( int initial ) {
|
||||
allocate( initial, -1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public ByteChunk getClone() {
|
||||
try {
|
||||
return (ByteChunk)this.clone();
|
||||
} catch( Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isNull() {
|
||||
return ! isSet; // buff==null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the message buff to an uninitialized state.
|
||||
*/
|
||||
public void recycle() {
|
||||
// buff = null;
|
||||
charset=null;
|
||||
start=0;
|
||||
end=0;
|
||||
isSet=false;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
buff=null;
|
||||
}
|
||||
|
||||
// -------------------- Setup --------------------
|
||||
|
||||
public void allocate( int initial, int limit ) {
|
||||
if( buff==null || buff.length < initial ) {
|
||||
buff=new byte[initial];
|
||||
}
|
||||
this.limit=limit;
|
||||
start=0;
|
||||
end=0;
|
||||
isSet=true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message bytes to the specified subarray of bytes.
|
||||
*
|
||||
* @param b the ascii bytes
|
||||
* @param off the start offset of the bytes
|
||||
* @param len the length of the bytes
|
||||
*/
|
||||
public void setBytes(byte[] b, int off, int len) {
|
||||
buff = b;
|
||||
start = off;
|
||||
end = start+ len;
|
||||
isSet=true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setOptimizedWrite(boolean optimizedWrite) {
|
||||
this.optimizedWrite = optimizedWrite;
|
||||
}
|
||||
|
||||
public void setCharset(Charset charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
public Charset getCharset() {
|
||||
if (charset == null) {
|
||||
charset = DEFAULT_CHARSET;
|
||||
}
|
||||
return charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message bytes.
|
||||
*/
|
||||
public byte[] getBytes() {
|
||||
return getBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message bytes.
|
||||
*/
|
||||
public byte[] getBuffer() {
|
||||
return buff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start offset of the bytes.
|
||||
* For output this is the end of the buffer.
|
||||
*/
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public void setOffset(int off) {
|
||||
if (end < off ) {
|
||||
end=off;
|
||||
}
|
||||
start=off;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of the bytes.
|
||||
* XXX need to clean this up
|
||||
*/
|
||||
public int getLength() {
|
||||
return end-start;
|
||||
}
|
||||
|
||||
/** Maximum amount of data in this buffer.
|
||||
*
|
||||
* If -1 or not set, the buffer will grow indefinitely.
|
||||
* Can be smaller than the current buffer size ( which will not shrink ).
|
||||
* When the limit is reached, the buffer will be flushed ( if out is set )
|
||||
* or throw exception.
|
||||
*/
|
||||
public void setLimit(int limit) {
|
||||
this.limit=limit;
|
||||
}
|
||||
|
||||
public int getLimit() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the buffer is empty, read the data from the input channel.
|
||||
*/
|
||||
public void setByteInputChannel(ByteInputChannel in) {
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
/** When the buffer is full, write the data to the output channel.
|
||||
* Also used when large amount of data is appended.
|
||||
*
|
||||
* If not set, the buffer will grow to the limit.
|
||||
*/
|
||||
public void setByteOutputChannel(ByteOutputChannel out) {
|
||||
this.out=out;
|
||||
}
|
||||
|
||||
public int getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public void setEnd( int i ) {
|
||||
end=i;
|
||||
}
|
||||
|
||||
// -------------------- Adding data to the buffer --------------------
|
||||
/** Append a char, by casting it to byte. This IS NOT intended for unicode.
|
||||
*
|
||||
* @param c
|
||||
* @throws IOException
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public void append( char c )
|
||||
throws IOException
|
||||
{
|
||||
append( (byte)c);
|
||||
}
|
||||
|
||||
public void append( byte b )
|
||||
throws IOException
|
||||
{
|
||||
makeSpace( 1 );
|
||||
|
||||
// couldn't make space
|
||||
if( limit >0 && end >= limit ) {
|
||||
flushBuffer();
|
||||
}
|
||||
buff[end++]=b;
|
||||
}
|
||||
|
||||
public void append( ByteChunk src )
|
||||
throws IOException
|
||||
{
|
||||
append( src.getBytes(), src.getStart(), src.getLength());
|
||||
}
|
||||
|
||||
/** Add data to the buffer
|
||||
*/
|
||||
public void append( byte src[], int off, int len )
|
||||
throws IOException
|
||||
{
|
||||
// will grow, up to limit
|
||||
makeSpace( len );
|
||||
|
||||
// if we don't have limit: makeSpace can grow as it wants
|
||||
if( limit < 0 ) {
|
||||
// assert: makeSpace made enough space
|
||||
System.arraycopy( src, off, buff, end, len );
|
||||
end+=len;
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimize on a common case.
|
||||
// If the buffer is empty and the source is going to fill up all the
|
||||
// space in buffer, may as well write it directly to the output,
|
||||
// and avoid an extra copy
|
||||
if ( optimizedWrite && len == limit && end == start && out != null ) {
|
||||
out.realWriteBytes( src, off, len );
|
||||
return;
|
||||
}
|
||||
// if we have limit and we're below
|
||||
if( len <= limit - end ) {
|
||||
// makeSpace will grow the buffer to the limit,
|
||||
// so we have space
|
||||
System.arraycopy( src, off, buff, end, len );
|
||||
end+=len;
|
||||
return;
|
||||
}
|
||||
|
||||
// need more space than we can afford, need to flush
|
||||
// buffer
|
||||
|
||||
// the buffer is already at ( or bigger than ) limit
|
||||
|
||||
// We chunk the data into slices fitting in the buffer limit, although
|
||||
// if the data is written directly if it doesn't fit
|
||||
|
||||
int avail=limit-end;
|
||||
System.arraycopy(src, off, buff, end, avail);
|
||||
end += avail;
|
||||
|
||||
flushBuffer();
|
||||
|
||||
int remain = len - avail;
|
||||
|
||||
while (remain > (limit - end)) {
|
||||
out.realWriteBytes( src, (off + len) - remain, limit - end );
|
||||
remain = remain - (limit - end);
|
||||
}
|
||||
|
||||
System.arraycopy(src, (off + len) - remain, buff, end, remain);
|
||||
end += remain;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// -------------------- Removing data from the buffer --------------------
|
||||
|
||||
public int substract()
|
||||
throws IOException {
|
||||
|
||||
if ((end - start) == 0) {
|
||||
if (in == null) {
|
||||
return -1;
|
||||
}
|
||||
int n = in.realReadBytes( buff, 0, buff.length );
|
||||
if (n < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return (buff[start++] & 0xFF);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public int substract(ByteChunk src)
|
||||
throws IOException {
|
||||
|
||||
if ((end - start) == 0) {
|
||||
if (in == null) {
|
||||
return -1;
|
||||
}
|
||||
int n = in.realReadBytes( buff, 0, buff.length );
|
||||
if (n < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int len = getLength();
|
||||
src.append(buff, start, len);
|
||||
start = end;
|
||||
return len;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public byte substractB()
|
||||
throws IOException {
|
||||
|
||||
if ((end - start) == 0) {
|
||||
if (in == null)
|
||||
return -1;
|
||||
int n = in.realReadBytes( buff, 0, buff.length );
|
||||
if (n < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (buff[start++]);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public int substract( byte src[], int off, int len )
|
||||
throws IOException {
|
||||
|
||||
if ((end - start) == 0) {
|
||||
if (in == null) {
|
||||
return -1;
|
||||
}
|
||||
int n = in.realReadBytes( buff, 0, buff.length );
|
||||
if (n < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int n = len;
|
||||
if (len > getLength()) {
|
||||
n = getLength();
|
||||
}
|
||||
System.arraycopy(buff, start, src, off, n);
|
||||
start += n;
|
||||
return n;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send the buffer to the sink. Called by append() when the limit is
|
||||
* reached. You can also call it explicitly to force the data to be written.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public void flushBuffer()
|
||||
throws IOException
|
||||
{
|
||||
//assert out!=null
|
||||
if( out==null ) {
|
||||
throw new IOException( "Buffer overflow, no sink " + limit + " " +
|
||||
buff.length );
|
||||
}
|
||||
out.realWriteBytes( buff, start, end-start );
|
||||
end=start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make space for len chars. If len is small, allocate a reserve space too.
|
||||
* Never grow bigger than limit.
|
||||
*/
|
||||
public void makeSpace(int count) {
|
||||
byte[] tmp = null;
|
||||
|
||||
int newSize;
|
||||
int desiredSize=end + count;
|
||||
|
||||
// Can't grow above the limit
|
||||
if( limit > 0 &&
|
||||
desiredSize > limit) {
|
||||
desiredSize=limit;
|
||||
}
|
||||
|
||||
if( buff==null ) {
|
||||
if( desiredSize < 256 )
|
||||
{
|
||||
desiredSize=256; // take a minimum
|
||||
}
|
||||
buff=new byte[desiredSize];
|
||||
}
|
||||
|
||||
// limit < buf.length ( the buffer is already big )
|
||||
// or we already have space XXX
|
||||
if( desiredSize <= buff.length ) {
|
||||
return;
|
||||
}
|
||||
// grow in larger chunks
|
||||
if( desiredSize < 2 * buff.length ) {
|
||||
newSize= buff.length * 2;
|
||||
if( limit >0 &&
|
||||
newSize > limit ) {
|
||||
newSize=limit;
|
||||
}
|
||||
tmp=new byte[newSize];
|
||||
} else {
|
||||
newSize= buff.length * 2 + count ;
|
||||
if( limit > 0 &&
|
||||
newSize > limit ) {
|
||||
newSize=limit;
|
||||
}
|
||||
tmp=new byte[newSize];
|
||||
}
|
||||
|
||||
System.arraycopy(buff, start, tmp, 0, end-start);
|
||||
buff = tmp;
|
||||
tmp = null;
|
||||
end=end-start;
|
||||
start=0;
|
||||
}
|
||||
|
||||
// -------------------- Conversion and getters --------------------
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (null == buff) {
|
||||
return null;
|
||||
} else if (end-start == 0) {
|
||||
return "";
|
||||
}
|
||||
return StringCache.toString(this);
|
||||
}
|
||||
|
||||
public String toStringInternal() {
|
||||
if (charset == null) {
|
||||
charset = DEFAULT_CHARSET;
|
||||
}
|
||||
// new String(byte[], int, int, Charset) takes a defensive copy of the
|
||||
// entire byte array. This is expensive if only a small subset of the
|
||||
// bytes will be used. The code below is from Apache Harmony.
|
||||
CharBuffer cb;
|
||||
cb = charset.decode(ByteBuffer.wrap(buff, start, end-start));
|
||||
return new String(cb.array(), cb.arrayOffset(), cb.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public int getInt()
|
||||
{
|
||||
return Ascii.parseInt(buff, start,end-start);
|
||||
}
|
||||
|
||||
public long getLong() {
|
||||
return Ascii.parseLong(buff, start,end-start);
|
||||
}
|
||||
|
||||
|
||||
// -------------------- equals --------------------
|
||||
|
||||
/**
|
||||
* Compares the message bytes to the specified String object.
|
||||
* @param s the String to compare
|
||||
* @return true if the comparison succeeded, false otherwise
|
||||
*/
|
||||
public boolean equals(String s) {
|
||||
// XXX ENCODING - this only works if encoding is UTF8-compat
|
||||
// ( ok for tomcat, where we compare ascii - header names, etc )!!!
|
||||
|
||||
byte[] b = buff;
|
||||
int blen = end-start;
|
||||
if (b == null || blen != s.length()) {
|
||||
return false;
|
||||
}
|
||||
int boff = start;
|
||||
for (int i = 0; i < blen; i++) {
|
||||
if (b[boff++] != s.charAt(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the message bytes to the specified String object.
|
||||
* @param s the String to compare
|
||||
* @return true if the comparison succeeded, false otherwise
|
||||
*/
|
||||
public boolean equalsIgnoreCase(String s) {
|
||||
byte[] b = buff;
|
||||
int blen = end-start;
|
||||
if (b == null || blen != s.length()) {
|
||||
return false;
|
||||
}
|
||||
int boff = start;
|
||||
for (int i = 0; i < blen; i++) {
|
||||
if (Ascii.toLower(b[boff++]) != Ascii.toLower(s.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean equals( ByteChunk bb ) {
|
||||
return equals( bb.getBytes(), bb.getStart(), bb.getLength());
|
||||
}
|
||||
|
||||
public boolean equals( byte b2[], int off2, int len2) {
|
||||
byte b1[]=buff;
|
||||
if( b1==null && b2==null ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int len=end-start;
|
||||
if ( len2 != len || b1==null || b2==null ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int off1 = start;
|
||||
|
||||
while ( len-- > 0) {
|
||||
if (b1[off1++] != b2[off2++]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean equals( CharChunk cc ) {
|
||||
return equals( cc.getChars(), cc.getStart(), cc.getLength());
|
||||
}
|
||||
|
||||
public boolean equals( char c2[], int off2, int len2) {
|
||||
// XXX works only for enc compatible with ASCII/UTF !!!
|
||||
byte b1[]=buff;
|
||||
if( c2==null && b1==null ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (b1== null || c2==null || end-start != len2 ) {
|
||||
return false;
|
||||
}
|
||||
int off1 = start;
|
||||
int len=end-start;
|
||||
|
||||
while ( len-- > 0) {
|
||||
if ( (char)b1[off1++] != c2[off2++]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the message bytes starts with the specified string.
|
||||
* @param s the string
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean startsWith(String s) {
|
||||
// Works only if enc==UTF
|
||||
byte[] b = buff;
|
||||
int blen = s.length();
|
||||
if (b == null || blen > end-start) {
|
||||
return false;
|
||||
}
|
||||
int boff = start;
|
||||
for (int i = 0; i < blen; i++) {
|
||||
if (b[boff++] != s.charAt(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the message bytes start with the specified byte array.
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean startsWith(byte[] b2) {
|
||||
byte[] b1 = buff;
|
||||
if (b1 == null && b2 == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int len = end - start;
|
||||
if (b1 == null || b2 == null || b2.length > len) {
|
||||
return false;
|
||||
}
|
||||
for (int i = start, j = 0; i < end && j < b2.length;) {
|
||||
if (b1[i++] != b2[j++]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the message bytes starts with the specified string.
|
||||
* @param s the string
|
||||
* @param pos The position
|
||||
*/
|
||||
public boolean startsWithIgnoreCase(String s, int pos) {
|
||||
byte[] b = buff;
|
||||
int len = s.length();
|
||||
if (b == null || len+pos > end-start) {
|
||||
return false;
|
||||
}
|
||||
int off = start+pos;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (Ascii.toLower( b[off++] ) != Ascii.toLower( s.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public int indexOf( String src, int srcOff, int srcLen, int myOff ) {
|
||||
char first=src.charAt( srcOff );
|
||||
|
||||
// Look for first char
|
||||
int srcEnd = srcOff + srcLen;
|
||||
|
||||
mainLoop:
|
||||
for( int i=myOff+start; i <= (end - srcLen); i++ ) {
|
||||
if( buff[i] != first ) {
|
||||
continue;
|
||||
}
|
||||
// found first char, now look for a match
|
||||
int myPos=i+1;
|
||||
for( int srcPos=srcOff + 1; srcPos< srcEnd;) {
|
||||
if( buff[myPos++] != src.charAt( srcPos++ )) {
|
||||
continue mainLoop;
|
||||
}
|
||||
}
|
||||
return i-start; // found it
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// -------------------- Hash code --------------------
|
||||
|
||||
// normal hash.
|
||||
public int hash() {
|
||||
return hashBytes( buff, start, end-start);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public int hashIgnoreCase() {
|
||||
return hashBytesIC( buff, start, end-start );
|
||||
}
|
||||
|
||||
private static int hashBytes( byte buff[], int start, int bytesLen ) {
|
||||
int max=start+bytesLen;
|
||||
byte bb[]=buff;
|
||||
int code=0;
|
||||
for (int i = start; i < max ; i++) {
|
||||
code = code * 37 + bb[i];
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
private static int hashBytesIC( byte bytes[], int start,
|
||||
int bytesLen )
|
||||
{
|
||||
int max=start+bytesLen;
|
||||
byte bb[]=bytes;
|
||||
int code=0;
|
||||
for (int i = start; i < max ; i++) {
|
||||
code = code * 37 + Ascii.toLower(bb[i]);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first instance of the given character in this ByteChunk
|
||||
* starting at the specified byte. If the character is not found, -1 is
|
||||
* returned.
|
||||
* <br/>
|
||||
* NOTE: This only works for characters in the range 0-127.
|
||||
*
|
||||
* @param c The character
|
||||
* @param starting The start position
|
||||
* @return The position of the first instance of the character or
|
||||
* -1 if the character is not found.
|
||||
*/
|
||||
public int indexOf(char c, int starting) {
|
||||
int ret = indexOf(buff, start + starting, end, c);
|
||||
return (ret >= start) ? ret - start : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first instance of the given character in the given byte array
|
||||
* between the specified start and end.
|
||||
* <br/>
|
||||
* NOTE: This only works for characters in the range 0-127.
|
||||
*
|
||||
* @param bytes The byte array to search
|
||||
* @param start The point to start searching from in the byte array
|
||||
* @param end The point to stop searching in the byte array
|
||||
* @param c The character to search for
|
||||
* @return The position of the first instance of the character or -1
|
||||
* if the character is not found.
|
||||
*/
|
||||
public static int indexOf(byte bytes[], int start, int end, char c) {
|
||||
int offset = start;
|
||||
|
||||
while (offset < end) {
|
||||
byte b=bytes[offset];
|
||||
if (b == c) {
|
||||
return offset;
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first instance of the given byte in the byte array between
|
||||
* the specified start and end.
|
||||
*
|
||||
* @param bytes The byte array to search
|
||||
* @param start The point to start searching from in the byte array
|
||||
* @param end The point to stop searching in the byte array
|
||||
* @param b The byte to search for
|
||||
* @return The position of the first instance of the byte or -1 if the
|
||||
* byte is not found.
|
||||
*/
|
||||
public static int findByte(byte bytes[], int start, int end, byte b) {
|
||||
int offset = start;
|
||||
while (offset < end) {
|
||||
if (bytes[offset] == b) {
|
||||
return offset;
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first instance of any of the given bytes in the byte array
|
||||
* between the specified start and end.
|
||||
*
|
||||
* @param bytes The byte array to search
|
||||
* @param start The point to start searching from in the byte array
|
||||
* @param end The point to stop searching in the byte array
|
||||
* @param b The array of bytes to search for
|
||||
* @return The position of the first instance of the byte or -1 if the
|
||||
* byte is not found.
|
||||
*/
|
||||
public static int findBytes(byte bytes[], int start, int end, byte b[]) {
|
||||
int blen = b.length;
|
||||
int offset = start;
|
||||
while (offset < end) {
|
||||
for (int i = 0; i < blen; i++) {
|
||||
if (bytes[offset] == b[i]) {
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first instance of any byte that is not one of the given bytes
|
||||
* in the byte array between the specified start and end.
|
||||
*
|
||||
* @param bytes The byte array to search
|
||||
* @param start The point to start searching from in the byte array
|
||||
* @param end The point to stop searching in the byte array
|
||||
* @param b The list of bytes to search for
|
||||
* @return The position of the first instance a byte that is not
|
||||
* in the list of bytes to search for or -1 if no such byte
|
||||
* is found.
|
||||
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
|
||||
*/
|
||||
@Deprecated
|
||||
public static int findNotBytes(byte bytes[], int start, int end, byte b[]) {
|
||||
int blen = b.length;
|
||||
int offset = start;
|
||||
boolean found;
|
||||
|
||||
while (offset < end) {
|
||||
found = true;
|
||||
for (int i = 0; i < blen; i++) {
|
||||
if (bytes[offset] == b[i]) {
|
||||
found=false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
return offset;
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert specified String to a byte array. This ONLY WORKS for ascii, UTF
|
||||
* chars will be truncated.
|
||||
*
|
||||
* @param value to convert to byte array
|
||||
* @return the byte array value
|
||||
*/
|
||||
public static final byte[] convertToBytes(String value) {
|
||||
byte[] result = new byte[value.length()];
|
||||
for (int i = 0; i < value.length(); i++) {
|
||||
result[i] = (byte) value.charAt(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,700 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.struts2.util.tomcat.buf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Utilities to manipulate char chunks. While String is
|
||||
* the easiest way to manipulate chars ( search, substrings, etc),
|
||||
* it is known to not be the most efficient solution - Strings are
|
||||
* designed as immutable and secure objects.
|
||||
*
|
||||
* @author dac@sun.com
|
||||
* @author James Todd [gonzo@sun.com]
|
||||
* @author Costin Manolache
|
||||
* @author Remy Maucherat
|
||||
*/
|
||||
public final class CharChunk implements Cloneable, Serializable, CharSequence {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// Input interface, used when the buffer is emptied.
|
||||
public static interface CharInputChannel {
|
||||
/**
|
||||
* Read new bytes ( usually the internal conversion buffer ).
|
||||
* The implementation is allowed to ignore the parameters,
|
||||
* and mutate the chunk if it wishes to implement its own buffering.
|
||||
*/
|
||||
public int realReadChars(char cbuf[], int off, int len)
|
||||
throws IOException;
|
||||
}
|
||||
/**
|
||||
* When we need more space we'll either
|
||||
* grow the buffer ( up to the limit ) or send it to a channel.
|
||||
*/
|
||||
public static interface CharOutputChannel {
|
||||
/** Send the bytes ( usually the internal conversion buffer ).
|
||||
* Expect 8k output if the buffer is full.
|
||||
*/
|
||||
public void realWriteChars(char cbuf[], int off, int len)
|
||||
throws IOException;
|
||||
}
|
||||
|
||||
// --------------------
|
||||
|
||||
private int hashCode = 0;
|
||||
// did we compute the hashcode ?
|
||||
private boolean hasHashCode = false;
|
||||
|
||||
// char[]
|
||||
private char buff[];
|
||||
|
||||
private int start;
|
||||
private int end;
|
||||
|
||||
private boolean isSet=false; // XXX
|
||||
|
||||
// -1: grow indefinitely
|
||||
// maximum amount to be cached
|
||||
private int limit=-1;
|
||||
|
||||
private CharInputChannel in = null;
|
||||
private CharOutputChannel out = null;
|
||||
|
||||
private boolean optimizedWrite=true;
|
||||
|
||||
/**
|
||||
* Creates a new, uninitialized CharChunk object.
|
||||
*/
|
||||
public CharChunk() {
|
||||
}
|
||||
|
||||
public CharChunk(int size) {
|
||||
allocate( size, -1 );
|
||||
}
|
||||
|
||||
// --------------------
|
||||
|
||||
public boolean isNull() {
|
||||
if( end > 0 ) {
|
||||
return false;
|
||||
}
|
||||
return !isSet; //XXX
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the message bytes to an uninitialized state.
|
||||
*/
|
||||
public void recycle() {
|
||||
// buff=null;
|
||||
isSet=false; // XXX
|
||||
hasHashCode = false;
|
||||
start=0;
|
||||
end=0;
|
||||
}
|
||||
|
||||
// -------------------- Setup --------------------
|
||||
|
||||
public void allocate( int initial, int limit ) {
|
||||
if( buff==null || buff.length < initial ) {
|
||||
buff=new char[initial];
|
||||
}
|
||||
this.limit=limit;
|
||||
start=0;
|
||||
end=0;
|
||||
isSet=true;
|
||||
hasHashCode = false;
|
||||
}
|
||||
|
||||
|
||||
public void setOptimizedWrite(boolean optimizedWrite) {
|
||||
this.optimizedWrite = optimizedWrite;
|
||||
}
|
||||
|
||||
public void setChars( char[] c, int off, int len ) {
|
||||
buff=c;
|
||||
start=off;
|
||||
end=start + len;
|
||||
isSet=true;
|
||||
hasHashCode = false;
|
||||
}
|
||||
|
||||
/** Maximum amount of data in this buffer.
|
||||
*
|
||||
* If -1 or not set, the buffer will grow indefinitely.
|
||||
* Can be smaller than the current buffer size ( which will not shrink ).
|
||||
* When the limit is reached, the buffer will be flushed ( if out is set )
|
||||
* or throw exception.
|
||||
*/
|
||||
public void setLimit(int limit) {
|
||||
this.limit=limit;
|
||||
}
|
||||
|
||||
public int getLimit() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the buffer is empty, read the data from the input channel.
|
||||
*/
|
||||
public void setCharInputChannel(CharInputChannel in) {
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
/** When the buffer is full, write the data to the output channel.
|
||||
* Also used when large amount of data is appended.
|
||||
*
|
||||
* If not set, the buffer will grow to the limit.
|
||||
*/
|
||||
public void setCharOutputChannel(CharOutputChannel out) {
|
||||
this.out=out;
|
||||
}
|
||||
|
||||
// compat
|
||||
public char[] getChars()
|
||||
{
|
||||
return getBuffer();
|
||||
}
|
||||
|
||||
public char[] getBuffer()
|
||||
{
|
||||
return buff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start offset of the bytes.
|
||||
* For output this is the end of the buffer.
|
||||
*/
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start offset of the bytes.
|
||||
*/
|
||||
public void setOffset(int off) {
|
||||
start=off;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of the bytes.
|
||||
*/
|
||||
public int getLength() {
|
||||
return end-start;
|
||||
}
|
||||
|
||||
|
||||
public int getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public void setEnd( int i ) {
|
||||
end=i;
|
||||
}
|
||||
|
||||
// -------------------- Adding data --------------------
|
||||
|
||||
public void append( char b )
|
||||
throws IOException
|
||||
{
|
||||
makeSpace( 1 );
|
||||
|
||||
// couldn't make space
|
||||
if( limit >0 && end >= limit ) {
|
||||
flushBuffer();
|
||||
}
|
||||
buff[end++]=b;
|
||||
}
|
||||
|
||||
public void append( CharChunk src )
|
||||
throws IOException
|
||||
{
|
||||
append( src.getBuffer(), src.getOffset(), src.getLength());
|
||||
}
|
||||
|
||||
/** Add data to the buffer
|
||||
*/
|
||||
public void append( char src[], int off, int len )
|
||||
throws IOException
|
||||
{
|
||||
// will grow, up to limit
|
||||
makeSpace( len );
|
||||
|
||||
// if we don't have limit: makeSpace can grow as it wants
|
||||
if( limit < 0 ) {
|
||||
// assert: makeSpace made enough space
|
||||
System.arraycopy( src, off, buff, end, len );
|
||||
end+=len;
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimize on a common case.
|
||||
// If the source is going to fill up all the space in buffer, may
|
||||
// as well write it directly to the output, and avoid an extra copy
|
||||
if ( optimizedWrite && len == limit && end == start && out != null ) {
|
||||
out.realWriteChars( src, off, len );
|
||||
return;
|
||||
}
|
||||
|
||||
// if we have limit and we're below
|
||||
if( len <= limit - end ) {
|
||||
// makeSpace will grow the buffer to the limit,
|
||||
// so we have space
|
||||
System.arraycopy( src, off, buff, end, len );
|
||||
|
||||
end+=len;
|
||||
return;
|
||||
}
|
||||
|
||||
// need more space than we can afford, need to flush
|
||||
// buffer
|
||||
|
||||
// the buffer is already at ( or bigger than ) limit
|
||||
|
||||
// Optimization:
|
||||
// If len-avail < length ( i.e. after we fill the buffer with
|
||||
// what we can, the remaining will fit in the buffer ) we'll just
|
||||
// copy the first part, flush, then copy the second part - 1 write
|
||||
// and still have some space for more. We'll still have 2 writes, but
|
||||
// we write more on the first.
|
||||
|
||||
if( len + end < 2 * limit ) {
|
||||
/* If the request length exceeds the size of the output buffer,
|
||||
flush the output buffer and then write the data directly.
|
||||
We can't avoid 2 writes, but we can write more on the second
|
||||
*/
|
||||
int avail=limit-end;
|
||||
System.arraycopy(src, off, buff, end, avail);
|
||||
end += avail;
|
||||
|
||||
flushBuffer();
|
||||
|
||||
System.arraycopy(src, off+avail, buff, end, len - avail);
|
||||
end+= len - avail;
|
||||
|
||||
} else { // len > buf.length + avail
|
||||
// long write - flush the buffer and write the rest
|
||||
// directly from source
|
||||
flushBuffer();
|
||||
|
||||
out.realWriteChars( src, off, len );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Append a string to the buffer
|
||||
*/
|
||||
public void append(String s) throws IOException {
|
||||
append(s, 0, s.length());
|
||||
}
|
||||
|
||||
/** Append a string to the buffer
|
||||
*/
|
||||
public void append(String s, int off, int len) throws IOException {
|
||||
if (s==null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// will grow, up to limit
|
||||
makeSpace( len );
|
||||
|
||||
// if we don't have limit: makeSpace can grow as it wants
|
||||
if( limit < 0 ) {
|
||||
// assert: makeSpace made enough space
|
||||
s.getChars(off, off+len, buff, end );
|
||||
end+=len;
|
||||
return;
|
||||
}
|
||||
|
||||
int sOff = off;
|
||||
int sEnd = off + len;
|
||||
while (sOff < sEnd) {
|
||||
int d = min(limit - end, sEnd - sOff);
|
||||
s.getChars( sOff, sOff+d, buff, end);
|
||||
sOff += d;
|
||||
end += d;
|
||||
if (end >= limit) {
|
||||
flushBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------- Removing data from the buffer --------------------
|
||||
|
||||
public int substract()
|
||||
throws IOException {
|
||||
|
||||
if ((end - start) == 0) {
|
||||
if (in == null) {
|
||||
return -1;
|
||||
}
|
||||
int n = in.realReadChars(buff, end, buff.length - end);
|
||||
if (n < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return (buff[start++]);
|
||||
|
||||
}
|
||||
|
||||
public int substract( char src[], int off, int len )
|
||||
throws IOException {
|
||||
|
||||
if ((end - start) == 0) {
|
||||
if (in == null) {
|
||||
return -1;
|
||||
}
|
||||
int n = in.realReadChars( buff, end, buff.length - end);
|
||||
if (n < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int n = len;
|
||||
if (len > getLength()) {
|
||||
n = getLength();
|
||||
}
|
||||
System.arraycopy(buff, start, src, off, n);
|
||||
start += n;
|
||||
return n;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void flushBuffer()
|
||||
throws IOException
|
||||
{
|
||||
//assert out!=null
|
||||
if( out==null ) {
|
||||
throw new IOException( "Buffer overflow, no sink " + limit + " " +
|
||||
buff.length );
|
||||
}
|
||||
out.realWriteChars( buff, start, end - start );
|
||||
end=start;
|
||||
}
|
||||
|
||||
/** Make space for len chars. If len is small, allocate
|
||||
* a reserve space too. Never grow bigger than limit.
|
||||
*/
|
||||
public void makeSpace(int count)
|
||||
{
|
||||
char[] tmp = null;
|
||||
|
||||
int newSize;
|
||||
int desiredSize=end + count;
|
||||
|
||||
// Can't grow above the limit
|
||||
if( limit > 0 &&
|
||||
desiredSize > limit) {
|
||||
desiredSize=limit;
|
||||
}
|
||||
|
||||
if( buff==null ) {
|
||||
if( desiredSize < 256 )
|
||||
{
|
||||
desiredSize=256; // take a minimum
|
||||
}
|
||||
buff=new char[desiredSize];
|
||||
}
|
||||
|
||||
// limit < buf.length ( the buffer is already big )
|
||||
// or we already have space XXX
|
||||
if( desiredSize <= buff.length) {
|
||||
return;
|
||||
}
|
||||
// grow in larger chunks
|
||||
if( desiredSize < 2 * buff.length ) {
|
||||
newSize= buff.length * 2;
|
||||
if( limit >0 &&
|
||||
newSize > limit ) {
|
||||
newSize=limit;
|
||||
}
|
||||
tmp=new char[newSize];
|
||||
} else {
|
||||
newSize= buff.length * 2 + count ;
|
||||
if( limit > 0 &&
|
||||
newSize > limit ) {
|
||||
newSize=limit;
|
||||
}
|
||||
tmp=new char[newSize];
|
||||
}
|
||||
|
||||
System.arraycopy(buff, 0, tmp, 0, end);
|
||||
buff = tmp;
|
||||
tmp = null;
|
||||
}
|
||||
|
||||
// -------------------- Conversion and getters --------------------
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (null == buff) {
|
||||
return null;
|
||||
} else if (end-start == 0) {
|
||||
return "";
|
||||
}
|
||||
return StringCache.toString(this);
|
||||
}
|
||||
|
||||
public String toStringInternal() {
|
||||
return new String(buff, start, end-start);
|
||||
}
|
||||
|
||||
// -------------------- equals --------------------
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof CharChunk) {
|
||||
return equals((CharChunk) obj);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the message bytes to the specified String object.
|
||||
* @param s the String to compare
|
||||
* @return true if the comparison succeeded, false otherwise
|
||||
*/
|
||||
public boolean equals(String s) {
|
||||
char[] c = buff;
|
||||
int len = end-start;
|
||||
if (c == null || len != s.length()) {
|
||||
return false;
|
||||
}
|
||||
int off = start;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (c[off++] != s.charAt(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the message bytes to the specified String object.
|
||||
* @param s the String to compare
|
||||
* @return true if the comparison succeeded, false otherwise
|
||||
*/
|
||||
public boolean equalsIgnoreCase(String s) {
|
||||
char[] c = buff;
|
||||
int len = end-start;
|
||||
if (c == null || len != s.length()) {
|
||||
return false;
|
||||
}
|
||||
int off = start;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (Ascii.toLower( c[off++] ) != Ascii.toLower( s.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean equals(CharChunk cc) {
|
||||
return equals( cc.getChars(), cc.getOffset(), cc.getLength());
|
||||
}
|
||||
|
||||
public boolean equals(char b2[], int off2, int len2) {
|
||||
char b1[]=buff;
|
||||
if( b1==null && b2==null ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (b1== null || b2==null || end-start != len2) {
|
||||
return false;
|
||||
}
|
||||
int off1 = start;
|
||||
int len=end-start;
|
||||
while ( len-- > 0) {
|
||||
if (b1[off1++] != b2[off2++]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the message bytes starts with the specified string.
|
||||
* @param s the string
|
||||
*/
|
||||
public boolean startsWith(String s) {
|
||||
char[] c = buff;
|
||||
int len = s.length();
|
||||
if (c == null || len > end-start) {
|
||||
return false;
|
||||
}
|
||||
int off = start;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (c[off++] != s.charAt(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the message bytes starts with the specified string.
|
||||
* @param s the string
|
||||
*/
|
||||
public boolean startsWithIgnoreCase(String s, int pos) {
|
||||
char[] c = buff;
|
||||
int len = s.length();
|
||||
if (c == null || len+pos > end-start) {
|
||||
return false;
|
||||
}
|
||||
int off = start+pos;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (Ascii.toLower( c[off++] ) != Ascii.toLower( s.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the message bytes end with the specified string.
|
||||
* @param s the string
|
||||
*/
|
||||
public boolean endsWith(String s) {
|
||||
char[] c = buff;
|
||||
int len = s.length();
|
||||
if (c == null || len > end-start) {
|
||||
return false;
|
||||
}
|
||||
int off = end - len;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (c[off++] != s.charAt(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// -------------------- Hash code --------------------
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (hasHashCode) {
|
||||
return hashCode;
|
||||
}
|
||||
int code = 0;
|
||||
|
||||
code = hash();
|
||||
hashCode = code;
|
||||
hasHashCode = true;
|
||||
return code;
|
||||
}
|
||||
|
||||
// normal hash.
|
||||
public int hash() {
|
||||
int code=0;
|
||||
for (int i = start; i < start + end-start; i++) {
|
||||
code = code * 37 + buff[i];
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
public int indexOf(char c) {
|
||||
return indexOf( c, start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the message bytes starts with the specified string.
|
||||
* @param c the character
|
||||
*/
|
||||
public int indexOf(char c, int starting) {
|
||||
int ret = indexOf( buff, start+starting, end, c );
|
||||
return (ret >= start) ? ret - start : -1;
|
||||
}
|
||||
|
||||
public static int indexOf( char chars[], int off, int cend, char qq )
|
||||
{
|
||||
while( off < cend ) {
|
||||
char b=chars[off];
|
||||
if( b==qq ) {
|
||||
return off;
|
||||
}
|
||||
off++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
public int indexOf(String src, int srcOff, int srcLen, int myOff ) {
|
||||
char first=src.charAt( srcOff );
|
||||
|
||||
// Look for first char
|
||||
int srcEnd = srcOff + srcLen;
|
||||
|
||||
for( int i=myOff+start; i <= (end - srcLen); i++ ) {
|
||||
if( buff[i] != first ) {
|
||||
continue;
|
||||
}
|
||||
// found first char, now look for a match
|
||||
int myPos=i+1;
|
||||
for( int srcPos=srcOff + 1; srcPos< srcEnd;) {
|
||||
if( buff[myPos++] != src.charAt( srcPos++ )) {
|
||||
break;
|
||||
}
|
||||
if( srcPos==srcEnd )
|
||||
{
|
||||
return i-start; // found it
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// -------------------- utils
|
||||
private int min(int a, int b) {
|
||||
if (a < b) {
|
||||
return a;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
// Char sequence impl
|
||||
|
||||
public char charAt(int index) {
|
||||
return buff[index + start];
|
||||
}
|
||||
|
||||
public CharSequence subSequence(int start, int end) {
|
||||
try {
|
||||
CharChunk result = (CharChunk) this.clone();
|
||||
result.setOffset(this.start + start);
|
||||
result.setEnd(this.start + end);
|
||||
return result;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
// Cannot happen
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return end - start;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.struts2.util.tomcat.buf;
|
||||
|
||||
/**
|
||||
* Tables useful when converting byte arrays to and from strings of hexadecimal
|
||||
* digits.
|
||||
* Code from Ajp11, from Apache's JServ.
|
||||
*
|
||||
* @author Craig R. McClanahan
|
||||
*/
|
||||
public final class HexUtils {
|
||||
|
||||
// -------------------------------------------------------------- Constants
|
||||
|
||||
/**
|
||||
* Table for HEX to DEC byte translation.
|
||||
*/
|
||||
private static final int[] DEC = {
|
||||
00, 01, 02, 03, 04, 05, 06, 07, 8, 9, -1, -1, -1, -1, -1, -1,
|
||||
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, 10, 11, 12, 13, 14, 15,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Table for DEC to HEX byte translation.
|
||||
*/
|
||||
private static final byte[] HEX =
|
||||
{ (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5',
|
||||
(byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b',
|
||||
(byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f' };
|
||||
|
||||
|
||||
/**
|
||||
* Table for byte to hex string translation.
|
||||
*/
|
||||
private static final char[] hex = "0123456789abcdef".toCharArray();
|
||||
|
||||
|
||||
// --------------------------------------------------------- Static Methods
|
||||
|
||||
public static int getDec(int index) {
|
||||
// Fast for correct values, slower for incorrect ones
|
||||
try {
|
||||
return DEC[index - '0'];
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static byte getHex(int index) {
|
||||
return HEX[index];
|
||||
}
|
||||
|
||||
|
||||
public static String toHexString(byte[] bytes) {
|
||||
if (null == bytes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder(bytes.length << 1);
|
||||
|
||||
for(int i = 0; i < bytes.length; ++i) {
|
||||
sb.append(hex[(bytes[i] & 0xf0) >> 4])
|
||||
.append(hex[(bytes[i] & 0x0f)])
|
||||
;
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
public static byte[] fromHexString(String input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((input.length() & 1) == 1) {
|
||||
// Odd number of characters
|
||||
throw new IllegalArgumentException("The input must consist of an even number of hex digits");
|
||||
}
|
||||
|
||||
char[] inputChars = input.toCharArray();
|
||||
byte[] result = new byte[input.length() >> 1];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
int upperNibble = getDec(inputChars[2*i]);
|
||||
int lowerNibble = getDec(inputChars[2*i + 1]);
|
||||
if (upperNibble < 0 || lowerNibble < 0) {
|
||||
// Non hex character
|
||||
throw new IllegalArgumentException("The input must consist only of hex digits");
|
||||
}
|
||||
result[i] = (byte) ((upperNibble << 4) + lowerNibble);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,546 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.struts2.util.tomcat.buf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* This class is used to represent a subarray of bytes in an HTTP message.
|
||||
* It represents all request/response elements. The byte/char conversions are
|
||||
* delayed and cached. Everything is recyclable.
|
||||
*
|
||||
* The object can represent a byte[], a char[], or a (sub) String. All
|
||||
* operations can be made in case sensitive mode or not.
|
||||
*
|
||||
* @author dac@eng.sun.com
|
||||
* @author James Todd [gonzo@eng.sun.com]
|
||||
* @author Costin Manolache
|
||||
*/
|
||||
public final class MessageBytes implements Cloneable, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// primary type ( whatever is set as original value )
|
||||
private int type = T_NULL;
|
||||
|
||||
public static final int T_NULL = 0;
|
||||
/** getType() is T_STR if the the object used to create the MessageBytes
|
||||
was a String */
|
||||
public static final int T_STR = 1;
|
||||
/** getType() is T_STR if the the object used to create the MessageBytes
|
||||
was a byte[] */
|
||||
public static final int T_BYTES = 2;
|
||||
/** getType() is T_STR if the the object used to create the MessageBytes
|
||||
was a char[] */
|
||||
public static final int T_CHARS = 3;
|
||||
|
||||
private int hashCode=0;
|
||||
// did we compute the hashcode ?
|
||||
private boolean hasHashCode=false;
|
||||
|
||||
// Internal objects to represent array + offset, and specific methods
|
||||
private final ByteChunk byteC=new ByteChunk();
|
||||
private final CharChunk charC=new CharChunk();
|
||||
|
||||
// String
|
||||
private String strValue;
|
||||
// true if a String value was computed. Probably not needed,
|
||||
// strValue!=null is the same
|
||||
private boolean hasStrValue=false;
|
||||
|
||||
/**
|
||||
* Creates a new, uninitialized MessageBytes object.
|
||||
* Use static newInstance() in order to allow
|
||||
* future hooks.
|
||||
*/
|
||||
private MessageBytes() {
|
||||
}
|
||||
|
||||
/** Construct a new MessageBytes instance
|
||||
*/
|
||||
public static MessageBytes newInstance() {
|
||||
return factory.newInstance();
|
||||
}
|
||||
|
||||
public boolean isNull() {
|
||||
// should we check also hasStrValue ???
|
||||
return byteC.isNull() && charC.isNull() && ! hasStrValue;
|
||||
// bytes==null && strValue==null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the message bytes to an uninitialized (NULL) state.
|
||||
*/
|
||||
public void recycle() {
|
||||
type=T_NULL;
|
||||
byteC.recycle();
|
||||
charC.recycle();
|
||||
|
||||
strValue=null;
|
||||
|
||||
hasStrValue=false;
|
||||
hasHashCode=false;
|
||||
hasLongValue=false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the content to the specified subarray of bytes.
|
||||
*
|
||||
* @param b the bytes
|
||||
* @param off the start offset of the bytes
|
||||
* @param len the length of the bytes
|
||||
*/
|
||||
public void setBytes(byte[] b, int off, int len) {
|
||||
byteC.setBytes( b, off, len );
|
||||
type=T_BYTES;
|
||||
hasStrValue=false;
|
||||
hasHashCode=false;
|
||||
hasLongValue=false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the content to be a char[]
|
||||
*
|
||||
* @param c the bytes
|
||||
* @param off the start offset of the bytes
|
||||
* @param len the length of the bytes
|
||||
*/
|
||||
public void setChars( char[] c, int off, int len ) {
|
||||
charC.setChars( c, off, len );
|
||||
type=T_CHARS;
|
||||
hasStrValue=false;
|
||||
hasHashCode=false;
|
||||
hasLongValue=false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the content to be a string
|
||||
*/
|
||||
public void setString( String s ) {
|
||||
strValue=s;
|
||||
hasHashCode=false;
|
||||
hasLongValue=false;
|
||||
if (s == null) {
|
||||
hasStrValue=false;
|
||||
type=T_NULL;
|
||||
} else {
|
||||
hasStrValue=true;
|
||||
type=T_STR;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------- Conversion and getters --------------------
|
||||
|
||||
/** Compute the string value
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
if( hasStrValue ) {
|
||||
return strValue;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case T_CHARS:
|
||||
strValue=charC.toString();
|
||||
hasStrValue=true;
|
||||
return strValue;
|
||||
case T_BYTES:
|
||||
strValue=byteC.toString();
|
||||
hasStrValue=true;
|
||||
return strValue;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
/** Return the type of the original content. Can be
|
||||
* T_STR, T_BYTES, T_CHARS or T_NULL
|
||||
*/
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the byte chunk, representing the byte[] and offset/length.
|
||||
* Valid only if T_BYTES or after a conversion was made.
|
||||
*/
|
||||
public ByteChunk getByteChunk() {
|
||||
return byteC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the char chunk, representing the char[] and offset/length.
|
||||
* Valid only if T_CHARS or after a conversion was made.
|
||||
*/
|
||||
public CharChunk getCharChunk() {
|
||||
return charC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string value.
|
||||
* Valid only if T_STR or after a conversion was made.
|
||||
*/
|
||||
public String getString() {
|
||||
return strValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Charset used for string<->byte conversions.
|
||||
*/
|
||||
public Charset getCharset() {
|
||||
return byteC.getCharset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Charset used for string<->byte conversions.
|
||||
*/
|
||||
public void setCharset(Charset charset) {
|
||||
byteC.setCharset(charset);
|
||||
}
|
||||
|
||||
/** Do a char->byte conversion.
|
||||
*/
|
||||
public void toBytes() {
|
||||
if (!byteC.isNull()) {
|
||||
type=T_BYTES;
|
||||
return;
|
||||
}
|
||||
toString();
|
||||
type=T_BYTES;
|
||||
Charset charset = byteC.getCharset();
|
||||
ByteBuffer result = charset.encode(strValue);
|
||||
byteC.setBytes(result.array(), result.arrayOffset(), result.limit());
|
||||
}
|
||||
|
||||
/** Convert to char[] and fill the CharChunk.
|
||||
* XXX Not optimized - it converts to String first.
|
||||
*/
|
||||
public void toChars() {
|
||||
if( ! charC.isNull() ) {
|
||||
type=T_CHARS;
|
||||
return;
|
||||
}
|
||||
// inefficient
|
||||
toString();
|
||||
type=T_CHARS;
|
||||
char cc[]=strValue.toCharArray();
|
||||
charC.setChars(cc, 0, cc.length);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the length of the original buffer.
|
||||
* Note that the length in bytes may be different from the length
|
||||
* in chars.
|
||||
*/
|
||||
public int getLength() {
|
||||
if(type==T_BYTES) {
|
||||
return byteC.getLength();
|
||||
}
|
||||
if(type==T_CHARS) {
|
||||
return charC.getLength();
|
||||
}
|
||||
if(type==T_STR) {
|
||||
return strValue.length();
|
||||
}
|
||||
toString();
|
||||
if( strValue==null ) {
|
||||
return 0;
|
||||
}
|
||||
return strValue.length();
|
||||
}
|
||||
|
||||
// -------------------- equals --------------------
|
||||
|
||||
/**
|
||||
* Compares the message bytes to the specified String object.
|
||||
* @param s the String to compare
|
||||
* @return true if the comparison succeeded, false otherwise
|
||||
*/
|
||||
public boolean equals(String s) {
|
||||
switch (type) {
|
||||
case T_STR:
|
||||
if (strValue == null) {
|
||||
return s == null;
|
||||
}
|
||||
return strValue.equals( s );
|
||||
case T_CHARS:
|
||||
return charC.equals( s );
|
||||
case T_BYTES:
|
||||
return byteC.equals( s );
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the message bytes to the specified String object.
|
||||
* @param s the String to compare
|
||||
* @return true if the comparison succeeded, false otherwise
|
||||
*/
|
||||
public boolean equalsIgnoreCase(String s) {
|
||||
switch (type) {
|
||||
case T_STR:
|
||||
if (strValue == null) {
|
||||
return s == null;
|
||||
}
|
||||
return strValue.equalsIgnoreCase( s );
|
||||
case T_CHARS:
|
||||
return charC.equalsIgnoreCase( s );
|
||||
case T_BYTES:
|
||||
return byteC.equalsIgnoreCase( s );
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof MessageBytes) {
|
||||
return equals((MessageBytes) obj);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean equals(MessageBytes mb) {
|
||||
switch (type) {
|
||||
case T_STR:
|
||||
return mb.equals( strValue );
|
||||
}
|
||||
|
||||
if( mb.type != T_CHARS &&
|
||||
mb.type!= T_BYTES ) {
|
||||
// it's a string or int/date string value
|
||||
return equals( mb.toString() );
|
||||
}
|
||||
|
||||
// mb is either CHARS or BYTES.
|
||||
// this is either CHARS or BYTES
|
||||
// Deal with the 4 cases ( in fact 3, one is symmetric)
|
||||
|
||||
if( mb.type == T_CHARS && type==T_CHARS ) {
|
||||
return charC.equals( mb.charC );
|
||||
}
|
||||
if( mb.type==T_BYTES && type== T_BYTES ) {
|
||||
return byteC.equals( mb.byteC );
|
||||
}
|
||||
if( mb.type== T_CHARS && type== T_BYTES ) {
|
||||
return byteC.equals( mb.charC );
|
||||
}
|
||||
if( mb.type== T_BYTES && type== T_CHARS ) {
|
||||
return mb.byteC.equals( charC );
|
||||
}
|
||||
// can't happen
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the message bytes starts with the specified string.
|
||||
* @param s the string
|
||||
* @param pos The start position
|
||||
*/
|
||||
public boolean startsWithIgnoreCase(String s, int pos) {
|
||||
switch (type) {
|
||||
case T_STR:
|
||||
if( strValue==null ) {
|
||||
return false;
|
||||
}
|
||||
if( strValue.length() < pos + s.length() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for( int i=0; i<s.length(); i++ ) {
|
||||
if( Ascii.toLower( s.charAt( i ) ) !=
|
||||
Ascii.toLower( strValue.charAt( pos + i ))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case T_CHARS:
|
||||
return charC.startsWithIgnoreCase( s, pos );
|
||||
case T_BYTES:
|
||||
return byteC.startsWithIgnoreCase( s, pos );
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -------------------- Hash code --------------------
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if( hasHashCode ) {
|
||||
return hashCode;
|
||||
}
|
||||
int code = 0;
|
||||
|
||||
code=hash();
|
||||
hashCode=code;
|
||||
hasHashCode=true;
|
||||
return code;
|
||||
}
|
||||
|
||||
// normal hash.
|
||||
private int hash() {
|
||||
int code=0;
|
||||
switch (type) {
|
||||
case T_STR:
|
||||
// We need to use the same hash function
|
||||
for (int i = 0; i < strValue.length(); i++) {
|
||||
code = code * 37 + strValue.charAt( i );
|
||||
}
|
||||
return code;
|
||||
case T_CHARS:
|
||||
return charC.hash();
|
||||
case T_BYTES:
|
||||
return byteC.hash();
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Inefficient initial implementation. Will be replaced on the next
|
||||
// round of tune-up
|
||||
public int indexOf(String s, int starting) {
|
||||
toString();
|
||||
return strValue.indexOf( s, starting );
|
||||
}
|
||||
|
||||
// Inefficient initial implementation. Will be replaced on the next
|
||||
// round of tune-up
|
||||
public int indexOf(String s) {
|
||||
return indexOf( s, 0 );
|
||||
}
|
||||
|
||||
public int indexOfIgnoreCase(String s, int starting) {
|
||||
toString();
|
||||
String upper=strValue.toUpperCase(Locale.ENGLISH);
|
||||
String sU=s.toUpperCase(Locale.ENGLISH);
|
||||
return upper.indexOf( sU, starting );
|
||||
}
|
||||
|
||||
/** Copy the src into this MessageBytes, allocating more space if
|
||||
* needed
|
||||
*/
|
||||
public void duplicate( MessageBytes src ) throws IOException
|
||||
{
|
||||
switch( src.getType() ) {
|
||||
case MessageBytes.T_BYTES:
|
||||
type=T_BYTES;
|
||||
ByteChunk bc=src.getByteChunk();
|
||||
byteC.allocate( 2 * bc.getLength(), -1 );
|
||||
byteC.append( bc );
|
||||
break;
|
||||
case MessageBytes.T_CHARS:
|
||||
type=T_CHARS;
|
||||
CharChunk cc=src.getCharChunk();
|
||||
charC.allocate( 2 * cc.getLength(), -1 );
|
||||
charC.append( cc );
|
||||
break;
|
||||
case MessageBytes.T_STR:
|
||||
type=T_STR;
|
||||
String sc=src.getString();
|
||||
this.setString( sc );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------- Deprecated code --------------------
|
||||
// efficient long
|
||||
// XXX used only for headers - shouldn't be stored here.
|
||||
private long longValue;
|
||||
private boolean hasLongValue=false;
|
||||
|
||||
/** Set the buffer to the representation of an long
|
||||
*/
|
||||
public void setLong(long l) {
|
||||
byteC.allocate(32, 64);
|
||||
long current = l;
|
||||
byte[] buf = byteC.getBuffer();
|
||||
int start = 0;
|
||||
int end = 0;
|
||||
if (l == 0) {
|
||||
buf[end++] = (byte) '0';
|
||||
}
|
||||
if (l < 0) {
|
||||
current = -l;
|
||||
buf[end++] = (byte) '-';
|
||||
}
|
||||
while (current > 0) {
|
||||
int digit = (int) (current % 10);
|
||||
current = current / 10;
|
||||
buf[end++] = HexUtils.getHex(digit);
|
||||
}
|
||||
byteC.setOffset(0);
|
||||
byteC.setEnd(end);
|
||||
// Inverting buffer
|
||||
end--;
|
||||
if (l < 0) {
|
||||
start++;
|
||||
}
|
||||
while (end > start) {
|
||||
byte temp = buf[start];
|
||||
buf[start] = buf[end];
|
||||
buf[end] = temp;
|
||||
start++;
|
||||
end--;
|
||||
}
|
||||
longValue=l;
|
||||
hasStrValue=false;
|
||||
hasHashCode=false;
|
||||
hasLongValue=true;
|
||||
type=T_BYTES;
|
||||
}
|
||||
|
||||
// Used for headers conversion
|
||||
/** Convert the buffer to an long, cache the value
|
||||
*/
|
||||
public long getLong() {
|
||||
if( hasLongValue ) {
|
||||
return longValue;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case T_BYTES:
|
||||
longValue=byteC.getLong();
|
||||
break;
|
||||
default:
|
||||
longValue= Long.parseLong(toString());
|
||||
}
|
||||
|
||||
hasLongValue=true;
|
||||
return longValue;
|
||||
|
||||
}
|
||||
|
||||
// -------------------- Future may be different --------------------
|
||||
|
||||
private static final MessageBytesFactory factory=new MessageBytesFactory();
|
||||
|
||||
private static class MessageBytesFactory {
|
||||
protected MessageBytesFactory() {
|
||||
}
|
||||
public MessageBytes newInstance() {
|
||||
return new MessageBytes();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,695 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.struts2.util.tomcat.buf;
|
||||
|
||||
import com.opensymphony.xwork2.util.logging.Logger;
|
||||
import com.opensymphony.xwork2.util.logging.LoggerFactory;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* This class implements a String cache for ByteChunk and CharChunk.
|
||||
*
|
||||
* @author Remy Maucherat
|
||||
*/
|
||||
public class StringCache {
|
||||
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(StringCache.class);
|
||||
|
||||
|
||||
// ------------------------------------------------------- Static Variables
|
||||
|
||||
|
||||
/**
|
||||
* Enabled ?
|
||||
*/
|
||||
protected static boolean byteEnabled = ("true".equals(System.getProperty(
|
||||
"tomcat.util.buf.StringCache.byte.enabled", "false")));
|
||||
|
||||
|
||||
protected static boolean charEnabled = ("true".equals(System.getProperty(
|
||||
"tomcat.util.buf.StringCache.char.enabled", "false")));
|
||||
|
||||
|
||||
protected static int trainThreshold = Integer.parseInt(System.getProperty(
|
||||
"tomcat.util.buf.StringCache.trainThreshold", "20000"));
|
||||
|
||||
|
||||
protected static int cacheSize = Integer.parseInt(System.getProperty(
|
||||
"tomcat.util.buf.StringCache.cacheSize", "200"));
|
||||
|
||||
|
||||
protected static final int maxStringSize =
|
||||
Integer.parseInt(System.getProperty(
|
||||
"tomcat.util.buf.StringCache.maxStringSize", "128"));
|
||||
|
||||
|
||||
/**
|
||||
* Statistics hash map for byte chunk.
|
||||
*/
|
||||
protected static final HashMap<ByteEntry,int[]> bcStats =
|
||||
new HashMap<ByteEntry, int[]>(cacheSize);
|
||||
|
||||
|
||||
/**
|
||||
* toString count for byte chunk.
|
||||
*/
|
||||
protected static int bcCount = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Cache for byte chunk.
|
||||
*/
|
||||
protected static ByteEntry[] bcCache = null;
|
||||
|
||||
|
||||
/**
|
||||
* Statistics hash map for char chunk.
|
||||
*/
|
||||
protected static final HashMap<CharEntry,int[]> ccStats =
|
||||
new HashMap<CharEntry, int[]>(cacheSize);
|
||||
|
||||
|
||||
/**
|
||||
* toString count for char chunk.
|
||||
*/
|
||||
protected static int ccCount = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Cache for char chunk.
|
||||
*/
|
||||
protected static CharEntry[] ccCache = null;
|
||||
|
||||
|
||||
/**
|
||||
* Access count.
|
||||
*/
|
||||
protected static int accessCount = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Hit count.
|
||||
*/
|
||||
protected static int hitCount = 0;
|
||||
|
||||
|
||||
// ------------------------------------------------------------ Properties
|
||||
|
||||
|
||||
/**
|
||||
* @return Returns the cacheSize.
|
||||
*/
|
||||
public int getCacheSize() {
|
||||
return cacheSize;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param cacheSize The cacheSize to set.
|
||||
*/
|
||||
public void setCacheSize(int cacheSize) {
|
||||
StringCache.cacheSize = cacheSize;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Returns the enabled.
|
||||
*/
|
||||
public boolean getByteEnabled() {
|
||||
return byteEnabled;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param byteEnabled The enabled to set.
|
||||
*/
|
||||
public void setByteEnabled(boolean byteEnabled) {
|
||||
StringCache.byteEnabled = byteEnabled;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Returns the enabled.
|
||||
*/
|
||||
public boolean getCharEnabled() {
|
||||
return charEnabled;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param charEnabled The enabled to set.
|
||||
*/
|
||||
public void setCharEnabled(boolean charEnabled) {
|
||||
StringCache.charEnabled = charEnabled;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Returns the trainThreshold.
|
||||
*/
|
||||
public int getTrainThreshold() {
|
||||
return trainThreshold;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param trainThreshold The trainThreshold to set.
|
||||
*/
|
||||
public void setTrainThreshold(int trainThreshold) {
|
||||
StringCache.trainThreshold = trainThreshold;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Returns the accessCount.
|
||||
*/
|
||||
public int getAccessCount() {
|
||||
return accessCount;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Returns the hitCount.
|
||||
*/
|
||||
public int getHitCount() {
|
||||
return hitCount;
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------- Public Static Methods
|
||||
|
||||
|
||||
public void reset() {
|
||||
hitCount = 0;
|
||||
accessCount = 0;
|
||||
synchronized (bcStats) {
|
||||
bcCache = null;
|
||||
bcCount = 0;
|
||||
}
|
||||
synchronized (ccStats) {
|
||||
ccCache = null;
|
||||
ccCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static String toString(ByteChunk bc) {
|
||||
|
||||
// If the cache is null, then either caching is disabled, or we're
|
||||
// still training
|
||||
if (bcCache == null) {
|
||||
String value = bc.toStringInternal();
|
||||
if (byteEnabled && (value.length() < maxStringSize)) {
|
||||
// If training, everything is synced
|
||||
synchronized (bcStats) {
|
||||
// If the cache has been generated on a previous invocation
|
||||
// while waiting for the lock, just return the toString
|
||||
// value we just calculated
|
||||
if (bcCache != null) {
|
||||
return value;
|
||||
}
|
||||
// Two cases: either we just exceeded the train count, in
|
||||
// which case the cache must be created, or we just update
|
||||
// the count for the string
|
||||
if (bcCount > trainThreshold) {
|
||||
long t1 = System.currentTimeMillis();
|
||||
// Sort the entries according to occurrence
|
||||
TreeMap<Integer,ArrayList<ByteEntry>> tempMap =
|
||||
new TreeMap<Integer, ArrayList<ByteEntry>>();
|
||||
for (Entry<ByteEntry,int[]> item : bcStats.entrySet()) {
|
||||
ByteEntry entry = item.getKey();
|
||||
int[] countA = item.getValue();
|
||||
Integer count = Integer.valueOf(countA[0]);
|
||||
// Add to the list for that count
|
||||
ArrayList<ByteEntry> list = tempMap.get(count);
|
||||
if (list == null) {
|
||||
// Create list
|
||||
list = new ArrayList<ByteEntry>();
|
||||
tempMap.put(count, list);
|
||||
}
|
||||
list.add(entry);
|
||||
}
|
||||
// Allocate array of the right size
|
||||
int size = bcStats.size();
|
||||
if (size > cacheSize) {
|
||||
size = cacheSize;
|
||||
}
|
||||
ByteEntry[] tempbcCache = new ByteEntry[size];
|
||||
// Fill it up using an alphabetical order
|
||||
// and a dumb insert sort
|
||||
ByteChunk tempChunk = new ByteChunk();
|
||||
int n = 0;
|
||||
while (n < size) {
|
||||
Object key = tempMap.lastKey();
|
||||
ArrayList<ByteEntry> list = tempMap.get(key);
|
||||
for (int i = 0; i < list.size() && n < size; i++) {
|
||||
ByteEntry entry = list.get(i);
|
||||
tempChunk.setBytes(entry.name, 0,
|
||||
entry.name.length);
|
||||
int insertPos = findClosest(tempChunk,
|
||||
tempbcCache, n);
|
||||
if (insertPos == n) {
|
||||
tempbcCache[n + 1] = entry;
|
||||
} else {
|
||||
System.arraycopy(tempbcCache, insertPos + 1,
|
||||
tempbcCache, insertPos + 2,
|
||||
n - insertPos - 1);
|
||||
tempbcCache[insertPos + 1] = entry;
|
||||
}
|
||||
n++;
|
||||
}
|
||||
tempMap.remove(key);
|
||||
}
|
||||
bcCount = 0;
|
||||
bcStats.clear();
|
||||
bcCache = tempbcCache;
|
||||
if (log.isDebugEnabled()) {
|
||||
long t2 = System.currentTimeMillis();
|
||||
log.debug("ByteCache generation time: " +
|
||||
(t2 - t1) + "ms");
|
||||
}
|
||||
} else {
|
||||
bcCount++;
|
||||
// Allocate new ByteEntry for the lookup
|
||||
ByteEntry entry = new ByteEntry();
|
||||
entry.value = value;
|
||||
int[] count = bcStats.get(entry);
|
||||
if (count == null) {
|
||||
int end = bc.getEnd();
|
||||
int start = bc.getStart();
|
||||
// Create byte array and copy bytes
|
||||
entry.name = new byte[bc.getLength()];
|
||||
System.arraycopy(bc.getBuffer(), start, entry.name,
|
||||
0, end - start);
|
||||
// Set encoding
|
||||
entry.charset = bc.getCharset();
|
||||
// Initialize occurrence count to one
|
||||
count = new int[1];
|
||||
count[0] = 1;
|
||||
// Set in the stats hash map
|
||||
bcStats.put(entry, count);
|
||||
} else {
|
||||
count[0] = count[0] + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
} else {
|
||||
accessCount++;
|
||||
// Find the corresponding String
|
||||
String result = find(bc);
|
||||
if (result == null) {
|
||||
return bc.toStringInternal();
|
||||
}
|
||||
// Note: We don't care about safety for the stats
|
||||
hitCount++;
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static String toString(CharChunk cc) {
|
||||
|
||||
// If the cache is null, then either caching is disabled, or we're
|
||||
// still training
|
||||
if (ccCache == null) {
|
||||
String value = cc.toStringInternal();
|
||||
if (charEnabled && (value.length() < maxStringSize)) {
|
||||
// If training, everything is synced
|
||||
synchronized (ccStats) {
|
||||
// If the cache has been generated on a previous invocation
|
||||
// while waiting for the lock, just return the toString
|
||||
// value we just calculated
|
||||
if (ccCache != null) {
|
||||
return value;
|
||||
}
|
||||
// Two cases: either we just exceeded the train count, in
|
||||
// which case the cache must be created, or we just update
|
||||
// the count for the string
|
||||
if (ccCount > trainThreshold) {
|
||||
long t1 = System.currentTimeMillis();
|
||||
// Sort the entries according to occurrence
|
||||
TreeMap<Integer,ArrayList<CharEntry>> tempMap =
|
||||
new TreeMap<Integer, ArrayList<CharEntry>>();
|
||||
for (Entry<CharEntry,int[]> item : ccStats.entrySet()) {
|
||||
CharEntry entry = item.getKey();
|
||||
int[] countA = item.getValue();
|
||||
Integer count = Integer.valueOf(countA[0]);
|
||||
// Add to the list for that count
|
||||
ArrayList<CharEntry> list = tempMap.get(count);
|
||||
if (list == null) {
|
||||
// Create list
|
||||
list = new ArrayList<CharEntry>();
|
||||
tempMap.put(count, list);
|
||||
}
|
||||
list.add(entry);
|
||||
}
|
||||
// Allocate array of the right size
|
||||
int size = ccStats.size();
|
||||
if (size > cacheSize) {
|
||||
size = cacheSize;
|
||||
}
|
||||
CharEntry[] tempccCache = new CharEntry[size];
|
||||
// Fill it up using an alphabetical order
|
||||
// and a dumb insert sort
|
||||
CharChunk tempChunk = new CharChunk();
|
||||
int n = 0;
|
||||
while (n < size) {
|
||||
Object key = tempMap.lastKey();
|
||||
ArrayList<CharEntry> list = tempMap.get(key);
|
||||
for (int i = 0; i < list.size() && n < size; i++) {
|
||||
CharEntry entry = list.get(i);
|
||||
tempChunk.setChars(entry.name, 0,
|
||||
entry.name.length);
|
||||
int insertPos = findClosest(tempChunk,
|
||||
tempccCache, n);
|
||||
if (insertPos == n) {
|
||||
tempccCache[n + 1] = entry;
|
||||
} else {
|
||||
System.arraycopy(tempccCache, insertPos + 1,
|
||||
tempccCache, insertPos + 2,
|
||||
n - insertPos - 1);
|
||||
tempccCache[insertPos + 1] = entry;
|
||||
}
|
||||
n++;
|
||||
}
|
||||
tempMap.remove(key);
|
||||
}
|
||||
ccCount = 0;
|
||||
ccStats.clear();
|
||||
ccCache = tempccCache;
|
||||
if (log.isDebugEnabled()) {
|
||||
long t2 = System.currentTimeMillis();
|
||||
log.debug("CharCache generation time: " +
|
||||
(t2 - t1) + "ms");
|
||||
}
|
||||
} else {
|
||||
ccCount++;
|
||||
// Allocate new CharEntry for the lookup
|
||||
CharEntry entry = new CharEntry();
|
||||
entry.value = value;
|
||||
int[] count = ccStats.get(entry);
|
||||
if (count == null) {
|
||||
int end = cc.getEnd();
|
||||
int start = cc.getStart();
|
||||
// Create char array and copy chars
|
||||
entry.name = new char[cc.getLength()];
|
||||
System.arraycopy(cc.getBuffer(), start, entry.name,
|
||||
0, end - start);
|
||||
// Initialize occurrence count to one
|
||||
count = new int[1];
|
||||
count[0] = 1;
|
||||
// Set in the stats hash map
|
||||
ccStats.put(entry, count);
|
||||
} else {
|
||||
count[0] = count[0] + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
} else {
|
||||
accessCount++;
|
||||
// Find the corresponding String
|
||||
String result = find(cc);
|
||||
if (result == null) {
|
||||
return cc.toStringInternal();
|
||||
}
|
||||
// Note: We don't care about safety for the stats
|
||||
hitCount++;
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------- Protected Methods
|
||||
|
||||
|
||||
/**
|
||||
* Compare given byte chunk with byte array.
|
||||
* Return -1, 0 or +1 if inferior, equal, or superior to the String.
|
||||
*/
|
||||
protected static final int compare(ByteChunk name, byte[] compareTo) {
|
||||
int result = 0;
|
||||
|
||||
byte[] b = name.getBuffer();
|
||||
int start = name.getStart();
|
||||
int end = name.getEnd();
|
||||
int len = compareTo.length;
|
||||
|
||||
if ((end - start) < len) {
|
||||
len = end - start;
|
||||
}
|
||||
for (int i = 0; (i < len) && (result == 0); i++) {
|
||||
if (b[i + start] > compareTo[i]) {
|
||||
result = 1;
|
||||
} else if (b[i + start] < compareTo[i]) {
|
||||
result = -1;
|
||||
}
|
||||
}
|
||||
if (result == 0) {
|
||||
if (compareTo.length > (end - start)) {
|
||||
result = -1;
|
||||
} else if (compareTo.length < (end - start)) {
|
||||
result = 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find an entry given its name in the cache and return the associated
|
||||
* String.
|
||||
*/
|
||||
protected static final String find(ByteChunk name) {
|
||||
int pos = findClosest(name, bcCache, bcCache.length);
|
||||
if ((pos < 0) || (compare(name, bcCache[pos].name) != 0)
|
||||
|| !(name.getCharset().equals(bcCache[pos].charset))) {
|
||||
return null;
|
||||
} else {
|
||||
return bcCache[pos].value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find an entry given its name in a sorted array of map elements.
|
||||
* This will return the index for the closest inferior or equal item in the
|
||||
* given array.
|
||||
*/
|
||||
protected static final int findClosest(ByteChunk name, ByteEntry[] array,
|
||||
int len) {
|
||||
|
||||
int a = 0;
|
||||
int b = len - 1;
|
||||
|
||||
// Special cases: -1 and 0
|
||||
if (b == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (compare(name, array[0].name) < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (b == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
while (true) {
|
||||
i = (b + a) >>> 1;
|
||||
int result = compare(name, array[i].name);
|
||||
if (result == 1) {
|
||||
a = i;
|
||||
} else if (result == 0) {
|
||||
return i;
|
||||
} else {
|
||||
b = i;
|
||||
}
|
||||
if ((b - a) == 1) {
|
||||
int result2 = compare(name, array[b].name);
|
||||
if (result2 < 0) {
|
||||
return a;
|
||||
} else {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compare given char chunk with char array.
|
||||
* Return -1, 0 or +1 if inferior, equal, or superior to the String.
|
||||
*/
|
||||
protected static final int compare(CharChunk name, char[] compareTo) {
|
||||
int result = 0;
|
||||
|
||||
char[] c = name.getBuffer();
|
||||
int start = name.getStart();
|
||||
int end = name.getEnd();
|
||||
int len = compareTo.length;
|
||||
|
||||
if ((end - start) < len) {
|
||||
len = end - start;
|
||||
}
|
||||
for (int i = 0; (i < len) && (result == 0); i++) {
|
||||
if (c[i + start] > compareTo[i]) {
|
||||
result = 1;
|
||||
} else if (c[i + start] < compareTo[i]) {
|
||||
result = -1;
|
||||
}
|
||||
}
|
||||
if (result == 0) {
|
||||
if (compareTo.length > (end - start)) {
|
||||
result = -1;
|
||||
} else if (compareTo.length < (end - start)) {
|
||||
result = 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find an entry given its name in the cache and return the associated
|
||||
* String.
|
||||
*/
|
||||
protected static final String find(CharChunk name) {
|
||||
int pos = findClosest(name, ccCache, ccCache.length);
|
||||
if ((pos < 0) || (compare(name, ccCache[pos].name) != 0)) {
|
||||
return null;
|
||||
} else {
|
||||
return ccCache[pos].value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find an entry given its name in a sorted array of map elements.
|
||||
* This will return the index for the closest inferior or equal item in the
|
||||
* given array.
|
||||
*/
|
||||
protected static final int findClosest(CharChunk name, CharEntry[] array,
|
||||
int len) {
|
||||
|
||||
int a = 0;
|
||||
int b = len - 1;
|
||||
|
||||
// Special cases: -1 and 0
|
||||
if (b == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (compare(name, array[0].name) < 0 ) {
|
||||
return -1;
|
||||
}
|
||||
if (b == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
while (true) {
|
||||
i = (b + a) >>> 1;
|
||||
int result = compare(name, array[i].name);
|
||||
if (result == 1) {
|
||||
a = i;
|
||||
} else if (result == 0) {
|
||||
return i;
|
||||
} else {
|
||||
b = i;
|
||||
}
|
||||
if ((b - a) == 1) {
|
||||
int result2 = compare(name, array[b].name);
|
||||
if (result2 < 0) {
|
||||
return a;
|
||||
} else {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------- ByteEntry Inner Class
|
||||
|
||||
|
||||
private static class ByteEntry {
|
||||
|
||||
private byte[] name = null;
|
||||
private Charset charset = null;
|
||||
private String value = null;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value.hashCode();
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof ByteEntry) {
|
||||
return value.equals(((ByteEntry) obj).value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------- CharEntry Inner Class
|
||||
|
||||
|
||||
private static class CharEntry {
|
||||
|
||||
private char[] name = null;
|
||||
private String value = null;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value.hashCode();
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof CharEntry) {
|
||||
return value.equals(((CharEntry) obj).value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,421 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.struts2.util.tomcat.buf;
|
||||
|
||||
import com.opensymphony.xwork2.util.logging.LoggerFactory;
|
||||
|
||||
import com.opensymphony.xwork2.util.logging.Logger;
|
||||
import java.io.CharConversionException;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
/**
|
||||
* All URL decoding happens here. This way we can reuse, review, optimize
|
||||
* without adding complexity to the buffers.
|
||||
*
|
||||
* The conversion will modify the original buffer.
|
||||
*
|
||||
* @author Costin Manolache
|
||||
*/
|
||||
public final class UDecoder {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(UDecoder.class);
|
||||
|
||||
public static final boolean ALLOW_ENCODED_SLASH =
|
||||
Boolean.parseBoolean(System.getProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "false"));
|
||||
|
||||
private static class DecodeException extends CharConversionException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
public DecodeException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Throwable fillInStackTrace() {
|
||||
// This class does not provide a stack trace
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/** Unexpected end of data. */
|
||||
private static final IOException EXCEPTION_EOF = new DecodeException("EOF");
|
||||
|
||||
/** %xx with not-hex digit */
|
||||
private static final IOException EXCEPTION_NOT_HEX_DIGIT = new DecodeException(
|
||||
"isHexDigit");
|
||||
|
||||
/** %-encoded slash is forbidden in resource path */
|
||||
private static final IOException EXCEPTION_SLASH = new DecodeException(
|
||||
"noSlash");
|
||||
|
||||
public UDecoder()
|
||||
{
|
||||
}
|
||||
|
||||
/** URLDecode, will modify the source.
|
||||
*/
|
||||
public void convert( ByteChunk mb, boolean query )
|
||||
throws IOException
|
||||
{
|
||||
int start=mb.getOffset();
|
||||
byte buff[]=mb.getBytes();
|
||||
int end=mb.getEnd();
|
||||
|
||||
int idx= ByteChunk.findByte( buff, start, end, (byte) '%' );
|
||||
int idx2=-1;
|
||||
if( query ) {
|
||||
idx2= ByteChunk.findByte( buff, start, (idx >= 0 ? idx : end), (byte) '+' );
|
||||
}
|
||||
if( idx<0 && idx2<0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// idx will be the smallest positive index ( first % or + )
|
||||
if( (idx2 >= 0 && idx2 < idx) || idx < 0 ) {
|
||||
idx=idx2;
|
||||
}
|
||||
|
||||
final boolean noSlash = !(ALLOW_ENCODED_SLASH || query);
|
||||
|
||||
for( int j=idx; j<end; j++, idx++ ) {
|
||||
if( buff[ j ] == '+' && query) {
|
||||
buff[idx]= (byte)' ' ;
|
||||
} else if( buff[ j ] != '%' ) {
|
||||
buff[idx]= buff[j];
|
||||
} else {
|
||||
// read next 2 digits
|
||||
if( j+2 >= end ) {
|
||||
throw EXCEPTION_EOF;
|
||||
}
|
||||
byte b1= buff[j+1];
|
||||
byte b2=buff[j+2];
|
||||
if( !isHexDigit( b1 ) || ! isHexDigit(b2 )) {
|
||||
throw EXCEPTION_NOT_HEX_DIGIT;
|
||||
}
|
||||
|
||||
j+=2;
|
||||
int res=x2c( b1, b2 );
|
||||
if (noSlash && (res == '/')) {
|
||||
throw EXCEPTION_SLASH;
|
||||
}
|
||||
buff[idx]=(byte)res;
|
||||
}
|
||||
}
|
||||
|
||||
mb.setEnd( idx );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// -------------------- Additional methods --------------------
|
||||
// XXX What do we do about charset ????
|
||||
|
||||
/** In-buffer processing - the buffer will be modified
|
||||
*/
|
||||
public void convert( CharChunk mb, boolean query )
|
||||
throws IOException
|
||||
{
|
||||
// log( "Converting a char chunk ");
|
||||
int start=mb.getOffset();
|
||||
char buff[]=mb.getBuffer();
|
||||
int cend=mb.getEnd();
|
||||
|
||||
int idx= CharChunk.indexOf( buff, start, cend, '%' );
|
||||
int idx2=-1;
|
||||
if( query ) {
|
||||
idx2= CharChunk.indexOf( buff, start, (idx >= 0 ? idx : cend), '+' );
|
||||
}
|
||||
if( idx<0 && idx2<0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// idx will be the smallest positive index ( first % or + )
|
||||
if( (idx2 >= 0 && idx2 < idx) || idx < 0 ) {
|
||||
idx=idx2;
|
||||
}
|
||||
|
||||
final boolean noSlash = !(ALLOW_ENCODED_SLASH || query);
|
||||
|
||||
for( int j=idx; j<cend; j++, idx++ ) {
|
||||
if( buff[ j ] == '+' && query ) {
|
||||
buff[idx]=( ' ' );
|
||||
} else if( buff[ j ] != '%' ) {
|
||||
buff[idx]=buff[j];
|
||||
} else {
|
||||
// read next 2 digits
|
||||
if( j+2 >= cend ) {
|
||||
// invalid
|
||||
throw EXCEPTION_EOF;
|
||||
}
|
||||
char b1= buff[j+1];
|
||||
char b2=buff[j+2];
|
||||
if( !isHexDigit( b1 ) || ! isHexDigit(b2 )) {
|
||||
throw EXCEPTION_NOT_HEX_DIGIT;
|
||||
}
|
||||
|
||||
j+=2;
|
||||
int res=x2c( b1, b2 );
|
||||
if (noSlash && (res == '/')) {
|
||||
throw EXCEPTION_SLASH;
|
||||
}
|
||||
buff[idx]=(char)res;
|
||||
}
|
||||
}
|
||||
mb.setEnd( idx );
|
||||
}
|
||||
|
||||
/** URLDecode, will modify the source
|
||||
*/
|
||||
public void convert(MessageBytes mb, boolean query)
|
||||
throws IOException
|
||||
{
|
||||
|
||||
switch (mb.getType()) {
|
||||
case MessageBytes.T_STR:
|
||||
String strValue=mb.toString();
|
||||
if( strValue==null ) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mb.setString( convert( strValue, query ));
|
||||
} catch (RuntimeException ex) {
|
||||
throw new DecodeException(ex.getMessage());
|
||||
}
|
||||
break;
|
||||
case MessageBytes.T_CHARS:
|
||||
CharChunk charC=mb.getCharChunk();
|
||||
convert( charC, query );
|
||||
break;
|
||||
case MessageBytes.T_BYTES:
|
||||
ByteChunk bytesC=mb.getByteChunk();
|
||||
convert( bytesC, query );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// XXX Old code, needs to be replaced !!!!
|
||||
//
|
||||
public final String convert(String str, boolean query)
|
||||
{
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if( (!query || str.indexOf( '+' ) < 0) && str.indexOf( '%' ) < 0 ) {
|
||||
return str;
|
||||
}
|
||||
|
||||
final boolean noSlash = !(ALLOW_ENCODED_SLASH || query);
|
||||
|
||||
StringBuilder dec = new StringBuilder(); // decoded string output
|
||||
int strPos = 0;
|
||||
int strLen = str.length();
|
||||
|
||||
dec.ensureCapacity(str.length());
|
||||
while (strPos < strLen) {
|
||||
int laPos; // lookahead position
|
||||
|
||||
// look ahead to next URLencoded metacharacter, if any
|
||||
for (laPos = strPos; laPos < strLen; laPos++) {
|
||||
char laChar = str.charAt(laPos);
|
||||
if ((laChar == '+' && query) || (laChar == '%')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if there were non-metacharacters, copy them all as a block
|
||||
if (laPos > strPos) {
|
||||
dec.append(str.substring(strPos,laPos));
|
||||
strPos = laPos;
|
||||
}
|
||||
|
||||
// shortcut out of here if we're at the end of the string
|
||||
if (strPos >= strLen) {
|
||||
break;
|
||||
}
|
||||
|
||||
// process next metacharacter
|
||||
char metaChar = str.charAt(strPos);
|
||||
if (metaChar == '+') {
|
||||
dec.append(' ');
|
||||
strPos++;
|
||||
continue;
|
||||
} else if (metaChar == '%') {
|
||||
// We throw the original exception - the super will deal with
|
||||
// it
|
||||
// try {
|
||||
char res = (char) Integer.parseInt(
|
||||
str.substring(strPos + 1, strPos + 3), 16);
|
||||
if (noSlash && (res == '/')) {
|
||||
throw new IllegalArgumentException("noSlash");
|
||||
}
|
||||
dec.append(res);
|
||||
strPos += 3;
|
||||
}
|
||||
}
|
||||
|
||||
return dec.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decode and return the specified URL-encoded String.
|
||||
* When the byte array is converted to a string, the system default
|
||||
* character encoding is used... This may be different than some other
|
||||
* servers. It is assumed the string is not a query string.
|
||||
*
|
||||
* @param str The url-encoded string
|
||||
*
|
||||
* @exception IllegalArgumentException if a '%' character is not followed
|
||||
* by a valid 2-digit hexadecimal number
|
||||
*/
|
||||
public static String URLDecode(String str) {
|
||||
return URLDecode(str, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decode and return the specified URL-encoded String. It is assumed the
|
||||
* string is not a query string.
|
||||
*
|
||||
* @param str The url-encoded string
|
||||
* @param enc The encoding to use; if null, the default encoding is used. If
|
||||
* an unsupported encoding is specified null will be returned
|
||||
* @exception IllegalArgumentException if a '%' character is not followed
|
||||
* by a valid 2-digit hexadecimal number
|
||||
*/
|
||||
public static String URLDecode(String str, String enc) {
|
||||
return URLDecode(str, enc, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decode and return the specified URL-encoded String.
|
||||
*
|
||||
* @param str The url-encoded string
|
||||
* @param enc The encoding to use; if null, the default encoding is used. If
|
||||
* an unsupported encoding is specified null will be returned
|
||||
* @param isQuery Is this a query string being processed
|
||||
* @exception IllegalArgumentException if a '%' character is not followed
|
||||
* by a valid 2-digit hexadecimal number
|
||||
*/
|
||||
public static String URLDecode(String str, String enc, boolean isQuery) {
|
||||
if (str == null)
|
||||
return (null);
|
||||
|
||||
// use the specified encoding to extract bytes out of the
|
||||
// given string so that the encoding is not lost. If an
|
||||
// encoding is not specified, use ISO-8859-1
|
||||
byte[] bytes = null;
|
||||
try {
|
||||
if (enc == null) {
|
||||
bytes = str.getBytes("ISO-8859-1");
|
||||
} else {
|
||||
bytes = str.getBytes(B2CConverter.getCharset(enc));
|
||||
}
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Unable to URL decode the specified input since the encoding "+ enc + " is not supported.", uee);
|
||||
}
|
||||
}
|
||||
|
||||
return URLDecode(bytes, enc, isQuery);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decode and return the specified URL-encoded byte array.
|
||||
*
|
||||
* @param bytes The url-encoded byte array
|
||||
* @param enc The encoding to use; if null, the default encoding is used. If
|
||||
* an unsupported encoding is specified null will be returned
|
||||
* @param isQuery Is this a query string being processed
|
||||
* @exception IllegalArgumentException if a '%' character is not followed
|
||||
* by a valid 2-digit hexadecimal number
|
||||
*/
|
||||
public static String URLDecode(byte[] bytes, String enc, boolean isQuery) {
|
||||
|
||||
if (bytes == null)
|
||||
return null;
|
||||
|
||||
int len = bytes.length;
|
||||
int ix = 0;
|
||||
int ox = 0;
|
||||
while (ix < len) {
|
||||
byte b = bytes[ix++]; // Get byte to test
|
||||
if (b == '+' && isQuery) {
|
||||
b = (byte)' ';
|
||||
} else if (b == '%') {
|
||||
if (ix + 2 > len) {
|
||||
throw new IllegalArgumentException(
|
||||
"The % character must be followed by two hexademical digits");
|
||||
}
|
||||
b = (byte) ((convertHexDigit(bytes[ix++]) << 4)
|
||||
+ convertHexDigit(bytes[ix++]));
|
||||
}
|
||||
bytes[ox++] = b;
|
||||
}
|
||||
if (enc != null) {
|
||||
try {
|
||||
return new String(bytes, 0, ox, B2CConverter.getCharset(enc));
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Unable to URL decode the specified input since the encoding " + enc + " is not supported.", uee);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return new String(bytes, 0, ox);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static byte convertHexDigit( byte b ) {
|
||||
if ((b >= '0') && (b <= '9')) return (byte)(b - '0');
|
||||
if ((b >= 'a') && (b <= 'f')) return (byte)(b - 'a' + 10);
|
||||
if ((b >= 'A') && (b <= 'F')) return (byte)(b - 'A' + 10);
|
||||
throw new IllegalArgumentException(((char) b) + " is not a hexadecimal digit");
|
||||
}
|
||||
|
||||
|
||||
private static boolean isHexDigit( int c ) {
|
||||
return ( ( c>='0' && c<='9' ) ||
|
||||
( c>='a' && c<='f' ) ||
|
||||
( c>='A' && c<='F' ));
|
||||
}
|
||||
|
||||
|
||||
private static int x2c( byte b1, byte b2 ) {
|
||||
int digit= (b1>='A') ? ( (b1 & 0xDF)-'A') + 10 :
|
||||
(b1 -'0');
|
||||
digit*=16;
|
||||
digit +=(b2>='A') ? ( (b2 & 0xDF)-'A') + 10 :
|
||||
(b2 -'0');
|
||||
return digit;
|
||||
}
|
||||
|
||||
|
||||
private static int x2c( char b1, char b2 ) {
|
||||
int digit= (b1>='A') ? ( (b1 & 0xDF)-'A') + 10 :
|
||||
(b1 -'0');
|
||||
digit*=16;
|
||||
digit +=(b2>='A') ? ( (b2 & 0xDF)-'A') + 10 :
|
||||
(b2 -'0');
|
||||
return digit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.struts2.util.tomcat.buf;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CoderResult;
|
||||
|
||||
/**
|
||||
* Decodes bytes to UTF-8. Extracted from Apache Harmony and modified to reject
|
||||
* code points from U+D800 to U+DFFF as per RFC3629. The standard Java decoder
|
||||
* does not reject these. It has also been modified to reject code points
|
||||
* greater than U+10FFFF which the standard Java decoder rejects but the harmony
|
||||
* one does not.
|
||||
*/
|
||||
public class Utf8Decoder extends CharsetDecoder {
|
||||
|
||||
// The next table contains information about UTF-8 charset and
|
||||
// correspondence of 1st byte to the length of sequence
|
||||
// For information please visit http://www.ietf.org/rfc/rfc3629.txt
|
||||
//
|
||||
// Please note, o means 0, actually.
|
||||
// -------------------------------------------------------------------
|
||||
// 0 1 2 3 Value
|
||||
// -------------------------------------------------------------------
|
||||
// oxxxxxxx 00000000 00000000 0xxxxxxx
|
||||
// 11oyyyyy 1oxxxxxx 00000000 00000yyy yyxxxxxx
|
||||
// 111ozzzz 1oyyyyyy 1oxxxxxx 00000000 zzzzyyyy yyxxxxxx
|
||||
// 1111ouuu 1ouuzzzz 1oyyyyyy 1oxxxxxx 000uuuuu zzzzyyyy yyxxxxxx
|
||||
private static final int remainingBytes[] = {
|
||||
// 1owwwwww
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
// 11oyyyyy
|
||||
-1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
// 111ozzzz
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
// 1111ouuu
|
||||
3, 3, 3, 3, 3, -1, -1, -1,
|
||||
// > 11110111
|
||||
-1, -1, -1, -1, -1, -1, -1, -1};
|
||||
private static final int remainingNumbers[] = {0, // 0 1 2 3
|
||||
4224, // (01o00000b << 6)+(1o000000b)
|
||||
401536, // (011o0000b << 12)+(1o000000b << 6)+(1o000000b)
|
||||
29892736 // (0111o000b << 18)+(1o000000b << 12)+(1o000000b <<
|
||||
// 6)+(1o000000b)
|
||||
};
|
||||
private static final int lowerEncodingLimit[] = {-1, 0x80, 0x800, 0x10000};
|
||||
|
||||
|
||||
public Utf8Decoder() {
|
||||
super(B2CConverter.UTF_8, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
|
||||
if (in.hasArray() && out.hasArray()) {
|
||||
return decodeHasArray(in, out);
|
||||
}
|
||||
return decodeNotHasArray(in, out);
|
||||
}
|
||||
|
||||
|
||||
private CoderResult decodeNotHasArray(ByteBuffer in, CharBuffer out) {
|
||||
int outRemaining = out.remaining();
|
||||
int pos = in.position();
|
||||
int limit = in.limit();
|
||||
try {
|
||||
while (pos < limit) {
|
||||
if (outRemaining == 0) {
|
||||
return CoderResult.OVERFLOW;
|
||||
}
|
||||
int jchar = in.get();
|
||||
if (jchar < 0) {
|
||||
jchar = jchar & 0x7F;
|
||||
int tail = remainingBytes[jchar];
|
||||
if (tail == -1) {
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
if (limit - pos < 1 + tail) {
|
||||
// No early test for invalid sequences here as peeking
|
||||
// at the next byte is harder
|
||||
return CoderResult.UNDERFLOW;
|
||||
}
|
||||
int nextByte;
|
||||
for (int i = 0; i < tail; i++) {
|
||||
nextByte = in.get() & 0xFF;
|
||||
if ((nextByte & 0xC0) != 0x80) {
|
||||
return CoderResult.malformedForLength(1 + i);
|
||||
}
|
||||
jchar = (jchar << 6) + nextByte;
|
||||
}
|
||||
jchar -= remainingNumbers[tail];
|
||||
if (jchar < lowerEncodingLimit[tail]) {
|
||||
// Should have been encoded in a fewer octets
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
pos += tail;
|
||||
}
|
||||
// Apache Tomcat added test
|
||||
if (jchar >= 0xD800 && jchar <= 0xDFFF) {
|
||||
return CoderResult.unmappableForLength(3);
|
||||
}
|
||||
// Apache Tomcat added test
|
||||
if (jchar > 0x10FFFF) {
|
||||
return CoderResult.unmappableForLength(4);
|
||||
}
|
||||
if (jchar <= 0xffff) {
|
||||
out.put((char) jchar);
|
||||
outRemaining--;
|
||||
} else {
|
||||
if (outRemaining < 2) {
|
||||
return CoderResult.OVERFLOW;
|
||||
}
|
||||
out.put((char) ((jchar >> 0xA) + 0xD7C0));
|
||||
out.put((char) ((jchar & 0x3FF) + 0xDC00));
|
||||
outRemaining -= 2;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
return CoderResult.UNDERFLOW;
|
||||
} finally {
|
||||
in.position(pos);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private CoderResult decodeHasArray(ByteBuffer in, CharBuffer out) {
|
||||
int outRemaining = out.remaining();
|
||||
int pos = in.position();
|
||||
int limit = in.limit();
|
||||
final byte[] bArr = in.array();
|
||||
final char[] cArr = out.array();
|
||||
final int inIndexLimit = limit + in.arrayOffset();
|
||||
int inIndex = pos + in.arrayOffset();
|
||||
int outIndex = out.position() + out.arrayOffset();
|
||||
// if someone would change the limit in process,
|
||||
// he would face consequences
|
||||
for (; inIndex < inIndexLimit && outRemaining > 0; inIndex++) {
|
||||
int jchar = bArr[inIndex];
|
||||
if (jchar < 0) {
|
||||
jchar = jchar & 0x7F;
|
||||
// If first byte is invalid, tail will be set to -1
|
||||
int tail = remainingBytes[jchar];
|
||||
if (tail == -1) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
// Additional checks to detect invalid sequences ASAP
|
||||
// Checks derived from Unicode 6.2, Chapter 3, Table 3-7
|
||||
// Check 2nd byte
|
||||
int tailAvailable = inIndexLimit - inIndex - 1;
|
||||
if (tailAvailable > 0) {
|
||||
// First byte C2..DF, second byte 80..BF
|
||||
if (jchar > 0x41 && jchar < 0x60 &&
|
||||
(bArr[inIndex + 1] & 0xC0) != 0x80) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
// First byte E0, second byte A0..BF
|
||||
if (jchar == 0x60 && (bArr[inIndex + 1] & 0xE0) != 0xA0) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
// First byte E1..EC, second byte 80..BF
|
||||
if (jchar > 0x60 && jchar < 0x6D &&
|
||||
(bArr[inIndex + 1] & 0xC0) != 0x80) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
// First byte ED, second byte 80..9F
|
||||
if (jchar == 0x6D && (bArr[inIndex + 1] & 0xE0) != 0x80) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
// First byte EE..EF, second byte 80..BF
|
||||
if (jchar > 0x6D && jchar < 0x70 &&
|
||||
(bArr[inIndex + 1] & 0xC0) != 0x80) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
// First byte F0, second byte 90..BF
|
||||
if (jchar == 0x70 &&
|
||||
((bArr[inIndex + 1] & 0xFF) < 0x90 ||
|
||||
(bArr[inIndex + 1] & 0xFF) > 0xBF)) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
// First byte F1..F3, second byte 80..BF
|
||||
if (jchar > 0x70 && jchar < 0x74 &&
|
||||
(bArr[inIndex + 1] & 0xC0) != 0x80) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
// First byte F4, second byte 80..8F
|
||||
if (jchar == 0x74 &&
|
||||
(bArr[inIndex + 1] & 0xF0) != 0x80) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
}
|
||||
// Check third byte if present and expected
|
||||
if (tailAvailable > 1 && tail > 1) {
|
||||
if ((bArr[inIndex + 2] & 0xC0) != 0x80) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(2);
|
||||
}
|
||||
}
|
||||
// Check fourth byte if present and expected
|
||||
if (tailAvailable > 2 && tail > 2) {
|
||||
if ((bArr[inIndex + 3] & 0xC0) != 0x80) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(3);
|
||||
}
|
||||
}
|
||||
if (tailAvailable < tail) {
|
||||
break;
|
||||
}
|
||||
for (int i = 0; i < tail; i++) {
|
||||
int nextByte = bArr[inIndex + i + 1] & 0xFF;
|
||||
if ((nextByte & 0xC0) != 0x80) {
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(1 + i);
|
||||
}
|
||||
jchar = (jchar << 6) + nextByte;
|
||||
}
|
||||
jchar -= remainingNumbers[tail];
|
||||
if (jchar < lowerEncodingLimit[tail]) {
|
||||
// Should have been encoded in fewer octets
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return CoderResult.malformedForLength(1);
|
||||
}
|
||||
inIndex += tail;
|
||||
}
|
||||
// Apache Tomcat added test
|
||||
if (jchar >= 0xD800 && jchar <= 0xDFFF) {
|
||||
return CoderResult.unmappableForLength(3);
|
||||
}
|
||||
// Apache Tomcat added test
|
||||
if (jchar > 0x10FFFF) {
|
||||
return CoderResult.unmappableForLength(4);
|
||||
}
|
||||
if (jchar <= 0xffff) {
|
||||
cArr[outIndex++] = (char) jchar;
|
||||
outRemaining--;
|
||||
} else {
|
||||
if (outRemaining < 2) {
|
||||
return CoderResult.OVERFLOW;
|
||||
}
|
||||
cArr[outIndex++] = (char) ((jchar >> 0xA) + 0xD7C0);
|
||||
cArr[outIndex++] = (char) ((jchar & 0x3FF) + 0xDC00);
|
||||
outRemaining -= 2;
|
||||
}
|
||||
}
|
||||
in.position(inIndex - in.arrayOffset());
|
||||
out.position(outIndex - out.arrayOffset());
|
||||
return (outRemaining == 0 && inIndex < inIndexLimit) ?
|
||||
CoderResult.OVERFLOW :
|
||||
CoderResult.UNDERFLOW;
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import freemarker.cache.FileTemplateLoader;
|
||||
import freemarker.cache.MultiTemplateLoader;
|
||||
import freemarker.cache.TemplateLoader;
|
||||
import freemarker.cache.WebappTemplateLoader;
|
||||
import freemarker.core.TemplateClassResolver;
|
||||
import freemarker.ext.jsp.TaglibFactory;
|
||||
import freemarker.ext.servlet.HttpRequestHashModel;
|
||||
import freemarker.ext.servlet.HttpRequestParametersHashModel;
|
||||
@@ -344,6 +345,9 @@ public class FreemarkerManager {
|
||||
configuration.setLocalizedLookup(false);
|
||||
configuration.setWhitespaceStripping(true);
|
||||
|
||||
LOG.debug("Sets NewBuiltinClassResolver to TemplateClassResolver.SAFER_RESOLVER");
|
||||
configuration.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
@@ -365,6 +369,7 @@ public class FreemarkerManager {
|
||||
servletContext.setAttribute(ATTR_APPLICATION_MODEL, servletContextModel);
|
||||
}
|
||||
TaglibFactory taglibs = new TaglibFactory(servletContext);
|
||||
taglibs.setObjectWrapper(wrapper);
|
||||
servletContext.setAttribute(ATTR_JSP_TAGLIBS_MODEL, taglibs);
|
||||
}
|
||||
model.put(KEY_APPLICATION, servletContextModel);
|
||||
|
||||
@@ -306,7 +306,7 @@ public abstract class AbstractUITag extends ComponentTagSupport implements Dynam
|
||||
}
|
||||
|
||||
public void setDynamicAttribute(String uri, String localName, Object value) throws JspException {
|
||||
if (ComponentUtils.altSyntax(getStack()) && ComponentUtils.isExpression(value)) {
|
||||
if (ComponentUtils.altSyntax(getStack()) && ComponentUtils.isExpression(value.toString())) {
|
||||
dynamicAttributes.put(localName, String.valueOf(ObjectUtils.defaultIfNull(findValue(value.toString()), value)));
|
||||
} else {
|
||||
dynamicAttributes.put(localName, value);
|
||||
|
||||
@@ -24,14 +24,26 @@ package org.apache.struts2.views.util;
|
||||
import com.opensymphony.xwork2.inject.Inject;
|
||||
import com.opensymphony.xwork2.util.logging.Logger;
|
||||
import com.opensymphony.xwork2.util.logging.LoggerFactory;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang3.StringEscapeUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.struts2.StrutsConstants;
|
||||
import org.apache.struts2.util.URLDecoderUtil;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -85,12 +97,17 @@ public class DefaultUrlHelper implements UrlHelper {
|
||||
return buildUrl(action, request, response, params, scheme, includeContext, encodeResult, forceAddSchemeHostAndPort, true);
|
||||
}
|
||||
|
||||
public String buildUrl(String action, HttpServletRequest request, HttpServletResponse response, Map<String, Object> params, String scheme,
|
||||
public String buildUrl(String action, HttpServletRequest request, HttpServletResponse response, Map<String, Object> params, String urlScheme,
|
||||
boolean includeContext, boolean encodeResult, boolean forceAddSchemeHostAndPort, boolean escapeAmp) {
|
||||
|
||||
StringBuilder link = new StringBuilder();
|
||||
boolean changedScheme = false;
|
||||
|
||||
String scheme = null;
|
||||
if (isValidScheme(urlScheme)) {
|
||||
scheme = urlScheme;
|
||||
}
|
||||
|
||||
// only append scheme if it is different to the current scheme *OR*
|
||||
// if we explicity want it to be appended by having forceAddSchemeHostAndPort = true
|
||||
if (forceAddSchemeHostAndPort) {
|
||||
@@ -174,9 +191,9 @@ public class DefaultUrlHelper implements UrlHelper {
|
||||
|
||||
//if the action was not explicitly set grab the params from the request
|
||||
if (escapeAmp) {
|
||||
buildParametersString(params, link, AMP);
|
||||
buildParametersString(params, link, AMP, true);
|
||||
} else {
|
||||
buildParametersString(params, link, "&");
|
||||
buildParametersString(params, link, "&", true);
|
||||
}
|
||||
|
||||
String result = link.toString();
|
||||
@@ -197,6 +214,10 @@ public class DefaultUrlHelper implements UrlHelper {
|
||||
}
|
||||
|
||||
public void buildParametersString(Map<String, Object> params, StringBuilder link, String paramSeparator) {
|
||||
buildParametersString(params, link, paramSeparator, true);
|
||||
}
|
||||
|
||||
public void buildParametersString(Map<String, Object> params, StringBuilder link, String paramSeparator, boolean encode) {
|
||||
if ((params != null) && (params.size() > 0)) {
|
||||
if (!link.toString().contains("?")) {
|
||||
link.append("?");
|
||||
@@ -214,7 +235,7 @@ public class DefaultUrlHelper implements UrlHelper {
|
||||
if (value instanceof Iterable) {
|
||||
for (Iterator iterator = ((Iterable) value).iterator(); iterator.hasNext();) {
|
||||
Object paramValue = iterator.next();
|
||||
link.append(buildParameterSubstring(name, paramValue != null ? paramValue.toString() : StringUtils.EMPTY));
|
||||
link.append(buildParameterSubstring(name, paramValue != null ? paramValue.toString() : StringUtils.EMPTY, encode));
|
||||
|
||||
if (iterator.hasNext()) {
|
||||
link.append(paramSeparator);
|
||||
@@ -224,14 +245,14 @@ public class DefaultUrlHelper implements UrlHelper {
|
||||
Object[] array = (Object[]) value;
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
Object paramValue = array[i];
|
||||
link.append(buildParameterSubstring(name, paramValue != null ? paramValue.toString() : StringUtils.EMPTY));
|
||||
link.append(buildParameterSubstring(name, paramValue != null ? paramValue.toString() : StringUtils.EMPTY, encode));
|
||||
|
||||
if (i < array.length - 1) {
|
||||
link.append(paramSeparator);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
link.append(buildParameterSubstring(name, value != null ? value.toString() : StringUtils.EMPTY));
|
||||
link.append(buildParameterSubstring(name, value != null ? value.toString() : StringUtils.EMPTY, encode));
|
||||
}
|
||||
|
||||
if (iter.hasNext()) {
|
||||
@@ -241,11 +262,15 @@ public class DefaultUrlHelper implements UrlHelper {
|
||||
}
|
||||
}
|
||||
|
||||
private String buildParameterSubstring(String name, String value) {
|
||||
protected boolean isValidScheme(String scheme) {
|
||||
return HTTP_PROTOCOL.equals(scheme) || HTTPS_PROTOCOL.equals(scheme);
|
||||
}
|
||||
|
||||
private String buildParameterSubstring(String name, String value, boolean encode) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(encode(name));
|
||||
builder.append(encode ? encode(name) : name);
|
||||
builder.append('=');
|
||||
builder.append(encode(value));
|
||||
builder.append(encode ? encode(value) : value);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@@ -267,15 +292,15 @@ public class DefaultUrlHelper implements UrlHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the URL using {@link java.net.URLDecoder#decode(String, String)} with the encoding specified in the configuration.
|
||||
* Decodes the URL using {@link URLDecoderUtil#decode(String, String)} with the encoding specified in the configuration.
|
||||
*
|
||||
* @param input the input to decode
|
||||
* @return the encoded string
|
||||
*/
|
||||
public String decode( String input ) {
|
||||
try {
|
||||
return URLDecoder.decode(input, encoding);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
return URLDecoderUtil.decode(input, encoding, false);
|
||||
} catch (Exception e) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("Could not decode URL parameter '#0', returning value un-decoded", input);
|
||||
}
|
||||
@@ -283,6 +308,22 @@ public class DefaultUrlHelper implements UrlHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the URL using {@link URLDecoderUtil#decode(String, String, boolean)} with the encoding specified in the configuration.
|
||||
*
|
||||
* @param input the input to decode
|
||||
* @param isQueryString whether input is a query string. If <code>true</code> other decoding rules apply.
|
||||
* @return the encoded string
|
||||
*/
|
||||
public String decode( String input, boolean isQueryString ) {
|
||||
try {
|
||||
return URLDecoderUtil.decode(input, encoding, isQueryString);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Could not decode URL parameter '{}', returning value un-decoded", input);
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> parseQueryString(String queryString, boolean forceValueArray) {
|
||||
Map<String, Object> queryParams = new LinkedHashMap<String, Object>();
|
||||
if (queryString != null) {
|
||||
@@ -299,8 +340,8 @@ public class DefaultUrlHelper implements UrlHelper {
|
||||
paramValue = tmpParams[1];
|
||||
}
|
||||
if (paramName != null) {
|
||||
paramName = decode(paramName);
|
||||
String translatedParamValue = decode(paramValue);
|
||||
paramName = decode(paramName, true);
|
||||
String translatedParamValue = decode(paramValue, true);
|
||||
|
||||
if (queryParams.containsKey(paramName) || forceValueArray) {
|
||||
// WW-1619 append new param value to existing value(s)
|
||||
|
||||
@@ -448,12 +448,7 @@ public class XSLTResult implements Result {
|
||||
ServletActionContext.getServletContext());
|
||||
}
|
||||
|
||||
protected Templates getTemplates(String path) throws TransformerException, IOException {
|
||||
String pathFromRequest = ServletActionContext.getRequest().getParameter("xslt.location");
|
||||
|
||||
if (pathFromRequest != null)
|
||||
path = pathFromRequest;
|
||||
|
||||
protected Templates getTemplates(final String path) throws TransformerException, IOException {
|
||||
if (path == null)
|
||||
throw new TransformerException("Stylesheet path is null");
|
||||
|
||||
|
||||
@@ -46,13 +46,26 @@
|
||||
java.lang.Class,
|
||||
java.lang.ClassLoader,
|
||||
java.lang.Shutdown,
|
||||
ognl.OgnlContext,
|
||||
ognl.MemberAccess,
|
||||
ognl.ClassResolver,
|
||||
ognl.TypeConverter,
|
||||
java.lang.ProcessBuilder,
|
||||
com.opensymphony.xwork2.ActionContext" />
|
||||
|
||||
<!-- this must be valid regex, each '.' in package name must be escaped! -->
|
||||
<constant name="struts.excludedPackageNamePatterns" value="^java\.lang\..*,^ognl.*,^(?!javax\.servlet\..+)(javax\..+)" />
|
||||
<!-- it's more flexible but slower than simple string comparison -->
|
||||
<!-- constant name="struts.excludedPackageNamePatterns" value="^java\.lang\..*,^ognl.*,^(?!javax\.servlet\..+)(javax\..+)" / -->
|
||||
|
||||
<!-- this is simpler version of the above used with string comparison -->
|
||||
<constant name="struts.excludedPackageNames"
|
||||
value="
|
||||
ognl.,
|
||||
javax.,
|
||||
freemarker.core.,
|
||||
freemarker.template.,
|
||||
freemarker.ext.rhino.,
|
||||
sun.reflect.,
|
||||
javassist.,
|
||||
com.opensymphony.xwork2.ognl.,
|
||||
com.opensymphony.xwork2.security.,
|
||||
com.opensymphony.xwork2.util." />
|
||||
|
||||
<bean class="com.opensymphony.xwork2.ObjectFactory" name="struts"/>
|
||||
<bean type="com.opensymphony.xwork2.factory.ResultFactory" name="struts" class="org.apache.struts2.factory.StrutsResultFactory" />
|
||||
|
||||
@@ -26,8 +26,10 @@
|
||||
<#assign itemCount = itemCount + 1/>
|
||||
<#if parameters.listKey??>
|
||||
<#assign itemKey = stack.findValue(parameters.listKey)/>
|
||||
<#else>
|
||||
<#assign itemKey = stack.findValue('top')/>
|
||||
<#assign itemKeyStr = stack.findString(parameters.listKey)/>
|
||||
<#else>
|
||||
<#assign itemKey = stack.findValue('top')/>
|
||||
<#assign itemKeyStr = stack.findString('top')>
|
||||
</#if>
|
||||
<#if parameters.listLabelKey??>
|
||||
<#-- checks the valueStack for the 'valueKey.' The valueKey is then looked-up in the locale
|
||||
@@ -59,7 +61,6 @@
|
||||
<#assign itemTitle = ''/>
|
||||
</#if>
|
||||
</#if>
|
||||
<#assign itemKeyStr=itemKey.toString() />
|
||||
<input type="checkbox" name="${parameters.name?html}" value="${itemKeyStr?html}"<#rt/>
|
||||
<#if parameters.id?has_content>
|
||||
id="${parameters.id?html}-${itemCount}"<#rt/>
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
<#assign itemCount = startCount/>
|
||||
var ${parameters.id}Group = new Array(${parameters.listSize} + ${startCount});
|
||||
for (i = 0; i < (${parameters.listSize} + ${startCount}); i++)
|
||||
${parameters.id}Group[i] = new Array();
|
||||
${parameters.id}Group[i] = [];
|
||||
|
||||
<@s.iterator value="parameters.list">
|
||||
<#if parameters.listKey??>
|
||||
@@ -91,7 +91,7 @@
|
||||
</#if>
|
||||
<#assign doubleItemCount = 0/>
|
||||
<#if parameters.doubleHeaderKey?? && parameters.doubleHeaderValue??>
|
||||
${parameters.id}Group[${itemCount}][${doubleItemCount}] = new Option("${doubleHeaderValue?js_string}", "${doubleHeaderKey?js_string}");
|
||||
${parameters.id}Group[${itemCount}][${doubleItemCount}] = new Option("${parameters.doubleHeaderValue?js_string}", "${parameters.doubleHeaderKey?js_string}");
|
||||
<#assign doubleItemCount = doubleItemCount + 1/>
|
||||
</#if>
|
||||
<#if parameters.doubleEmptyOption??>
|
||||
@@ -172,10 +172,7 @@
|
||||
${parameters.id}Temp.options[i] = new Option(${parameters.id}Group[x][i].text, ${parameters.id}Group[x][i].value);
|
||||
<#if parameters.doubleNameValue??>
|
||||
<#if parameters.doubleMultiple??>
|
||||
for (j = 0; j < ${parameters.doubleNameValue}.
|
||||
length;
|
||||
j++
|
||||
)
|
||||
for (j = 0; j < ${parameters.doubleNameValue}.length; j++)
|
||||
{
|
||||
if (${parameters.id}Temp.options[i].value == ${parameters.doubleNameValue?js_string}[j]) {
|
||||
${parameters.id}Temp.options[i].selected = true;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<#--
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
-->
|
||||
|
||||
<#--
|
||||
Code that will add javascript needed for tooltips
|
||||
--><#t/>
|
||||
<#if (parameters.hasTooltip?default(false))><#t/>
|
||||
<#lt/><!-- javascript that is needed for tooltips -->
|
||||
<#lt/><script type="text/javascript" src='<@s.url value="/struts/domTT.js" includeParams="none" encode="false" />'></script>
|
||||
<#lt/><link rel="stylesheet" type="text/css" href="<@s.url value="/struts/domTT.css" includeParams="none" encode="false" />"/>
|
||||
|
||||
</#if><#t/>
|
||||
@@ -101,13 +101,4 @@
|
||||
</script>
|
||||
</#if>
|
||||
|
||||
|
||||
<#--
|
||||
Code that will add javascript needed for tooltips
|
||||
--><#t/>
|
||||
<#if (parameters.hasTooltip?default(false))><#t/>
|
||||
<#lt/><!-- javascript that is needed for tooltips -->
|
||||
<#lt/><script type="text/javascript" src='<@s.url value="/struts/domTT.js" includeParams="none" encode="false" />'></script>
|
||||
<#lt/><link rel="stylesheet" type="text/css" href="<@s.url value="/struts/domTT.css" includeParams="none" encode="false" />"/>
|
||||
|
||||
</#if><#t/>
|
||||
<#include "/${parameters.templateDir}/${parameters.expandTheme}/form-close-tooltips.ftl" />
|
||||
|
||||
@@ -23,10 +23,11 @@
|
||||
<@s.iterator value="parameters.list">
|
||||
<#if parameters.listKey??>
|
||||
<#assign itemKey = stack.findValue(parameters.listKey)/>
|
||||
<#assign itemKeyStr = stack.findString(parameters.listKey)/>
|
||||
<#else>
|
||||
<#assign itemKey = stack.findValue('top')/>
|
||||
<#assign itemKeyStr = stack.findString('top')>
|
||||
</#if>
|
||||
<#assign itemKeyStr = itemKey.toString() />
|
||||
<#if parameters.listValueKey??>
|
||||
<#-- checks the valueStack for the 'valueKey.' The valueKey is then looked-up in the locale
|
||||
file for it's localized value. This is then used as a label -->
|
||||
@@ -64,7 +65,7 @@
|
||||
name="${parameters.name?html}"<#rt/>
|
||||
</#if>
|
||||
id="${parameters.id?html}${itemKeyStr?html}"<#rt/>
|
||||
<#if tag.contains(parameters.nameValue!'', itemKeyStr)>
|
||||
<#if tag.contains(parameters.nameValue!'', itemKey)>
|
||||
checked="checked"<#rt/>
|
||||
</#if>
|
||||
<#if itemKey??>
|
||||
|
||||
@@ -61,6 +61,9 @@
|
||||
</#if>
|
||||
<#else>
|
||||
<input type="submit"<#rt/>
|
||||
<#if parameters.nameValue?has_content>
|
||||
value="<@s.property value="parameters.nameValue"/>"<#rt/>
|
||||
</#if>
|
||||
</#if>
|
||||
<#if parameters.id?has_content>
|
||||
id="${parameters.id?html}"<#rt/>
|
||||
@@ -68,9 +71,6 @@
|
||||
<#if parameters.name?has_content>
|
||||
name="${parameters.name?html}"<#rt/>
|
||||
</#if>
|
||||
<#if parameters.nameValue?has_content>
|
||||
value="<@s.property value="parameters.nameValue"/>"<#rt/>
|
||||
</#if>
|
||||
<#if parameters.disabled!false>
|
||||
disabled="disabled"<#rt/>
|
||||
</#if>
|
||||
|
||||
@@ -164,21 +164,4 @@ public class UIBeanTest extends StrutsInternalTestCase {
|
||||
assertEquals("12", txtFld.getTheme());
|
||||
}
|
||||
|
||||
// I couldn't figure out how to make this test work. Bailing for now.
|
||||
// public void testEscapeLabel() throws Exception {
|
||||
// ValueStack stack = ActionContext.getContext().getValueStack();
|
||||
// MockHttpServletRequest req = new MockHttpServletRequest();
|
||||
// MockHttpServletResponse res = new MockHttpServletResponse();
|
||||
// stack.push(this);
|
||||
//
|
||||
// TextField txtFld = new TextField(stack, req, res);
|
||||
// txtFld.setKey("test['foo']");
|
||||
// txtFld.evaluateParams();
|
||||
// assertEquals("test_label", txtFld.getParameters().get("label"));
|
||||
// }
|
||||
//
|
||||
// public String getText(String key) {
|
||||
// assertEquals("test[\\'foo\\']", key);
|
||||
// return "test_label";
|
||||
// }
|
||||
}
|
||||
|
||||
+107
-66
@@ -30,6 +30,7 @@ import com.opensymphony.xwork2.config.ConfigurationManager;
|
||||
import com.opensymphony.xwork2.config.entities.PackageConfig;
|
||||
import com.opensymphony.xwork2.config.impl.DefaultConfiguration;
|
||||
import org.apache.struts2.ServletActionContext;
|
||||
import org.apache.struts2.StrutsException;
|
||||
import org.apache.struts2.StrutsInternalTestCase;
|
||||
import org.apache.struts2.dispatcher.StrutsResultSupport;
|
||||
import org.apache.struts2.views.jsp.StrutsMockHttpServletRequest;
|
||||
@@ -67,7 +68,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
};
|
||||
}
|
||||
|
||||
public void testGetMapping() throws Exception {
|
||||
public void testGetMapping() {
|
||||
req.setupGetRequestURI("/my/namespace/actionName.action");
|
||||
req.setupGetServletPath("/my/namespace/actionName.action");
|
||||
req.setupGetAttribute(null);
|
||||
@@ -81,7 +82,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertNull(mapping.getMethod());
|
||||
}
|
||||
|
||||
public void testGetMappingWithMethod() throws Exception {
|
||||
public void testGetMappingWithMethod() {
|
||||
req.setupGetParameterMap(new HashMap());
|
||||
req.setupGetRequestURI("/my/namespace/actionName!add.action");
|
||||
req.setupGetServletPath("/my/namespace/actionName!add.action");
|
||||
@@ -97,7 +98,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("add", mapping.getMethod());
|
||||
}
|
||||
|
||||
public void testGetMappingWithSlashedName() throws Exception {
|
||||
public void testGetMappingWithSlashedName() {
|
||||
|
||||
req.setupGetRequestURI("/my/foo/actionName.action");
|
||||
req.setupGetServletPath("/my/foo/actionName.action");
|
||||
@@ -113,7 +114,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertNull(mapping.getMethod());
|
||||
}
|
||||
|
||||
public void testGetMappingWithSlashedNameAtRootButNoSlashPackage() throws Exception {
|
||||
public void testGetMappingWithSlashedNameAtRootButNoSlashPackage() {
|
||||
|
||||
req.setupGetRequestURI("/foo/actionName.action");
|
||||
req.setupGetServletPath("/foo/actionName.action");
|
||||
@@ -129,7 +130,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertNull(mapping.getMethod());
|
||||
}
|
||||
|
||||
public void testGetMappingWithSlashedNameAtRoot() throws Exception {
|
||||
public void testGetMappingWithSlashedNameAtRoot() {
|
||||
config = new DefaultConfiguration();
|
||||
PackageConfig pkg = new PackageConfig.Builder("myns")
|
||||
.namespace("/my/namespace").build();
|
||||
@@ -160,10 +161,10 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
|
||||
|
||||
|
||||
public void testGetMappingWithNamespaceSlash() throws Exception {
|
||||
public void testGetMappingWithNamespaceSlash() {
|
||||
|
||||
req.setupGetRequestURI("/my.hh/abc.action");
|
||||
req.setupGetServletPath("/my.hh/abc.action");
|
||||
req.setupGetRequestURI("/my-hh/abc.action");
|
||||
req.setupGetServletPath("/my-hh/abc.action");
|
||||
req.setupGetAttribute(null);
|
||||
req.addExpectedGetAttributeName("javax.servlet.include.servlet_path");
|
||||
|
||||
@@ -180,10 +181,10 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
mapping = mapper.getMapping(req, configManager);
|
||||
|
||||
assertEquals("", mapping.getNamespace());
|
||||
assertEquals("my.hh/abc", mapping.getName());
|
||||
assertEquals("my-hh/abc", mapping.getName());
|
||||
}
|
||||
|
||||
public void testGetMappingWithUnknownNamespace() throws Exception {
|
||||
public void testGetMappingWithUnknownNamespace() {
|
||||
req.setupGetRequestURI("/bo/foo/actionName.action");
|
||||
req.setupGetServletPath("/bo/foo/actionName.action");
|
||||
req.setupGetAttribute(null);
|
||||
@@ -197,7 +198,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertNull(mapping.getMethod());
|
||||
}
|
||||
|
||||
public void testGetMappingWithUnknownNamespaceButFullNamespaceSelect() throws Exception {
|
||||
public void testGetMappingWithUnknownNamespaceButFullNamespaceSelect() {
|
||||
req.setupGetRequestURI("/bo/foo/actionName.action");
|
||||
req.setupGetServletPath("/bo/foo/actionName.action");
|
||||
req.setupGetAttribute(null);
|
||||
@@ -212,7 +213,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertNull(mapping.getMethod());
|
||||
}
|
||||
|
||||
public void testGetMappingWithActionName_methodAndName() throws Exception {
|
||||
public void testGetMappingWithActionName_methodAndName() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
mapper.setAllowDynamicMethodCalls("true");
|
||||
ActionMapping mapping = mapper.getMappingFromActionName("actionName!add");
|
||||
@@ -220,22 +221,22 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("add", mapping.getMethod());
|
||||
}
|
||||
|
||||
public void testGetMappingWithActionName_name() throws Exception {
|
||||
public void testGetMappingWithActionName_name() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
ActionMapping mapping = mapper.getMappingFromActionName("actionName");
|
||||
assertEquals("actionName", mapping.getName());
|
||||
assertEquals(null, mapping.getMethod());
|
||||
assertNull(mapping.getMethod());
|
||||
}
|
||||
|
||||
public void testGetMappingWithActionName_noDynamicMethod() throws Exception {
|
||||
public void testGetMappingWithActionName_noDynamicMethod() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
mapper.setAllowDynamicMethodCalls("false");
|
||||
ActionMapping mapping = mapper.getMappingFromActionName("actionName!add");
|
||||
assertEquals("actionName!add", mapping.getName());
|
||||
assertEquals(null, mapping.getMethod());
|
||||
assertNull(mapping.getMethod());
|
||||
}
|
||||
|
||||
public void testGetMappingWithActionName_noDynamicMethodColonPrefix() throws Exception {
|
||||
public void testGetMappingWithActionName_noDynamicMethodColonPrefix() {
|
||||
|
||||
Map parameterMap = new HashMap();
|
||||
parameterMap.put(DefaultActionMapper.METHOD_PREFIX + "someMethod", "");
|
||||
@@ -249,16 +250,16 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
ActionMapping actionMapping = defaultActionMapper.getMapping(request, configManager);
|
||||
|
||||
assertEquals("someServletPath", actionMapping.getName());
|
||||
assertEquals(null, actionMapping.getMethod());
|
||||
assertNull(actionMapping.getMethod());
|
||||
}
|
||||
|
||||
public void testGetMappingWithActionName_null() throws Exception {
|
||||
public void testGetMappingWithActionName_null() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
ActionMapping mapping = mapper.getMappingFromActionName(null);
|
||||
assertNull(mapping);
|
||||
}
|
||||
|
||||
public void testGetUri() throws Exception {
|
||||
public void testGetUri() {
|
||||
req.setupGetParameterMap(new HashMap());
|
||||
req.setupGetRequestURI("/my/namespace/actionName.action");
|
||||
req.setupGetServletPath("/my/namespace/actionName.action");
|
||||
@@ -270,7 +271,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("/my/namespace/actionName.action", mapper.getUriFromActionMapping(mapping));
|
||||
}
|
||||
|
||||
public void testGetUriWithSemicolonPresent() throws Exception {
|
||||
public void testGetUriWithSemicolonPresent() {
|
||||
req.setupGetParameterMap(new HashMap());
|
||||
req.setupGetRequestURI("/my/namespace/actionName.action;abc=123rty56");
|
||||
req.setupGetServletPath("/my/namespace/actionName.action;abc=123rty56");
|
||||
@@ -282,7 +283,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("/my/namespace/actionName.action", mapper.getUriFromActionMapping(mapping));
|
||||
}
|
||||
|
||||
public void testGetUriWithMethod() throws Exception {
|
||||
public void testGetUriWithMethod() {
|
||||
req.setupGetParameterMap(new HashMap());
|
||||
req.setupGetRequestURI("/my/namespace/actionName!add.action");
|
||||
req.setupGetServletPath("/my/namespace/actionName!add.action");
|
||||
@@ -295,7 +296,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("/my/namespace/actionName!add.action", mapper.getUriFromActionMapping(mapping));
|
||||
}
|
||||
|
||||
public void testGetUriWithOriginalExtension() throws Exception {
|
||||
public void testGetUriWithOriginalExtension() {
|
||||
ActionMapping mapping = new ActionMapping("actionName", "/ns", null, new HashMap());
|
||||
|
||||
ActionMapping orig = new ActionMapping();
|
||||
@@ -306,7 +307,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("/ns/actionName.foo", mapper.getUriFromActionMapping(mapping));
|
||||
}
|
||||
|
||||
public void testGetMappingWithNoExtension() throws Exception {
|
||||
public void testGetMappingWithNoExtension() {
|
||||
req.setupGetParameterMap(new HashMap());
|
||||
req.setupGetRequestURI("/my/namespace/actionName");
|
||||
req.setupGetServletPath("/my/namespace/actionName");
|
||||
@@ -322,7 +323,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertNull(mapping.getMethod());
|
||||
}
|
||||
|
||||
public void testGetMappingWithNoExtensionButUriHasExtension() throws Exception {
|
||||
public void testGetMappingWithNoExtensionButUriHasExtension() {
|
||||
req.setupGetParameterMap(new HashMap());
|
||||
req.setupGetRequestURI("/my/namespace/actionName.html");
|
||||
req.setupGetServletPath("/my/namespace/actionName.html");
|
||||
@@ -342,7 +343,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
// === test name & namespace ===
|
||||
// =============================
|
||||
|
||||
public void testParseNameAndNamespace1() throws Exception {
|
||||
public void testParseNameAndNamespace1() {
|
||||
ActionMapping actionMapping = new ActionMapping();
|
||||
|
||||
DefaultActionMapper defaultActionMapper = new DefaultActionMapper();
|
||||
@@ -352,7 +353,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals(actionMapping.getNamespace(), "");
|
||||
}
|
||||
|
||||
public void testParseNameAndNamespace2() throws Exception {
|
||||
public void testParseNameAndNamespace2() {
|
||||
ActionMapping actionMapping = new ActionMapping();
|
||||
|
||||
DefaultActionMapper defaultActionMapper = new DefaultActionMapper();
|
||||
@@ -362,7 +363,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals(actionMapping.getNamespace(), "/");
|
||||
}
|
||||
|
||||
public void testParseNameAndNamespace3() throws Exception {
|
||||
public void testParseNameAndNamespace3() {
|
||||
ActionMapping actionMapping = new ActionMapping();
|
||||
|
||||
DefaultActionMapper defaultActionMapper = new DefaultActionMapper();
|
||||
@@ -372,7 +373,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals(actionMapping.getNamespace(), "/my");
|
||||
}
|
||||
|
||||
public void testParseNameAndNamespace_NoSlashes() throws Exception {
|
||||
public void testParseNameAndNamespace_NoSlashes() {
|
||||
ActionMapping actionMapping = new ActionMapping();
|
||||
|
||||
DefaultActionMapper defaultActionMapper = new DefaultActionMapper();
|
||||
@@ -383,7 +384,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals(actionMapping.getNamespace(), "");
|
||||
}
|
||||
|
||||
public void testParseNameAndNamespace_AllowSlashes() throws Exception {
|
||||
public void testParseNameAndNamespace_AllowSlashes() {
|
||||
ActionMapping actionMapping = new ActionMapping();
|
||||
|
||||
DefaultActionMapper defaultActionMapper = new DefaultActionMapper();
|
||||
@@ -399,7 +400,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
// === test special prefix ===
|
||||
// ===========================
|
||||
|
||||
public void testActionPrefixWhenDisabled() throws Exception {
|
||||
public void testActionPrefixWhenDisabled() {
|
||||
Map parameterMap = new HashMap();
|
||||
parameterMap.put(DefaultActionMapper.ACTION_PREFIX + "myAction", "");
|
||||
|
||||
@@ -413,7 +414,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("someServletPath", actionMapping.getName());
|
||||
}
|
||||
|
||||
public void testActionPrefixWhenEnabled() throws Exception {
|
||||
public void testActionPrefixWhenEnabled() {
|
||||
Map parameterMap = new HashMap();
|
||||
parameterMap.put(DefaultActionMapper.ACTION_PREFIX + "myAction", "");
|
||||
|
||||
@@ -428,7 +429,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("myAction", actionMapping.getName());
|
||||
}
|
||||
|
||||
public void testActionPrefixWhenSlashesAndCrossNamespaceDisabled() throws Exception {
|
||||
public void testActionPrefixWhenSlashesAndCrossNamespaceDisabled() {
|
||||
Map parameterMap = new HashMap();
|
||||
parameterMap.put(DefaultActionMapper.ACTION_PREFIX + "my/Action", "");
|
||||
|
||||
@@ -444,7 +445,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("my/Action", actionMapping.getName());
|
||||
}
|
||||
|
||||
public void testActionPrefixWhenSlashesButSlashesDisabledAndCrossNamespaceDisabled() throws Exception {
|
||||
public void testActionPrefixWhenSlashesButSlashesDisabledAndCrossNamespaceDisabled() {
|
||||
Map parameterMap = new HashMap();
|
||||
parameterMap.put(DefaultActionMapper.ACTION_PREFIX + "my/Action", "");
|
||||
|
||||
@@ -460,7 +461,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("Action", actionMapping.getName());
|
||||
}
|
||||
|
||||
public void testActionPrefixWhenSlashesButSlashesDisabledAndCrossNamespace() throws Exception {
|
||||
public void testActionPrefixWhenSlashesButSlashesDisabledAndCrossNamespace() {
|
||||
Map parameterMap = new HashMap();
|
||||
parameterMap.put(DefaultActionMapper.ACTION_PREFIX + "my/Action", "");
|
||||
|
||||
@@ -477,7 +478,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("my/Action", actionMapping.getName());
|
||||
}
|
||||
|
||||
public void testActionPrefixWhenCrossNamespace() throws Exception {
|
||||
public void testActionPrefixWhenCrossNamespace() {
|
||||
Map parameterMap = new HashMap();
|
||||
parameterMap.put(DefaultActionMapper.ACTION_PREFIX + "/my/Action", "");
|
||||
|
||||
@@ -493,7 +494,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("/my/Action", actionMapping.getName());
|
||||
}
|
||||
|
||||
public void testActionPrefix_fromImageButton() throws Exception {
|
||||
public void testActionPrefix_fromImageButton() {
|
||||
Map parameterMap = new HashMap();
|
||||
parameterMap.put(DefaultActionMapper.ACTION_PREFIX + "myAction", "");
|
||||
parameterMap.put(DefaultActionMapper.ACTION_PREFIX + "myAction.x", "");
|
||||
@@ -510,7 +511,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("myAction", actionMapping.getName());
|
||||
}
|
||||
|
||||
public void testActionPrefix_fromIEImageButton() throws Exception {
|
||||
public void testActionPrefix_fromIEImageButton() {
|
||||
Map parameterMap = new HashMap();
|
||||
parameterMap.put(DefaultActionMapper.ACTION_PREFIX + "myAction.x", "");
|
||||
parameterMap.put(DefaultActionMapper.ACTION_PREFIX + "myAction.y", "");
|
||||
@@ -526,7 +527,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("myAction", actionMapping.getName());
|
||||
}
|
||||
|
||||
public void testRedirectPrefix() throws Exception {
|
||||
public void testRedirectPrefix() {
|
||||
Map parameterMap = new HashMap();
|
||||
parameterMap.put("redirect:" + "http://www.google.com", "");
|
||||
|
||||
@@ -542,7 +543,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
public void testUnsafeRedirectPrefix() throws Exception {
|
||||
public void testUnsafeRedirectPrefix() {
|
||||
Map parameterMap = new HashMap();
|
||||
parameterMap.put("redirect:" + "http://%{3*4}", "");
|
||||
|
||||
@@ -558,7 +559,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
public void testRedirectActionPrefix() throws Exception {
|
||||
public void testRedirectActionPrefix() {
|
||||
Map parameterMap = new HashMap();
|
||||
parameterMap.put("redirectAction:" + "myAction", "");
|
||||
|
||||
@@ -575,7 +576,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
public void testUnsafeRedirectActionPrefix() throws Exception {
|
||||
public void testUnsafeRedirectActionPrefix() {
|
||||
Map parameterMap = new HashMap();
|
||||
parameterMap.put("redirectAction:" + "%{3*4}", "");
|
||||
|
||||
@@ -592,7 +593,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
public void testRedirectActionPrefixWithEmptyExtension() throws Exception {
|
||||
public void testRedirectActionPrefixWithEmptyExtension() {
|
||||
Map parameterMap = new HashMap();
|
||||
parameterMap.put("redirectAction:" + "myAction", "");
|
||||
|
||||
@@ -610,7 +611,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
public void testCustomActionPrefix() throws Exception {
|
||||
public void testCustomActionPrefix() {
|
||||
Map parameterMap = new HashMap();
|
||||
parameterMap.put("foo:myAction", "");
|
||||
|
||||
@@ -629,7 +630,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals(actionMapping.getName(), "myAction");
|
||||
}
|
||||
|
||||
public void testDropExtension() throws Exception {
|
||||
public void testDropExtension() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
String name = mapper.dropExtension("foo.action");
|
||||
assertTrue("Name not right: "+name, "foo".equals(name));
|
||||
@@ -639,7 +640,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
|
||||
}
|
||||
|
||||
public void testDropExtensionWhenBlank() throws Exception {
|
||||
public void testDropExtensionWhenBlank() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
mapper.setExtensions("action,,");
|
||||
String name = mapper.dropExtension("foo.action");
|
||||
@@ -650,7 +651,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertNull(mapper.dropExtension("foo."));
|
||||
}
|
||||
|
||||
public void testDropExtensionEmbeddedDot() throws Exception {
|
||||
public void testDropExtensionEmbeddedDot() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
mapper.setExtensions("action,,");
|
||||
|
||||
@@ -661,7 +662,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertTrue("Name not right: "+name, "/foo/bar-1.0/baz".equals(name));
|
||||
}
|
||||
|
||||
public void testGetUriFromActionMapper1() throws Exception {
|
||||
public void testGetUriFromActionMapper1() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
ActionMapping actionMapping = new ActionMapping();
|
||||
actionMapping.setMethod("myMethod");
|
||||
@@ -672,7 +673,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("/myNamespace/myActionName!myMethod.action", uri);
|
||||
}
|
||||
|
||||
public void testGetUriFromActionMapper2() throws Exception {
|
||||
public void testGetUriFromActionMapper2() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
ActionMapping actionMapping = new ActionMapping();
|
||||
actionMapping.setMethod("myMethod");
|
||||
@@ -683,7 +684,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("/myActionName!myMethod.action", uri);
|
||||
}
|
||||
|
||||
public void testGetUriFromActionMapper3() throws Exception {
|
||||
public void testGetUriFromActionMapper3() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
ActionMapping actionMapping = new ActionMapping();
|
||||
actionMapping.setMethod("myMethod");
|
||||
@@ -695,7 +696,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
}
|
||||
|
||||
|
||||
public void testGetUriFromActionMapper4() throws Exception {
|
||||
public void testGetUriFromActionMapper4() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
ActionMapping actionMapping = new ActionMapping();
|
||||
actionMapping.setName("myActionName");
|
||||
@@ -705,7 +706,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("/myActionName.action", uri);
|
||||
}
|
||||
|
||||
public void testGetUriFromActionMapper5() throws Exception {
|
||||
public void testGetUriFromActionMapper5() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
ActionMapping actionMapping = new ActionMapping();
|
||||
actionMapping.setName("myActionName");
|
||||
@@ -716,7 +717,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
}
|
||||
|
||||
//
|
||||
public void testGetUriFromActionMapper6() throws Exception {
|
||||
public void testGetUriFromActionMapper6() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
ActionMapping actionMapping = new ActionMapping();
|
||||
actionMapping.setMethod("myMethod");
|
||||
@@ -727,7 +728,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("/myNamespace/myActionName!myMethod.action?test=bla", uri);
|
||||
}
|
||||
|
||||
public void testGetUriFromActionMapper7() throws Exception {
|
||||
public void testGetUriFromActionMapper7() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
ActionMapping actionMapping = new ActionMapping();
|
||||
actionMapping.setMethod("myMethod");
|
||||
@@ -738,7 +739,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("/myActionName!myMethod.action?test=bla", uri);
|
||||
}
|
||||
|
||||
public void testGetUriFromActionMapper8() throws Exception {
|
||||
public void testGetUriFromActionMapper8() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
ActionMapping actionMapping = new ActionMapping();
|
||||
actionMapping.setMethod("myMethod");
|
||||
@@ -750,7 +751,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
}
|
||||
|
||||
|
||||
public void testGetUriFromActionMapper9() throws Exception {
|
||||
public void testGetUriFromActionMapper9() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
ActionMapping actionMapping = new ActionMapping();
|
||||
actionMapping.setName("myActionName?test=bla");
|
||||
@@ -760,7 +761,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("/myActionName.action?test=bla", uri);
|
||||
}
|
||||
|
||||
public void testGetUriFromActionMapper10() throws Exception {
|
||||
public void testGetUriFromActionMapper10() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
ActionMapping actionMapping = new ActionMapping();
|
||||
actionMapping.setName("myActionName?test=bla");
|
||||
@@ -770,7 +771,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("/myActionName.action?test=bla", uri);
|
||||
}
|
||||
|
||||
public void testGetUriFromActionMapper11() throws Exception {
|
||||
public void testGetUriFromActionMapper11() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
ActionMapping actionMapping = new ActionMapping();
|
||||
actionMapping.setName("myActionName.action");
|
||||
@@ -780,7 +781,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("/myActionName.action", uri);
|
||||
}
|
||||
|
||||
public void testGetUriFromActionMapper12() throws Exception {
|
||||
public void testGetUriFromActionMapper12() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
ActionMapping actionMapping = new ActionMapping();
|
||||
actionMapping.setName("myActionName.action");
|
||||
@@ -790,7 +791,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("/myActionName.action", uri);
|
||||
}
|
||||
|
||||
public void testGetUriFromActionMapper_justActionAndMethod() throws Exception {
|
||||
public void testGetUriFromActionMapper_justActionAndMethod() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
ActionMapping actionMapping = new ActionMapping();
|
||||
actionMapping.setMethod("myMethod");
|
||||
@@ -801,7 +802,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("myActionName!myMethod", uri);
|
||||
}
|
||||
|
||||
public void testGetUriFromActionMapperWhenBlankExtension() throws Exception {
|
||||
public void testGetUriFromActionMapperWhenBlankExtension() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
mapper.setExtensions(",,");
|
||||
ActionMapping actionMapping = new ActionMapping();
|
||||
@@ -813,7 +814,7 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("/myNamespace/myActionName!myMethod", uri);
|
||||
}
|
||||
|
||||
public void testSetExtension() throws Exception {
|
||||
public void testSetExtension() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
mapper.setExtensions("");
|
||||
assertNull(mapper.extensions);
|
||||
@@ -838,20 +839,45 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
|
||||
}
|
||||
|
||||
public void testAllowedActionNames() throws Exception {
|
||||
public void testAllowedNamespaceNames() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
|
||||
String namespace = "/";
|
||||
assertEquals(namespace, mapper.cleanupNamespaceName(namespace));
|
||||
|
||||
namespace = "${namespace}";
|
||||
assertEquals(mapper.defaultNamespaceName, mapper.cleanupNamespaceName(namespace));
|
||||
|
||||
namespace = "${${%{namespace}}}";
|
||||
assertEquals(mapper.defaultNamespaceName, mapper.cleanupNamespaceName(namespace));
|
||||
|
||||
namespace = "${#foo='namespace',#foo}";
|
||||
assertEquals(mapper.defaultNamespaceName, mapper.cleanupNamespaceName(namespace));
|
||||
|
||||
namespace = "/test-namespace/namespace/";
|
||||
assertEquals("/test-namespace/namespace/", mapper.cleanupNamespaceName(namespace));
|
||||
|
||||
namespace = "/test_namespace/namespace-test/";
|
||||
assertEquals("/test_namespace/namespace-test/", mapper.cleanupNamespaceName(namespace));
|
||||
|
||||
namespace = "/test_namespace/namespace.test/";
|
||||
assertEquals("/test_namespace/namespace.test/", mapper.cleanupActionName(namespace));
|
||||
}
|
||||
|
||||
public void testAllowedActionNames() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
|
||||
String actionName = "action";
|
||||
assertEquals(actionName, mapper.cleanupActionName(actionName));
|
||||
|
||||
actionName = "${action}";
|
||||
assertEquals("action", mapper.cleanupActionName(actionName));
|
||||
assertEquals(mapper.defaultActionName, mapper.cleanupActionName(actionName));
|
||||
|
||||
actionName = "${${%{action}}}";
|
||||
assertEquals("action", mapper.cleanupActionName(actionName));
|
||||
assertEquals(mapper.defaultActionName, mapper.cleanupActionName(actionName));
|
||||
|
||||
actionName = "${#foo='action',#foo}";
|
||||
assertEquals("fooactionfoo", mapper.cleanupActionName(actionName));
|
||||
assertEquals(mapper.defaultActionName, mapper.cleanupActionName(actionName));
|
||||
|
||||
actionName = "test-action";
|
||||
assertEquals("test-action", mapper.cleanupActionName(actionName));
|
||||
@@ -863,4 +889,19 @@ public class DefaultActionMapperTest extends StrutsInternalTestCase {
|
||||
assertEquals("test!bar.action", mapper.cleanupActionName(actionName));
|
||||
}
|
||||
|
||||
public void testAllowedMethodNames() {
|
||||
DefaultActionMapper mapper = new DefaultActionMapper();
|
||||
|
||||
assertEquals("", mapper.cleanupMethodName(""));
|
||||
assertEquals("test", mapper.cleanupMethodName("test"));
|
||||
assertEquals("test_method", mapper.cleanupMethodName("test_method"));
|
||||
assertEquals("_test", mapper.cleanupMethodName("_test"));
|
||||
assertEquals("test1", mapper.cleanupMethodName("test1"));
|
||||
|
||||
assertEquals(mapper.defaultMethodName, mapper.cleanupMethodName("2test"));
|
||||
assertEquals(mapper.defaultMethodName, mapper.cleanupMethodName("%{exp}"));
|
||||
assertEquals(mapper.defaultMethodName, mapper.cleanupMethodName("${%{foo}}"));
|
||||
assertEquals(mapper.defaultMethodName, mapper.cleanupMethodName("${#foo='method',#foo}"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -361,7 +361,6 @@ public class CookieInterceptorTest extends StrutsInternalTestCase {
|
||||
ServletActionContext.setRequest(request);
|
||||
|
||||
final Map<String, Boolean> excludedName = new HashMap<String, Boolean>();
|
||||
final Map<String, Boolean> excludedValue = new HashMap<String, Boolean>();
|
||||
|
||||
CookieInterceptor interceptor = new CookieInterceptor() {
|
||||
@Override
|
||||
@@ -370,13 +369,6 @@ public class CookieInterceptorTest extends StrutsInternalTestCase {
|
||||
excludedName.put(name, accepted);
|
||||
return accepted;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAcceptableValue(String value) {
|
||||
boolean accepted = super.isAcceptableValue(value);
|
||||
excludedValue.put(value, accepted);
|
||||
return accepted;
|
||||
}
|
||||
};
|
||||
DefaultExcludedPatternsChecker excludedPatternsChecker = new DefaultExcludedPatternsChecker();
|
||||
excludedPatternsChecker.setAdditionalExcludePatterns(".*(^|\\.|\\[|'|\")class(\\.|\\[|'|\").*");
|
||||
@@ -395,13 +387,6 @@ public class CookieInterceptorTest extends StrutsInternalTestCase {
|
||||
assertFalse(excludedName.get(pollution4));
|
||||
assertFalse(excludedName.get(pollution5));
|
||||
assertFalse(excludedName.get(pollution6));
|
||||
|
||||
assertFalse(excludedValue.get(pollution1));
|
||||
assertFalse(excludedValue.get(pollution2));
|
||||
assertFalse(excludedValue.get(pollution3));
|
||||
assertFalse(excludedValue.get(pollution4));
|
||||
assertFalse(excludedValue.get(pollution5));
|
||||
assertFalse(excludedValue.get(pollution6));
|
||||
}
|
||||
|
||||
public void testCookiesWithStrutsInternalsAccess() throws Exception {
|
||||
@@ -424,7 +409,6 @@ public class CookieInterceptorTest extends StrutsInternalTestCase {
|
||||
ServletActionContext.setRequest(request);
|
||||
|
||||
final Map<String, Boolean> excludedName = new HashMap<String, Boolean>();
|
||||
final Map<String, Boolean> excludedValue = new HashMap<String, Boolean>();
|
||||
|
||||
CookieInterceptor interceptor = new CookieInterceptor() {
|
||||
@Override
|
||||
@@ -433,13 +417,6 @@ public class CookieInterceptorTest extends StrutsInternalTestCase {
|
||||
excludedName.put(name, accepted);
|
||||
return accepted;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAcceptableValue(String value) {
|
||||
boolean accepted = super.isAcceptableValue(value);
|
||||
excludedValue.put(value, accepted);
|
||||
return accepted;
|
||||
}
|
||||
};
|
||||
interceptor.setExcludedPatternsChecker(new DefaultExcludedPatternsChecker());
|
||||
interceptor.setAcceptedPatternsChecker(new DefaultAcceptedPatternsChecker());
|
||||
@@ -453,10 +430,6 @@ public class CookieInterceptorTest extends StrutsInternalTestCase {
|
||||
assertFalse(excludedName.get(sessionCookieName));
|
||||
assertFalse(excludedName.get(appCookieName));
|
||||
assertFalse(excludedName.get(reqCookieName));
|
||||
|
||||
assertFalse(excludedValue.get(sessionCookieValue));
|
||||
assertFalse(excludedValue.get(appCookieValue));
|
||||
assertFalse(excludedValue.get(reqCookieValue));
|
||||
}
|
||||
|
||||
public static class MockActionWithCookieAware extends ActionSupport implements CookiesAware {
|
||||
|
||||
@@ -173,12 +173,12 @@ public class I18nInterceptorTest {
|
||||
|
||||
@Test
|
||||
public void testCountryOnlyLocale() throws Exception {
|
||||
params.put(I18nInterceptor.DEFAULT_PARAMETER, "DK");
|
||||
params.put(I18nInterceptor.DEFAULT_PARAMETER, "NL");
|
||||
interceptor.intercept(mai);
|
||||
|
||||
assertNull(params.get(I18nInterceptor.DEFAULT_PARAMETER)); // should have been removed
|
||||
|
||||
Locale denmark = new Locale("DK");
|
||||
Locale denmark = new Locale("NL");
|
||||
assertNotNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should be stored here
|
||||
assertEquals(denmark, session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should create a locale object
|
||||
}
|
||||
@@ -197,31 +197,31 @@ public class I18nInterceptorTest {
|
||||
|
||||
@Test
|
||||
public void testWithVariant() throws Exception {
|
||||
params.put(I18nInterceptor.DEFAULT_PARAMETER, "fr_CA_xx");
|
||||
params.put(I18nInterceptor.DEFAULT_PARAMETER, "ja_JP_JP");
|
||||
interceptor.intercept(mai);
|
||||
|
||||
assertNull(params.get(I18nInterceptor.DEFAULT_PARAMETER)); // should have been removed
|
||||
|
||||
Locale variant = new Locale("fr", "CA", "xx");
|
||||
Locale variant = new Locale("ja", "JP", "JP");
|
||||
Locale locale = (Locale) session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE);
|
||||
assertNotNull(locale); // should be stored here
|
||||
assertEquals(variant, locale);
|
||||
assertEquals("xx", locale.getVariant());
|
||||
assertEquals("JP", locale.getVariant());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithVariantRequestOnly() throws Exception {
|
||||
params.put(I18nInterceptor.DEFAULT_REQUESTONLY_PARAMETER, "fr_CA_xx");
|
||||
params.put(I18nInterceptor.DEFAULT_REQUESTONLY_PARAMETER, "ja_JP_JP");
|
||||
interceptor.intercept(mai);
|
||||
|
||||
assertNull(params.get(I18nInterceptor.DEFAULT_PARAMETER)); // should have been removed
|
||||
assertNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE));
|
||||
|
||||
Locale variant = new Locale("fr", "CA", "xx");
|
||||
Locale variant = new Locale("ja", "JP", "JP");
|
||||
Locale locale = mai.getInvocationContext().getLocale();
|
||||
assertNotNull(locale); // should be stored here
|
||||
assertEquals(variant, locale);
|
||||
assertEquals("xx", locale.getVariant());
|
||||
assertEquals("JP", locale.getVariant());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user