mirror of
https://github.com/apache/struts.git
synced 2026-08-08 16:16:58 +00:00
Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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.25</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-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.25</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.25</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.25</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.25</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-portlet</artifactId>
|
||||
|
||||
@@ -26,12 +26,12 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-apps</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.25</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-rest-showcase</artifactId>
|
||||
<packaging>war</packaging>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.25</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.25</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-showcase</artifactId>
|
||||
|
||||
+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";
|
||||
}
|
||||
}
|
||||
@@ -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.25</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.25</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.25</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.25</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.25</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.25</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.25</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.25</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+12
-1
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.25</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-assembly</artifactId>
|
||||
@@ -129,6 +129,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 +144,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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
+3
-3
@@ -10,7 +10,7 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-bom</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.25</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.25</struts-version.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
@@ -197,6 +197,6 @@
|
||||
</dependencyManagement>
|
||||
|
||||
<scm>
|
||||
<tag>STRUTS_2_3_24_1</tag>
|
||||
<tag>STRUTS_2_3_25</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.25</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.25</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.25</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-osgi-bundles</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.25</version>
|
||||
</parent>
|
||||
<artifactId>struts2-core</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
+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);
|
||||
|
||||
}
|
||||
@@ -287,6 +287,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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -394,6 +394,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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
+11
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,94 @@
|
||||
/*
|
||||
* 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.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.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 {
|
||||
isRedirect = invocation.getResult() instanceof ServletRedirectResult;
|
||||
} 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();
|
||||
public static boolean isExpression(String expr) {
|
||||
return expr.startsWith("%{") && expr.endsWith("}");
|
||||
}
|
||||
|
||||
public static boolean containsExpression(String expr) {
|
||||
return expr.contains("%{") && expr.contains("}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -365,6 +365,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);
|
||||
|
||||
@@ -27,11 +27,11 @@ import com.opensymphony.xwork2.util.logging.LoggerFactory;
|
||||
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 +85,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) {
|
||||
@@ -241,6 +246,10 @@ public class DefaultUrlHelper implements UrlHelper {
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isValidScheme(String scheme) {
|
||||
return HTTP_PROTOCOL.equals(scheme) || HTTPS_PROTOCOL.equals(scheme);
|
||||
}
|
||||
|
||||
private String buildParameterSubstring(String name, String value) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(encode(name));
|
||||
@@ -267,15 +276,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);
|
||||
} catch (Exception e) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("Could not decode URL parameter '#0', returning value un-decoded", input);
|
||||
}
|
||||
|
||||
@@ -39,20 +39,14 @@
|
||||
<struts>
|
||||
|
||||
<constant name="struts.excludedClasses"
|
||||
value="
|
||||
java.lang.Object,
|
||||
java.lang.Runtime,
|
||||
java.lang.System,
|
||||
java.lang.Class,
|
||||
java.lang.ClassLoader,
|
||||
java.lang.Shutdown,
|
||||
ognl.OgnlContext,
|
||||
ognl.MemberAccess,
|
||||
ognl.ClassResolver,
|
||||
ognl.TypeConverter,
|
||||
com.opensymphony.xwork2.ActionContext" />
|
||||
value="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="java.lang,ognl,javax" />
|
||||
|
||||
<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??>
|
||||
|
||||
@@ -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
|
||||
|
||||
+48
-146
@@ -27,8 +27,9 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.opensymphony.xwork2.interceptor.PreResultListener;
|
||||
import org.apache.struts2.ServletActionContext;
|
||||
import org.apache.struts2.StrutsInternalTestCase;
|
||||
import org.apache.struts2.dispatcher.ServletActionRedirectResult;
|
||||
import org.easymock.EasyMock;
|
||||
|
||||
import com.opensymphony.xwork2.Action;
|
||||
@@ -36,6 +37,10 @@ import com.opensymphony.xwork2.ActionContext;
|
||||
import com.opensymphony.xwork2.ActionInvocation;
|
||||
import com.opensymphony.xwork2.ActionSupport;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
|
||||
/**
|
||||
* Test case for MessageStoreInterceptor.
|
||||
@@ -44,73 +49,16 @@ import com.opensymphony.xwork2.ActionSupport;
|
||||
*/
|
||||
public class MessageStoreInterceptorTest extends StrutsInternalTestCase {
|
||||
|
||||
public void testStoreMessage() throws Exception {
|
||||
MessageStoreInterceptor interceptor = new MessageStoreInterceptor();
|
||||
interceptor.setAllowRequestParameterSwitch(true);
|
||||
interceptor.setOperationMode(MessageStoreInterceptor.STORE_MODE);
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
HttpServletResponse response = EasyMock.createNiceControl().createMock(HttpServletResponse.class);
|
||||
response.isCommitted();
|
||||
EasyMock.expectLastCall().andReturn(Boolean.FALSE);
|
||||
EasyMock.replay(response);
|
||||
|
||||
Map paramMap = new LinkedHashMap();
|
||||
Map sessionMap = new LinkedHashMap();
|
||||
|
||||
ActionSupport action = new ActionSupport();
|
||||
action.addActionError("some action error 1");
|
||||
action.addActionError("some action error 2");
|
||||
action.addActionMessage("some action message 1");
|
||||
action.addActionMessage("some action message 2");
|
||||
action.addFieldError("field1", "some field error 1");
|
||||
action.addFieldError("field2", "some field error 2");
|
||||
|
||||
ActionContext actionContext = new ActionContext(new HashMap());
|
||||
actionContext.put(ActionContext.PARAMETERS, paramMap);
|
||||
actionContext.put(ActionContext.SESSION, sessionMap);
|
||||
|
||||
// Mock (ActionInvocation)
|
||||
ActionInvocation mockActionInvocation = EasyMock.createControl().createMock(ActionInvocation.class);
|
||||
mockActionInvocation.getInvocationContext();
|
||||
EasyMock.expectLastCall().andReturn(actionContext);
|
||||
EasyMock.expectLastCall().anyTimes();
|
||||
|
||||
mockActionInvocation.invoke();
|
||||
EasyMock.expectLastCall().andReturn(Action.SUCCESS);
|
||||
|
||||
mockActionInvocation.getAction();
|
||||
EasyMock.expectLastCall().andReturn(action);
|
||||
|
||||
mockActionInvocation.getResult();
|
||||
EasyMock.expectLastCall().andReturn(new ServletActionRedirectResult());
|
||||
|
||||
EasyMock.replay(mockActionInvocation);
|
||||
|
||||
interceptor.init();
|
||||
interceptor.intercept(mockActionInvocation);
|
||||
interceptor.destroy();
|
||||
|
||||
assertEquals(sessionMap.size(), 3);
|
||||
assertTrue(sessionMap.containsKey(MessageStoreInterceptor.actionErrorsSessionKey));
|
||||
assertTrue(sessionMap.containsKey(MessageStoreInterceptor.actionMessagesSessionKey));
|
||||
assertTrue(sessionMap.containsKey(MessageStoreInterceptor.fieldErrorsSessionKey));
|
||||
|
||||
List actionErrors = (List) sessionMap.get(MessageStoreInterceptor.actionErrorsSessionKey);
|
||||
List actionMessages = (List) sessionMap.get(MessageStoreInterceptor.actionMessagesSessionKey);
|
||||
Map fieldErrors = (Map) sessionMap.get(MessageStoreInterceptor.fieldErrorsSessionKey);
|
||||
|
||||
assertEquals(actionErrors.size(), 2);
|
||||
assertEquals(actionMessages.size(), 2);
|
||||
assertEquals(fieldErrors.size(), 2);
|
||||
|
||||
assertTrue(actionErrors.contains("some action error 1"));
|
||||
assertTrue(actionErrors.contains("some action error 2"));
|
||||
assertTrue(actionMessages.contains("some action message 1"));
|
||||
assertTrue(actionMessages.contains("some action message 2"));
|
||||
assertTrue(fieldErrors.containsKey("field1"));
|
||||
assertTrue(fieldErrors.containsKey("field2"));
|
||||
assertEquals(((List)fieldErrors.get("field1")).size(), 1);
|
||||
assertEquals(((List)fieldErrors.get("field2")).size(), 1);
|
||||
assertEquals(((List)fieldErrors.get("field1")).get(0), "some field error 1");
|
||||
assertEquals(((List)fieldErrors.get("field2")).get(0), "some field error 2");
|
||||
|
||||
EasyMock.verify(mockActionInvocation);
|
||||
ServletActionContext.setResponse(response);
|
||||
}
|
||||
|
||||
public void testIgnoreMessageWithoutSession() throws Exception {
|
||||
@@ -128,6 +76,15 @@ public class MessageStoreInterceptorTest extends StrutsInternalTestCase {
|
||||
ActionContext actionContext = new ActionContext(new HashMap());
|
||||
actionContext.put(ActionContext.PARAMETERS, paramMap);
|
||||
|
||||
HttpSession mockedSession = EasyMock.createControl().createMock(HttpSession.class);
|
||||
HttpServletRequest mockedRequest = EasyMock.createControl().createMock(HttpServletRequest.class);
|
||||
mockedRequest.getSession(false);
|
||||
EasyMock.expectLastCall().andReturn(mockedSession);
|
||||
EasyMock.expectLastCall().once();
|
||||
ServletActionContext.setRequest(mockedRequest);
|
||||
|
||||
EasyMock.replay(mockedRequest);
|
||||
|
||||
// Mock (ActionInvocation)
|
||||
ActionInvocation mockActionInvocation = EasyMock.createControl().createMock(ActionInvocation.class);
|
||||
mockActionInvocation.getInvocationContext();
|
||||
@@ -137,11 +94,8 @@ public class MessageStoreInterceptorTest extends StrutsInternalTestCase {
|
||||
mockActionInvocation.invoke();
|
||||
EasyMock.expectLastCall().andReturn(Action.SUCCESS);
|
||||
|
||||
mockActionInvocation.getAction();
|
||||
EasyMock.expectLastCall().andReturn(action);
|
||||
|
||||
mockActionInvocation.getResult();
|
||||
EasyMock.expectLastCall().andReturn(new ServletActionRedirectResult());
|
||||
mockActionInvocation.addPreResultListener(EasyMock.<PreResultListener>anyObject());
|
||||
EasyMock.expectLastCall();
|
||||
|
||||
EasyMock.replay(mockActionInvocation);
|
||||
|
||||
@@ -186,6 +140,14 @@ public class MessageStoreInterceptorTest extends StrutsInternalTestCase {
|
||||
sessionMap.put(MessageStoreInterceptor.actionMessagesSessionKey, actionMessages);
|
||||
sessionMap.put(MessageStoreInterceptor.fieldErrorsSessionKey, fieldErrors);
|
||||
|
||||
HttpSession mockedSession = EasyMock.createControl().createMock(HttpSession.class);
|
||||
HttpServletRequest mockedRequest = EasyMock.createControl().createMock(HttpServletRequest.class);
|
||||
mockedRequest.getSession(false);
|
||||
EasyMock.expectLastCall().andReturn(mockedSession);
|
||||
EasyMock.expectLastCall().once();
|
||||
ServletActionContext.setRequest(mockedRequest);
|
||||
|
||||
EasyMock.replay(mockedRequest);
|
||||
|
||||
ActionContext actionContext = new ActionContext(new HashMap());
|
||||
actionContext.put(ActionContext.PARAMETERS, paramsMap);
|
||||
@@ -199,8 +161,8 @@ public class MessageStoreInterceptorTest extends StrutsInternalTestCase {
|
||||
EasyMock.expectLastCall().andReturn(action);
|
||||
EasyMock.expectLastCall().anyTimes();
|
||||
|
||||
mockActionInvocation.getResult();
|
||||
EasyMock.expectLastCall().andReturn(new ServletActionRedirectResult());
|
||||
mockActionInvocation.addPreResultListener(EasyMock.<PreResultListener>anyObject());
|
||||
EasyMock.expectLastCall();
|
||||
|
||||
EasyMock.replay(mockActionInvocation);
|
||||
|
||||
@@ -244,12 +206,24 @@ public class MessageStoreInterceptorTest extends StrutsInternalTestCase {
|
||||
actionContext.put(ActionContext.PARAMETERS, paramMap);
|
||||
actionContext.put(ActionContext.SESSION, sessionMap);
|
||||
|
||||
HttpSession mockedSession = EasyMock.createControl().createMock(HttpSession.class);
|
||||
HttpServletRequest mockedRequest = EasyMock.createControl().createMock(HttpServletRequest.class);
|
||||
mockedRequest.getSession(false);
|
||||
EasyMock.expectLastCall().andReturn(mockedSession);
|
||||
EasyMock.expectLastCall().once();
|
||||
ServletActionContext.setRequest(mockedRequest);
|
||||
|
||||
EasyMock.replay(mockedRequest);
|
||||
|
||||
// Mock (ActionInvocation)
|
||||
ActionInvocation mockActionInvocation = EasyMock.createControl().createMock(ActionInvocation.class);
|
||||
mockActionInvocation.getInvocationContext();
|
||||
EasyMock.expectLastCall().andReturn(actionContext);
|
||||
EasyMock.expectLastCall().anyTimes();
|
||||
|
||||
mockActionInvocation.addPreResultListener(EasyMock.<PreResultListener>anyObject());
|
||||
EasyMock.expectLastCall();
|
||||
|
||||
mockActionInvocation.invoke();
|
||||
EasyMock.expectLastCall().andReturn(Action.SUCCESS);
|
||||
|
||||
@@ -257,85 +231,12 @@ public class MessageStoreInterceptorTest extends StrutsInternalTestCase {
|
||||
EasyMock.expectLastCall().andReturn(action);
|
||||
EasyMock.expectLastCall().anyTimes();
|
||||
|
||||
mockActionInvocation.getResult();
|
||||
EasyMock.expectLastCall().andReturn(new ServletActionRedirectResult());
|
||||
|
||||
EasyMock.replay(mockActionInvocation);
|
||||
|
||||
interceptor.init();
|
||||
interceptor.intercept(mockActionInvocation);
|
||||
interceptor.destroy();
|
||||
|
||||
assertEquals(3, sessionMap.size());
|
||||
assertTrue(sessionMap.containsKey(MessageStoreInterceptor.actionErrorsSessionKey));
|
||||
assertTrue(sessionMap.containsKey(MessageStoreInterceptor.actionMessagesSessionKey));
|
||||
assertTrue(sessionMap.containsKey(MessageStoreInterceptor.fieldErrorsSessionKey));
|
||||
|
||||
List actionErrors = (List) sessionMap.get(MessageStoreInterceptor.actionErrorsSessionKey);
|
||||
List actionMessages = (List) sessionMap.get(MessageStoreInterceptor.actionMessagesSessionKey);
|
||||
Map fieldErrors = (Map) sessionMap.get(MessageStoreInterceptor.fieldErrorsSessionKey);
|
||||
|
||||
assertEquals(2, actionErrors.size());
|
||||
assertEquals(2, actionMessages.size());
|
||||
assertEquals(2, fieldErrors.size());
|
||||
|
||||
assertTrue(actionErrors.contains("some action error 1"));
|
||||
assertTrue(actionErrors.contains("some action error 2"));
|
||||
assertTrue(actionMessages.contains("some action message 1"));
|
||||
assertTrue(actionMessages.contains("some action message 2"));
|
||||
assertTrue(fieldErrors.containsKey("field1"));
|
||||
assertTrue(fieldErrors.containsKey("field2"));
|
||||
assertEquals(((List)fieldErrors.get("field1")).size(), 1);
|
||||
assertEquals(((List)fieldErrors.get("field2")).size(), 1);
|
||||
assertEquals(((List)fieldErrors.get("field1")).get(0), "some field error 1");
|
||||
assertEquals(((List)fieldErrors.get("field2")).get(0), "some field error 2");
|
||||
|
||||
EasyMock.verify(mockActionInvocation);
|
||||
|
||||
action = new ActionSupport();
|
||||
|
||||
mockActionInvocation = EasyMock.createControl().createMock(ActionInvocation.class);
|
||||
mockActionInvocation.invoke();
|
||||
EasyMock.expectLastCall().andReturn(Action.SUCCESS);
|
||||
|
||||
sessionMap.put(MessageStoreInterceptor.actionErrorsSessionKey, actionErrors);
|
||||
sessionMap.put(MessageStoreInterceptor.actionMessagesSessionKey, actionMessages);
|
||||
sessionMap.put(MessageStoreInterceptor.fieldErrorsSessionKey, fieldErrors);
|
||||
|
||||
|
||||
actionContext = new ActionContext(new HashMap());
|
||||
actionContext.put(ActionContext.PARAMETERS, paramMap);
|
||||
actionContext.put(ActionContext.SESSION, sessionMap);
|
||||
|
||||
mockActionInvocation.getInvocationContext();
|
||||
EasyMock.expectLastCall().andReturn(actionContext);
|
||||
EasyMock.expectLastCall().anyTimes();
|
||||
|
||||
mockActionInvocation.getAction();
|
||||
EasyMock.expectLastCall().andReturn(action);
|
||||
EasyMock.expectLastCall().anyTimes();
|
||||
|
||||
mockActionInvocation.getResult();
|
||||
EasyMock.expectLastCall().andReturn(new ServletActionRedirectResult());
|
||||
|
||||
EasyMock.replay(mockActionInvocation);
|
||||
|
||||
interceptor.init();
|
||||
interceptor.intercept(mockActionInvocation);
|
||||
interceptor.destroy();
|
||||
|
||||
assertEquals(action.getActionErrors().size(), 2);
|
||||
assertEquals(action.getActionMessages().size(), 2);
|
||||
assertEquals(action.getFieldErrors().size(), 2);
|
||||
assertTrue(action.getActionErrors().contains("some action error 1"));
|
||||
assertTrue(action.getActionErrors().contains("some action error 2"));
|
||||
assertTrue(action.getActionMessages().contains("some action message 1"));
|
||||
assertTrue(action.getActionMessages().contains("some action message 2"));
|
||||
assertEquals(((List)action.getFieldErrors().get("field1")).size(), 1);
|
||||
assertEquals(((List)action.getFieldErrors().get("field2")).size(), 1);
|
||||
assertEquals(((List)action.getFieldErrors().get("field1")).get(0), "some field error 1");
|
||||
assertEquals(((List)action.getFieldErrors().get("field2")).get(0), "some field error 2");
|
||||
|
||||
EasyMock.verify(mockActionInvocation);
|
||||
}
|
||||
|
||||
@@ -407,4 +308,5 @@ public class MessageStoreInterceptorTest extends StrutsInternalTestCase {
|
||||
EasyMock.verify(mockActionInvocation);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
package org.apache.struts2.interceptor;
|
||||
|
||||
import com.opensymphony.xwork2.Action;
|
||||
import com.opensymphony.xwork2.ActionContext;
|
||||
import com.opensymphony.xwork2.ActionInvocation;
|
||||
import com.opensymphony.xwork2.ActionSupport;
|
||||
import org.apache.struts2.ServletActionContext;
|
||||
import org.apache.struts2.StrutsInternalTestCase;
|
||||
import org.apache.struts2.dispatcher.ServletActionRedirectResult;
|
||||
import org.easymock.EasyMock;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class MessageStorePreResultListenerTest extends StrutsInternalTestCase {
|
||||
|
||||
public void testSessionWasInvalidated() throws Exception {
|
||||
// given
|
||||
ActionContext actionContext = new ActionContext(new HashMap());
|
||||
actionContext.put(ActionContext.PARAMETERS, new LinkedHashMap());
|
||||
|
||||
ActionInvocation mockActionInvocation = EasyMock.createControl().createMock(ActionInvocation.class);
|
||||
|
||||
mockActionInvocation.getInvocationContext();
|
||||
EasyMock.expectLastCall().andReturn(actionContext);
|
||||
EasyMock.expectLastCall().anyTimes();
|
||||
|
||||
EasyMock.replay(mockActionInvocation);
|
||||
|
||||
HttpServletRequest mockedRequest = EasyMock.createControl().createMock(HttpServletRequest.class);
|
||||
mockedRequest.getSession(false);
|
||||
EasyMock.expectLastCall().andReturn(null);
|
||||
EasyMock.expectLastCall().once();
|
||||
ServletActionContext.setRequest(mockedRequest);
|
||||
|
||||
EasyMock.replay(mockedRequest);
|
||||
|
||||
HttpServletResponse mockedResponse = EasyMock.createControl().createMock(HttpServletResponse.class);
|
||||
mockedResponse.isCommitted();
|
||||
EasyMock.expectLastCall().andReturn(false);
|
||||
EasyMock.expectLastCall().once();
|
||||
ServletActionContext.setResponse(mockedResponse);
|
||||
|
||||
EasyMock.replay(mockedResponse);
|
||||
|
||||
// when
|
||||
MessageStoreInterceptor msi = new MessageStoreInterceptor();
|
||||
MessageStorePreResultListener listener = new MessageStorePreResultListener(msi);
|
||||
listener.beforeResult(mockActionInvocation, Action.SUCCESS);
|
||||
|
||||
// then
|
||||
EasyMock.verify(mockActionInvocation);
|
||||
EasyMock.verify(mockedRequest);
|
||||
EasyMock.verify(mockedResponse);
|
||||
}
|
||||
|
||||
public void testResponseWasComitted() throws Exception {
|
||||
// given
|
||||
ActionContext actionContext = new ActionContext(new HashMap());
|
||||
actionContext.put(ActionContext.PARAMETERS, new LinkedHashMap());
|
||||
|
||||
ActionInvocation mockActionInvocation = EasyMock.createControl().createMock(ActionInvocation.class);
|
||||
|
||||
mockActionInvocation.getInvocationContext();
|
||||
EasyMock.expectLastCall().andReturn(actionContext);
|
||||
EasyMock.expectLastCall().anyTimes();
|
||||
|
||||
EasyMock.replay(mockActionInvocation);
|
||||
|
||||
HttpServletResponse mockedResponse = EasyMock.createControl().createMock(HttpServletResponse.class);
|
||||
mockedResponse.isCommitted();
|
||||
EasyMock.expectLastCall().andReturn(true);
|
||||
EasyMock.expectLastCall().once();
|
||||
ServletActionContext.setResponse(mockedResponse);
|
||||
|
||||
EasyMock.replay(mockedResponse);
|
||||
|
||||
// when
|
||||
MessageStoreInterceptor msi = new MessageStoreInterceptor();
|
||||
MessageStorePreResultListener listener = new MessageStorePreResultListener(msi);
|
||||
listener.beforeResult(mockActionInvocation, Action.SUCCESS);
|
||||
|
||||
// then
|
||||
EasyMock.verify(mockActionInvocation);
|
||||
EasyMock.verify(mockedResponse);
|
||||
}
|
||||
|
||||
public void testAutomatic() throws Exception {
|
||||
MessageStoreInterceptor interceptor = new MessageStoreInterceptor();
|
||||
interceptor.setOperationMode(MessageStoreInterceptor.AUTOMATIC_MODE);
|
||||
|
||||
MessageStorePreResultListener listener = new MessageStorePreResultListener(interceptor);
|
||||
|
||||
Map paramMap = new LinkedHashMap();
|
||||
Map sessionMap = new LinkedHashMap();
|
||||
|
||||
ActionSupport action = new ActionSupport();
|
||||
action.addActionError("some action error 1");
|
||||
action.addActionError("some action error 2");
|
||||
action.addActionMessage("some action message 1");
|
||||
action.addActionMessage("some action message 2");
|
||||
action.addFieldError("field1", "some field error 1");
|
||||
action.addFieldError("field2", "some field error 2");
|
||||
|
||||
ActionContext actionContext = new ActionContext(new HashMap());
|
||||
actionContext.put(ActionContext.PARAMETERS, paramMap);
|
||||
actionContext.put(ActionContext.SESSION, sessionMap);
|
||||
|
||||
HttpSession mockedSession = EasyMock.createControl().createMock(HttpSession.class);
|
||||
HttpServletRequest mockedRequest = EasyMock.createControl().createMock(HttpServletRequest.class);
|
||||
mockedRequest.getSession(false);
|
||||
EasyMock.expectLastCall().andReturn(mockedSession);
|
||||
EasyMock.expectLastCall().once();
|
||||
ServletActionContext.setRequest(mockedRequest);
|
||||
|
||||
EasyMock.replay(mockedRequest);
|
||||
|
||||
HttpServletResponse mockedResponse = EasyMock.createControl().createMock(HttpServletResponse.class);
|
||||
mockedResponse.isCommitted();
|
||||
EasyMock.expectLastCall().andReturn(false);
|
||||
EasyMock.expectLastCall().once();
|
||||
ServletActionContext.setResponse(mockedResponse);
|
||||
|
||||
EasyMock.replay(mockedResponse);
|
||||
|
||||
// Mock (ActionInvocation)
|
||||
ActionInvocation mockActionInvocation = EasyMock.createControl().createMock(ActionInvocation.class);
|
||||
mockActionInvocation.getInvocationContext();
|
||||
EasyMock.expectLastCall().andReturn(actionContext);
|
||||
EasyMock.expectLastCall().anyTimes();
|
||||
|
||||
mockActionInvocation.getAction();
|
||||
EasyMock.expectLastCall().andReturn(action);
|
||||
EasyMock.expectLastCall().anyTimes();
|
||||
|
||||
mockActionInvocation.getResult();
|
||||
EasyMock.expectLastCall().andReturn(new ServletActionRedirectResult());
|
||||
|
||||
EasyMock.replay(mockActionInvocation);
|
||||
|
||||
interceptor.init();
|
||||
listener.beforeResult(mockActionInvocation, Action.SUCCESS);
|
||||
|
||||
List actionErrors = (List) sessionMap.get(MessageStoreInterceptor.actionErrorsSessionKey);
|
||||
List actionMessages = (List) sessionMap.get(MessageStoreInterceptor.actionMessagesSessionKey);
|
||||
Map fieldErrors = (Map) sessionMap.get(MessageStoreInterceptor.fieldErrorsSessionKey);
|
||||
|
||||
assertEquals(actionErrors.size(), 2);
|
||||
assertEquals(actionMessages.size(), 2);
|
||||
assertEquals(fieldErrors.size(), 2);
|
||||
assertTrue(actionErrors.contains("some action error 1"));
|
||||
assertTrue(actionErrors.contains("some action error 2"));
|
||||
assertTrue(actionMessages.contains("some action message 1"));
|
||||
assertTrue(actionMessages.contains("some action message 2"));
|
||||
assertEquals(((List) fieldErrors.get("field1")).size(), 1);
|
||||
assertEquals(((List) fieldErrors.get("field2")).size(), 1);
|
||||
assertEquals(((List) fieldErrors.get("field1")).get(0), "some field error 1");
|
||||
assertEquals(((List) fieldErrors.get("field2")).get(0), "some field error 2");
|
||||
|
||||
EasyMock.verify(mockActionInvocation);
|
||||
}
|
||||
|
||||
public void testStoreMessage() throws Exception {
|
||||
MessageStoreInterceptor interceptor = new MessageStoreInterceptor();
|
||||
interceptor.setAllowRequestParameterSwitch(true);
|
||||
interceptor.setOperationMode(MessageStoreInterceptor.STORE_MODE);
|
||||
|
||||
MessageStorePreResultListener listener = new MessageStorePreResultListener(interceptor);
|
||||
|
||||
Map paramMap = new LinkedHashMap();
|
||||
Map sessionMap = new LinkedHashMap();
|
||||
|
||||
ActionSupport action = new ActionSupport();
|
||||
action.addActionError("some action error 1");
|
||||
action.addActionError("some action error 2");
|
||||
action.addActionMessage("some action message 1");
|
||||
action.addActionMessage("some action message 2");
|
||||
action.addFieldError("field1", "some field error 1");
|
||||
action.addFieldError("field2", "some field error 2");
|
||||
|
||||
ActionContext actionContext = new ActionContext(new HashMap());
|
||||
actionContext.put(ActionContext.PARAMETERS, paramMap);
|
||||
actionContext.put(ActionContext.SESSION, sessionMap);
|
||||
|
||||
HttpSession mockedSession = EasyMock.createControl().createMock(HttpSession.class);
|
||||
HttpServletRequest mockedRequest = EasyMock.createControl().createMock(HttpServletRequest.class);
|
||||
mockedRequest.getSession(false);
|
||||
EasyMock.expectLastCall().andReturn(mockedSession);
|
||||
EasyMock.expectLastCall().once();
|
||||
ServletActionContext.setRequest(mockedRequest);
|
||||
|
||||
EasyMock.replay(mockedRequest);
|
||||
|
||||
HttpServletResponse mockedResponse = EasyMock.createControl().createMock(HttpServletResponse.class);
|
||||
mockedResponse.isCommitted();
|
||||
EasyMock.expectLastCall().andReturn(false);
|
||||
EasyMock.expectLastCall().once();
|
||||
ServletActionContext.setResponse(mockedResponse);
|
||||
|
||||
EasyMock.replay(mockedResponse);
|
||||
|
||||
// Mock (ActionInvocation)
|
||||
ActionInvocation mockActionInvocation = EasyMock.createControl().createMock(ActionInvocation.class);
|
||||
mockActionInvocation.getInvocationContext();
|
||||
EasyMock.expectLastCall().andReturn(actionContext);
|
||||
EasyMock.expectLastCall().anyTimes();
|
||||
|
||||
mockActionInvocation.getAction();
|
||||
EasyMock.expectLastCall().andReturn(action);
|
||||
|
||||
mockActionInvocation.getResult();
|
||||
EasyMock.expectLastCall().andReturn(new ServletActionRedirectResult());
|
||||
|
||||
EasyMock.replay(mockActionInvocation);
|
||||
|
||||
interceptor.init();
|
||||
listener.beforeResult(mockActionInvocation, Action.SUCCESS);
|
||||
|
||||
assertEquals(sessionMap.size(), 3);
|
||||
assertTrue(sessionMap.containsKey(MessageStoreInterceptor.actionErrorsSessionKey));
|
||||
assertTrue(sessionMap.containsKey(MessageStoreInterceptor.actionMessagesSessionKey));
|
||||
assertTrue(sessionMap.containsKey(MessageStoreInterceptor.fieldErrorsSessionKey));
|
||||
|
||||
List actionErrors = (List) sessionMap.get(MessageStoreInterceptor.actionErrorsSessionKey);
|
||||
List actionMessages = (List) sessionMap.get(MessageStoreInterceptor.actionMessagesSessionKey);
|
||||
Map fieldErrors = (Map) sessionMap.get(MessageStoreInterceptor.fieldErrorsSessionKey);
|
||||
|
||||
assertEquals(actionErrors.size(), 2);
|
||||
assertEquals(actionMessages.size(), 2);
|
||||
assertEquals(fieldErrors.size(), 2);
|
||||
|
||||
assertTrue(actionErrors.contains("some action error 1"));
|
||||
assertTrue(actionErrors.contains("some action error 2"));
|
||||
assertTrue(actionMessages.contains("some action message 1"));
|
||||
assertTrue(actionMessages.contains("some action message 2"));
|
||||
assertTrue(fieldErrors.containsKey("field1"));
|
||||
assertTrue(fieldErrors.containsKey("field2"));
|
||||
assertEquals(((List)fieldErrors.get("field1")).size(), 1);
|
||||
assertEquals(((List)fieldErrors.get("field2")).size(), 1);
|
||||
assertEquals(((List)fieldErrors.get("field1")).get(0), "some field error 1");
|
||||
assertEquals(((List)fieldErrors.get("field2")).get(0), "some field error 2");
|
||||
|
||||
EasyMock.verify(mockActionInvocation);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -71,6 +71,17 @@ public class ComponentUtilsTest extends StrutsInternalTestCase {
|
||||
assertTrue(actual);
|
||||
}
|
||||
|
||||
public void testIsExpressionIsFalseWhenCombined() throws Exception {
|
||||
// given
|
||||
String anExpression = "bar%{foo}";
|
||||
|
||||
// when
|
||||
boolean actual = ComponentUtils.isExpression(anExpression);
|
||||
|
||||
// then
|
||||
assertFalse(actual);
|
||||
}
|
||||
|
||||
public void testIsExpressionIsFalse() throws Exception {
|
||||
// given
|
||||
String anExpression = "foo";
|
||||
@@ -81,6 +92,39 @@ public class ComponentUtilsTest extends StrutsInternalTestCase {
|
||||
// then
|
||||
assertFalse(actual);
|
||||
}
|
||||
|
||||
public void testContainsExpressionIsTrue() throws Exception {
|
||||
// given
|
||||
String anExpression = "%{foo}";
|
||||
|
||||
// when
|
||||
boolean actual = ComponentUtils.containsExpression(anExpression);
|
||||
|
||||
// then
|
||||
assertTrue(actual);
|
||||
}
|
||||
|
||||
public void testIsContainsIsTrueWhenCombined() throws Exception {
|
||||
// given
|
||||
String anExpression = "bar%{foo}";
|
||||
|
||||
// when
|
||||
boolean actual = ComponentUtils.containsExpression(anExpression);
|
||||
|
||||
// then
|
||||
assertTrue(actual);
|
||||
}
|
||||
|
||||
public void testContainsExpressionIsFalse() throws Exception {
|
||||
// given
|
||||
String anExpression = "foo";
|
||||
|
||||
// when
|
||||
boolean actual = ComponentUtils.containsExpression(anExpression);
|
||||
|
||||
// then
|
||||
assertFalse(actual);
|
||||
}
|
||||
}
|
||||
|
||||
class MockConfigurationProvider implements ConfigurationProvider {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.apache.struts2.util;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class URLDecoderUtilTest {
|
||||
|
||||
@Test
|
||||
public void testURLDecodeStringInvalid() {
|
||||
// %n rather than %nn should throw an IAE according to the Javadoc
|
||||
Exception exception = null;
|
||||
try {
|
||||
URLDecoderUtil.decode("%5xxxxx", "ISO-8859-1");
|
||||
} catch (Exception e) {
|
||||
exception = e;
|
||||
}
|
||||
assertTrue(exception instanceof IllegalArgumentException);
|
||||
|
||||
// Edge case trying to trigger ArrayIndexOutOfBoundsException
|
||||
exception = null;
|
||||
try {
|
||||
URLDecoderUtil.decode("%5", "ISO-8859-1");
|
||||
} catch (Exception e) {
|
||||
exception = e;
|
||||
}
|
||||
assertTrue(exception instanceof IllegalArgumentException);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testURLDecodeStringValidIso88591Start() {
|
||||
|
||||
String result = URLDecoderUtil.decode("%41xxxx", "ISO-8859-1");
|
||||
assertEquals("Axxxx", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testURLDecodeStringValidIso88591Middle() {
|
||||
|
||||
String result = URLDecoderUtil.decode("xx%41xx", "ISO-8859-1");
|
||||
assertEquals("xxAxx", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testURLDecodeStringValidIso88591End() {
|
||||
|
||||
String result = URLDecoderUtil.decode("xxxx%41", "ISO-8859-1");
|
||||
assertEquals("xxxxA", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testURLDecodeStringValidUtf8Start() {
|
||||
String result = URLDecoderUtil.decode("%c3%aaxxxx", "UTF-8");
|
||||
assertEquals("\u00eaxxxx", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testURLDecodeStringValidUtf8Middle() {
|
||||
|
||||
String result = URLDecoderUtil.decode("xx%c3%aaxx", "UTF-8");
|
||||
assertEquals("xx\u00eaxx", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testURLDecodeStringValidUtf8End() {
|
||||
|
||||
String result = URLDecoderUtil.decode("xxxx%c3%aa", "UTF-8");
|
||||
assertEquals("xxxx\u00ea", result);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -179,7 +179,8 @@ public class ActionTagTest extends AbstractTagTest {
|
||||
|
||||
assertTrue(stack.getContext().containsKey(ServletActionContext.PAGE_CONTEXT));
|
||||
assertTrue(stack.getContext().get(ServletActionContext.PAGE_CONTEXT)instanceof PageContext);
|
||||
assertNull(result); // result is never executed, hence never set into invocation
|
||||
assertNotNull(result);
|
||||
assertFalse(result.isExecuted());
|
||||
}
|
||||
|
||||
public void testExecuteButResetReturnSameInvocation() throws Exception {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<script type="text/javascript">
|
||||
var fooGroup = new Array(2 + 0);
|
||||
for (i = 0; i < (2 + 0); i++)
|
||||
fooGroup[i] = new Array();
|
||||
fooGroup[i] = [];
|
||||
|
||||
fooGroup[0][0] = new Option("Antwerp", "AN");
|
||||
fooGroup[0][1] = new Option("Gent", "GN");
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<script type="text/javascript">
|
||||
var fooGroup = new Array(2 + 0);
|
||||
for (i = 0; i < (2 + 0); i++)
|
||||
fooGroup[i] = new Array();
|
||||
fooGroup[i] = [];
|
||||
|
||||
fooGroup[0][0] = new Option("Antwerp", "AN");
|
||||
fooGroup[0][1] = new Option("Gent", "GN");
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<script type="text/javascript">
|
||||
var foo_barGroup = new Array(2 + 0);
|
||||
for (i = 0; i < (2 + 0); i++)
|
||||
foo_barGroup[i] = new Array();
|
||||
foo_barGroup[i] = [];
|
||||
|
||||
foo_barGroup[0][0] = new Option("Antwerp", "AN");
|
||||
foo_barGroup[0][1] = new Option("Gent", "GN");
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<script type="text/javascript">
|
||||
var fooGroup = new Array(2 + 0);
|
||||
for (i = 0; i < (2 + 0); i++)
|
||||
fooGroup[i] = new Array();
|
||||
fooGroup[i] = [];
|
||||
|
||||
fooGroup[0][0] = new Option("Antwerp", "AN");
|
||||
fooGroup[0][1] = new Option("Gent", "GN");
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.25</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-cdi-plugin</artifactId>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.25</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-codebehind-plugin</artifactId>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.25</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-config-browser-plugin</artifactId>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.25</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-convention-plugin</artifactId>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<parent>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.25</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.25</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-dwr-plugin</artifactId>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.25</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-embeddedjsp-plugin</artifactId>
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.25</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-gxp-plugin</artifactId>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>2.3.24.1</version>
|
||||
<version>2.3.25</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-jasperreports-plugin</artifactId>
|
||||
|
||||
+7
-1
@@ -65,7 +65,7 @@ import java.util.TimeZone;
|
||||
* format is specified.
|
||||
* <p/>
|
||||
* <!-- END SNIPPET: description -->
|
||||
* <p />
|
||||
* <p>
|
||||
* <b>This result type takes the following parameters:</b>
|
||||
* <p/>
|
||||
* <!-- START SNIPPET: params -->
|
||||
@@ -397,6 +397,12 @@ public class JasperReportsResult extends StrutsResultSupport implements JasperRe
|
||||
String message = "Error producing " + format + " report for uri " + systemId;
|
||||
LOG.error(message, e);
|
||||
throw new ServletException(e.getMessage(), e);
|
||||
} finally {
|
||||
try {
|
||||
conn.close();
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Could not close db connection properly", e);
|
||||
}
|
||||
}
|
||||
|
||||
response.setContentLength(output.length);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user