Compare commits

...

191 Commits

Author SHA1 Message Date
Lukasz Lenart dbcca3512e [maven-release-plugin] prepare release STRUTS_2_3_35 2018-07-12 09:51:04 +02:00
Lukasz Lenart 6ec2bdc197 Uses proper path to Gitbox 2018-07-12 09:40:21 +02:00
Lukasz Lenart 9b9aa10059 [maven-release-plugin] rollback the release of STRUTS_2_3_35 2018-07-12 09:37:38 +02:00
Lukasz Lenart 6c216de38e [maven-release-plugin] prepare release STRUTS_2_3_35 2018-07-12 09:37:10 +02:00
Lukasz Lenart a89b7c11af Uses the new docs site to build assembly 2018-07-12 09:29:25 +02:00
Yasser Zamani 918182344c fix some sonar issue 2018-06-29 23:25:59 +04:30
Yasser Zamani 52e8d46364 revert a delete mistake from previous commit 2018-06-21 14:11:20 +04:30
Yasser Zamani c59f294fb9 delete redundant code for performance 2018-06-21 13:20:48 +04:30
Lukasz Lenart 86e5bd7f52 Upgrades freemarker 2018-06-21 08:19:44 +02:00
Lukasz Lenart bea6fb599d Ports changes to properly support primitives 2018-06-21 08:19:33 +02:00
Lukasz Lenart 4a3917176d Avoids parsing namespace when using existing namespace 2018-05-08 07:58:34 +02:00
Yasser Zamani a746fc872a revert 4db5cdc 2018-04-26 22:36:30 +04:30
Yasser Zamani 949e069f53 make maven's surefire show stacktrace in console 2018-04-26 21:22:33 +04:30
Yasser Zamani 4db5cdc473 not fail if OS or JDK doesn't support jpeg 2018-04-26 20:44:50 +04:30
Yasser Zamani afc8f20440 fork apt to avoid duplicate same class loading which fails JDK6 2018-04-24 18:27:01 +04:30
Lukasz Lenart 75d7bf481c Uses Java 6 in the pom 2017-11-29 11:22:48 +01:00
Lukasz Lenart d86539ed07 [maven-release-plugin] prepare for next development iteration 2017-09-05 20:37:36 +02:00
Lukasz Lenart f0b3a1d213 [maven-release-plugin] prepare release STRUTS_2_3_34 2017-09-05 20:37:14 +02:00
Lukasz Lenart 2306f5f7fa Uses better class resolver logic 2017-08-26 14:26:20 +02:00
Lukasz Lenart ed7f9dd4cb Drops unused annotation in this Java version 2017-08-24 12:07:33 +02:00
Lukasz Lenart cf53b59647 Fixes post cherry-pick 2017-08-24 11:09:29 +02:00
Lukasz Lenart 6dd6e5cfb7 Allows define allowed classes per action 2017-08-24 09:10:28 +02:00
Lukasz Lenart b9a0c497d3 Fixes post cherry-pick 2017-08-24 09:10:12 +02:00
Lukasz Lenart 2f95b9a130 Updates tests 2017-08-24 08:56:07 +02:00
Lukasz Lenart 103e3b174e Updates flow to handle new API 2017-08-24 08:55:50 +02:00
Lukasz Lenart 2776b34b44 Adds an abstract layer to allow easily handle API change 2017-08-24 08:53:57 +02:00
Lukasz Lenart 287e3bc8cb Adds new methods and marks current as deprecated 2017-08-24 08:49:21 +02:00
Lukasz Lenart 677ccebe83 WW-4836 Upgrades xstream lib to the latest version 2017-08-24 08:46:34 +02:00
Lukasz Lenart f11ac901d3 WW-4844 Upgrades to struts-master 11 2017-08-23 07:22:23 +02:00
Stefaan Dutry 3fddfb6eb5 WW-4834 Improve RegEx used to validate URLs 2017-08-04 13:58:31 +02:00
Lukasz Lenart 744c1f409d Uses better RegEx to improve performance 2017-08-01 12:01:43 +02:00
Lukasz Lenart 54a4dce7bf WW-4832 Upgrades to OGNL 3.0.21 2017-08-01 11:50:33 +02:00
Lukasz Lenart 16eb7fa768 [maven-release-plugin] prepare for next development iteration 2017-07-07 14:47:26 +02:00
Lukasz Lenart 631ce98d17 [maven-release-plugin] prepare release STRUTS_2_3_33 2017-07-07 14:47:12 +02:00
Yasser Zamani 086b637355 Adds constant to control proxy member access 2017-07-07 13:35:10 +04:30
Lukasz Lenart ae56301979 WW-4805 Ports proxy detection to 2.3 2017-06-29 09:41:53 +02:00
Lukasz Lenart 73da12e723 Uses resource message key instead of passing message directly 2017-06-23 09:22:43 +02:00
Yasser Zamani 583da3d5df WW-4805 Blocks ognl access to class members of Spring proxy 2017-06-21 16:40:29 +04:30
Stefaan Dutry 9e8627ca66 WW-4770 Fixes problem with dojo.version 2017-03-24 21:34:31 +01:00
Lukasz Lenart c349dfa5ab WW-4735 Ports more relax domain suffix validation 2017-03-24 11:45:51 +01:00
Lukasz Lenart 4d888a5ab6 [maven-release-plugin] prepare for next development iteration 2017-03-06 12:03:18 +01:00
Lukasz Lenart 1ed29d508f [maven-release-plugin] prepare release STRUTS_2_3_32 2017-03-06 12:03:06 +01:00
Lukasz Lenart 3523064939 Uses default error key if specified key doesn't exist 2017-03-06 11:56:39 +01:00
Lukasz Lenart 54dff362f5 Adds additional example URLs to check if validation works properly 2016-11-25 19:35:15 +01:00
Lukasz Lenart b12864dd38 [maven-release-plugin] prepare for next development iteration 2016-10-05 08:53:34 +02:00
Lukasz Lenart de90290354 [maven-release-plugin] prepare release STRUTS_2_3_31 2016-10-05 08:53:19 +02:00
Lukasz Lenart ba0563183b Fixes issues in config-browse plugin 2016-09-28 11:42:24 +02:00
Lukasz Lenart b348fc552c WW-4674 Uses the same logic as in Dispatcher to control handling exception 2016-09-16 16:37:29 +02:00
Lukasz Lenart 81371e09c5 WW-4685 Adjusts cherry pick to 2.3.x 2016-09-16 16:07:59 +02:00
Lukasz Lenart 431e50e922 WW-4685 Uses dedicated prefixes to access I18N and Struts internals 2016-09-16 16:00:45 +02:00
Lukasz Lenart 156db8e5df WW-4685 Supports evaluating expressions from tiles definitions as a Struts values 2016-09-16 16:00:27 +02:00
Lukasz Lenart 6f5ddca471 WW-4667 Applies params to all instances of interceptor defined in stack 2016-09-06 09:07:02 +02:00
Lukasz Lenart cc0d52f216 WW-4663 Checks if expression is null to avoid NPE 2016-09-02 13:57:15 +02:00
Lukasz Lenart 23e0181328 WW-4669 Returns default action/method instead of throwing exception 2016-08-30 09:20:41 +02:00
Lukasz Lenart 030ffa3354 Disables params evaluation when creating results by convention 2016-08-17 15:36:20 +02:00
Lukasz Lenart 8e67b9144a Adds double check if resource exists 2016-08-17 15:36:20 +02:00
Lukasz Lenart 77017cd677 Allows modify params on coping 2016-08-17 15:36:20 +02:00
cnenning 7d8c3598ef merged fix for WW-4628 (proper url decoding of query-string) 2016-08-03 13:28:00 +02:00
cnenning 83bb64f4c2 WW-4628: new issues were introduced by last change, restoring old behavior and ignoring new tests 2016-08-01 15:42:36 +02:00
cnenning ae2840f183 merged fix for WW-4628 (avoid double encoding of url parameters) 2016-08-01 13:50:23 +02:00
Lukasz Lenart e8b48f8bb4 WW-4601 Hides webconsole when not in devMode 2016-07-08 06:44:12 +02:00
Lukasz Lenart 3243448084 [maven-release-plugin] prepare for next development iteration 2016-07-07 08:32:14 +02:00
Lukasz Lenart 5c61c9a575 [maven-release-plugin] prepare release STRUTS_2_3_30 2016-07-07 08:31:52 +02:00
Lukasz Lenart e40dcaa3f4 Uses the latest OGNL version 2016-06-29 20:24:57 +02:00
Lukasz Lenart ab6acd2382 Cleans up test 2016-06-26 22:04:23 +02:00
Lukasz Lenart bf0a01d390 Upgrades OGNL to latest version 2016-06-26 22:02:32 +02:00
Lukasz Lenart 00c47b7ce6 WW-4648 Upgrades commons-fileupload to the latest version 2016-06-22 10:56:31 +02:00
Lukasz Lenart c84810ff82 Adds test to guard pre-evaulation of name attribute 2016-06-22 10:46:09 +02:00
Lukasz Lenart 2ea59b31b4 Drops commented out test 2016-06-22 10:38:39 +02:00
Lukasz Lenart fae1e0e799 Uses LocaleProvider and default TextProvider instead of evaluating expression 2016-06-22 10:38:12 +02:00
Lukasz Lenart e4ce3ce839 Reverts dropping name pre-evaluation 2016-06-22 08:55:42 +02:00
Lukasz Lenart 730441b7a7 [maven-release-plugin] prepare for next development iteration 2016-06-14 07:11:10 +02:00
Lukasz Lenart bb22c585b5 [maven-release-plugin] prepare release STRUTS_2_3_29 2016-06-14 07:10:57 +02:00
Lukasz Lenart eccc31ebce Improves validation RegEx 2016-06-14 06:53:36 +02:00
Lukasz Lenart 1c92a8b2c9 Reverts previous RegEx 2016-06-03 13:12:02 +02:00
Lukasz Lenart b28b78c062 Introduces new callMethod() function to be used to execute actions 2016-06-03 13:10:58 +02:00
Lukasz Lenart 8d172b8e9b Upgrades to latest OGNL version 2016-06-03 13:08:51 +02:00
Lukasz Lenart 88b885339a Drops pre-evaluation of name 2016-06-02 16:57:44 +02:00
Lukasz Lenart 3f526d630a Drops unneeded method 2016-06-02 16:46:40 +02:00
Lukasz Lenart 34025eafec Allows empty string as a action name 2016-05-23 17:04:38 +02:00
Lukasz Lenart 54e8bf1f75 Disallows dot in action name 2016-05-23 16:02:25 +02:00
Lukasz Lenart 9f2cae2f1e WW-4627 Adds unit test to confirm the issue 2016-05-13 08:00:04 +02:00
Lukasz Lenart 9bcd1bc9f6 WW-4626 Adds support for .cat domain 2016-05-13 07:40:25 +02:00
Lukasz Lenart f052398cd2 WW-4618 Supports custom redirect types which base on ServletRedirectResult 2016-05-13 07:04:50 +02:00
Lukasz Lenart 237432512d Throws away methods that doesn't match pattern 2016-05-12 15:40:04 +02:00
Lukasz Lenart ca25476120 Upgrades commons-collections 2016-05-05 10:46:56 +02:00
Aleksandr Mashchenko 731fde544c WW-4630 - <s:submit> generates a value attribute for type=image which violates W3C (cherry picked from commit c4d2b687be8bdb454a11c5376bb475a8fa7f9c67​) 2016-05-04 23:00:33 +03:00
Lukasz Lenart 76eb8f38a3 Cleans up method name 2016-04-29 08:40:03 +02:00
Lukasz Lenart deefeffd11 Cleans up method name 2016-04-20 08:01:29 +02:00
Lukasz Lenart 98d2692e43 Drops defining location via request 2016-04-20 08:01:22 +02:00
Lukasz Lenart 46517afb14 Adds additional blocked classes 2016-04-20 08:01:12 +02:00
Lukasz Lenart 5190b53673 Uses isSequence flag to block chained expressions 2016-04-20 08:01:02 +02:00
Lukasz Lenart d36f31b3e1 Upgrades OGNL 2016-04-20 08:00:53 +02:00
Lukasz Lenart cbae13a6a8 Reverts 098ee502b4 2016-04-11 16:30:38 +02:00
Lukasz Lenart 865f1f2784 Adds support for deprecated param name 2016-04-08 10:26:21 +02:00
Aleksandr Mashchenko 5ce807aaea WW-4622 - Adds fallback to static config definitions to allow work in restricted envs (cherry picked from 6770b35), getRealPath NPE check; WW-4623, WW-4624 - Adds support for multiple tiles defintions (cherry picked from c12cd61) 2016-04-06 19:36:13 +03:00
Lukasz Lenart 17dab897d9 Uses proper doctype version 2016-04-04 11:21:54 +02:00
Lukasz Lenart 45fa8d0320 Adds missing closing tags 2016-04-04 11:21:37 +02:00
Lukasz Lenart 6fee2eb1a4 [maven-release-plugin] prepare for next development iteration 2016-03-18 20:50:53 +01:00
Lukasz Lenart 0ac8932aa3 [maven-release-plugin] prepare release STRUTS_2_3_28 2016-03-18 20:50:41 +01:00
Lukasz Lenart 6b497ef8f7 WW-4605 Reverts to previous flow when result is created just before executing it 2016-03-18 20:41:06 +01:00
Lukasz Lenart 519c767114 [maven-release-plugin] prepare for next development iteration 2016-03-18 08:54:43 +01:00
Lukasz Lenart 8a59ed02c9 [maven-release-plugin] prepare release STRUTS_2_3_27 2016-03-18 08:54:31 +01:00
Lukasz Lenart f48c30eaa7 Adds proper logic to exclude MemberAccess 2016-03-18 08:45:13 +01:00
Lukasz Lenart 94c4462658 [maven-release-plugin] prepare for next development iteration 2016-03-14 11:39:11 +01:00
Lukasz Lenart 013077abcb [maven-release-plugin] prepare release STRUTS_2_3_26 2016-03-14 11:38:57 +01:00
Lukasz Lenart fbb91d1bb9 Reverts excluded classes 2016-03-14 11:25:00 +01:00
Lukasz Lenart a67ac45258 [maven-release-plugin] prepare for next development iteration 2016-03-11 16:02:55 +01:00
Lukasz Lenart 8ca0f2fc46 [maven-release-plugin] prepare release STRUTS_2_3_25 2016-03-11 16:02:17 +01:00
Lukasz Lenart 37842193ab Narrows possible locale to those from available locales 2016-03-11 11:03:36 +01:00
Lukasz Lenart 609cb0bb97 WW-4533 Reverts the old behaviour of how empty action should be handled 2016-03-10 08:45:12 +01:00
Lukasz Lenart b155532314 Reduces expression evaluation if there is no expression 2016-03-06 10:07:06 +01:00
Aleksandr Mashchenko 9de0cf4c8b radio checked fix vol.2
(cherry picked from commit f7364a50dc)
2016-03-04 20:52:53 +02:00
Aleksandr Mashchenko 5cf4aacd50 radio checked fix
(cherry picked from commit 866b8902cd)
2016-03-04 20:47:00 +02:00
Aleksandr Mashchenko d82289a1be formatting
(cherry picked from commit 820c52dd26)
2016-03-04 20:46:47 +02:00
Aleksandr Mashchenko 6ac2a58da7 WW-4586 Custom StrutsTypeConverter's convertToString not called in radio and checkboxlist tags
(cherry picked from commit 7a0ae0ebae)
2016-03-04 20:46:23 +02:00
Lukasz Lenart c714d2674a Upgrades lib to latest version 2016-03-04 18:38:25 +01:00
Lukasz Lenart ffa6d5cb5b WW-4610 Ports fix from master branch 2016-03-03 18:02:38 +01:00
Lukasz Lenart 9860ef829a WW-4410 Adds dedicated interface to allow inject ServletContext 2016-02-25 21:29:25 +01:00
Lukasz Lenart d028886cd1 Drops wrong arg passed to logger 2016-02-25 20:29:38 +01:00
Lukasz Lenart 1b4868df3e WW-4595 Resolves regression issue but keeping support for custom text provider 2016-02-25 20:12:23 +01:00
Lukasz Lenart 5cfe05075b WW-4563 Reverts checking if value is excluded and uses Internal Security Mechanism 2016-02-25 18:30:26 +01:00
Lukasz Lenart 5efc595011 WW-4560 Reverts checking if value is excluded and uses Internal Security Mechanism 2016-02-25 18:16:32 +01:00
Lukasz Lenart 9c7b833668 WW-4600 Ports solution with PreResultListener from WW-4605 2016-02-11 08:21:59 +01:00
cnenning 87979d2821 WW-4606 made @TilesDefinition work with tiles 2 2016-02-03 14:18:54 +01:00
cnenning 8a9e01727c removed outcommented line 2016-02-03 13:34:47 +01:00
cnenning 5b2b244a66 added missing license header 2016-02-03 13:34:32 +01:00
cnenning 473403ca17 formatted annotations more nicely 2016-02-03 13:34:09 +01:00
cnenning 1bbcd4bf70 renamed test class to stick to naming convention 2016-02-03 13:33:48 +01:00
cnenning 469b25f8e1 WW-4606 fixed javadoc 2016-02-03 13:33:22 +01:00
cnenning 27260685e1 updated javadoc 2016-02-03 13:23:01 +01:00
cnenning bd5e64e598 added tests for StrutsTilesAnnotationProcessor 2016-02-03 13:22:35 +01:00
cnenning 9a8f36dee2 WW-4606 added @TilesDefinition sample to showcase app 2016-02-03 13:21:59 +01:00
cnenning a6345e79c9 WW-4606 cherry-picked @TilesDefinition and related classes 2016-02-03 13:15:44 +01:00
Lukasz Lenart 85373951b7 WW-4600 Adds additional check if session was invalidated 2016-02-03 09:33:50 +01:00
cnenning c44566bd24 fixed jenkins by resolving compiler error in test class (did not appear locally) and adjusting asserts to older version of spring mock objects 2016-01-29 15:12:17 +01:00
cnenning 2a6acabe5d updated comments 2016-01-29 13:54:31 +01:00
cnenning 377c6cdda0 increased visibilty of methods and constants for better extensibility 2016-01-29 13:54:08 +01:00
cnenning 6438396ef8 made methods non static and private, javadoc updates 2016-01-29 13:53:48 +01:00
cnenning 69bc2947c1 WW-4599: backport JSONActionRedirectResult to struts 2.3 2016-01-29 13:53:31 +01:00
Lukasz Lenart 15c79e1398 WW-4587 Adds logic to cache missing bundles 2016-01-26 10:59:47 +01:00
Lukasz Lenart dd36946573 WW-4575 Adds missing constants translation 2016-01-26 10:00:21 +01:00
Lukasz Lenart 18bdc141ea WW-4575 Uses startWith instead of pattern matching to improve overall performance 2016-01-26 09:33:48 +01:00
petersr 2ec3890dc7 WW-4588: Improve the Struts2 Rest plugin to honor Accept header
Conflicts:
	plugins/rest/src/main/java/org/apache/struts2/rest/DefaultContentTypeHandlerManager.java
2016-01-20 16:12:55 +01:00
Lukasz Lenart 10a612c9da Merge branch 'support-2-3' of https://git-wip-us.apache.org/repos/asf/struts into support-2-3
Conflicts:
	plugins/rest/src/test/java/org/apache/struts2/rest/RestActionMapperTest.java
2016-01-20 10:13:03 +01:00
petersr 1d160f4d5f WW-4589 do not overwrite explicit method name
Conflicts:
	plugins/rest/src/test/java/org/apache/struts2/rest/RestActionMapperTest.java
2016-01-20 10:06:46 +01:00
donghui a398793177 make test passed for people not using GMT.
(cherry picked from commit 32ec7a669c)
2016-01-14 20:11:54 +02:00
Aleksandr Mashchenko ff4cdd9670 WW-4585 Struts2 Rest plugin doesn't handle JSESSIONID with DMI 2016-01-14 20:08:21 +02:00
Rene Gielen 7c4bb7e7bd WW-4403 - add JDK 8 profile, disabling JavaDoc lint checker when building with JDK8 2016-01-14 17:43:20 +01:00
Rene Gielen 5421930b49 WW-4507 - clone Tomcat UDecoder and use it for in query string handling 2016-01-14 14:52:03 +01:00
Lukasz Lenart c6750c110c Solves issue with warning abut Custom EL 2016-01-10 16:05:20 +01:00
Lukasz Lenart 997966829a WW-4568 Upgrades the Tiles plugin to Tiles 2 2016-01-10 12:21:23 +01:00
Lukasz Lenart bd1a121b82 Ports blocking eval expressions from commit 61a7ee2961 2016-01-10 12:17:34 +01:00
Lukasz Lenart 6177cf3387 Ports exclude access Class from commit 74e26830d2 2016-01-10 12:17:34 +01:00
Aleksandr Mashchenko b448d79959 Add import for UUID 2016-01-06 19:30:12 +02:00
Aleksandr Mashchenko 9a0a0a16e9 WW-4583 JakartaStreamMultiPartRequest fails on short file name and on empty file input
(cherry picked from commit a4f8d49214)
2016-01-06 19:26:18 +02:00
Victor Sosa bf46145baf A proper way to close the db connection
Conflicts:
	plugins/jasperreports/src/main/java/org/apache/struts2/views/jasperreports/JasperReportsResult.java
2016-01-04 20:21:15 +01:00
Victor Sosa 1158b0adda fix for WW-4573
NPE/ concurrent modification exception

using a CopyOnWriteArrayList. This is to keep Memory consistency on the
ValueStack.
2016-01-04 20:07:04 +01:00
Lukasz Lenart 5ed7b36567 Adds fetching resources from classpath 2015-12-27 15:17:28 +01:00
Lukasz Lenart 2d70ea2f7d Adds missing header with license 2015-12-19 10:23:39 +01:00
Lukasz Lenart 9136521d27 Cleans up and fixes issue based on feedback 2015-12-19 10:15:02 +01:00
Lukasz Lenart 5be1590a60 Uses the new context implementation 2015-12-19 10:14:40 +01:00
Lukasz Lenart f721d5490c Implements ApplicationContext which supports wildcards 2015-12-19 10:13:57 +01:00
Lukasz Lenart db318c2116 Drops duplicated and unused dependencies 2015-12-19 10:13:10 +01:00
Johannes Geppert f295852482 WW-4545 - Setting status code in HttpHeaders will be ignored
(cherry picked from commit 4cd9a74cb8)
2015-12-17 19:45:59 +02:00
Lukasz Lenart 8bbfb461c4 WW-4365 Fixes issue with evaluating doubleHeaderValue and doubleHeaderKey 2015-11-30 08:12:44 +01:00
Lukasz Lenart 38e8f86a75 WW-4569 Adds support for .tech
(cherry picked from commit 7f10be5c37)
2015-11-28 16:38:57 +01:00
Lukasz Lenart 696e566279 Reverts back to XWork logging layer 2015-11-28 15:57:52 +01:00
Lukasz Lenart 27aa7e1e27 Fixes dependency clash
(cherry picked from commit 6622fab25b)

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

Some files were not shown because too many files have changed in this diff Show More