PageRenderTime 45ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/src/main/java/org/owasp/esapi/reference/DefaultEncoder.java

http://owasp-esapi-java.googlecode.com/
Java | 448 lines | 306 code | 43 blank | 99 comment | 78 complexity | 2c33c67acbc8ff23c6f1851d59763033 MD5 | raw file
Possible License(s): BSD-3-Clause, CC-BY-SA-3.0
  1. /**
  2. * OWASP Enterprise Security API (ESAPI)
  3. *
  4. * This file is part of the Open Web Application Security Project (OWASP)
  5. * Enterprise Security API (ESAPI) project. For details, please see
  6. * <a href="http://www.owasp.org/index.php/ESAPI">http://www.owasp.org/index.php/ESAPI</a>.
  7. *
  8. * Copyright (c) 2007 - The OWASP Foundation
  9. *
  10. * The ESAPI is published by OWASP under the BSD license. You should read and accept the
  11. * LICENSE before you use, modify, and/or redistribute this software.
  12. *
  13. * @author Jeff Williams <a href="http://www.aspectsecurity.com">Aspect Security</a>
  14. * @created 2007
  15. */
  16. package org.owasp.esapi.reference;
  17. import java.io.IOException;
  18. import java.io.UnsupportedEncodingException;
  19. import java.net.URLDecoder;
  20. import java.net.URLEncoder;
  21. import java.util.ArrayList;
  22. import java.util.Iterator;
  23. import java.util.List;
  24. import org.owasp.esapi.ESAPI;
  25. import org.owasp.esapi.Encoder;
  26. import org.owasp.esapi.Logger;
  27. import org.owasp.esapi.codecs.Base64;
  28. import org.owasp.esapi.codecs.CSSCodec;
  29. import org.owasp.esapi.codecs.Codec;
  30. import org.owasp.esapi.codecs.HTMLEntityCodec;
  31. import org.owasp.esapi.codecs.JavaScriptCodec;
  32. import org.owasp.esapi.codecs.PercentCodec;
  33. import org.owasp.esapi.codecs.VBScriptCodec;
  34. import org.owasp.esapi.codecs.XMLEntityCodec;
  35. import org.owasp.esapi.errors.EncodingException;
  36. import org.owasp.esapi.errors.IntrusionException;
  37. /**
  38. * Reference implementation of the Encoder interface. This implementation takes
  39. * a whitelist approach to encoding, meaning that everything not specifically identified in a
  40. * list of "immune" characters is encoded.
  41. *
  42. * @author Jeff Williams (jeff.williams .at. aspectsecurity.com) <a
  43. * href="http://www.aspectsecurity.com">Aspect Security</a>
  44. * @since June 1, 2007
  45. * @see org.owasp.esapi.Encoder
  46. */
  47. public class DefaultEncoder implements Encoder {
  48. private static volatile Encoder singletonInstance;
  49. public static Encoder getInstance() {
  50. if ( singletonInstance == null ) {
  51. synchronized ( DefaultEncoder.class ) {
  52. if ( singletonInstance == null ) {
  53. singletonInstance = new DefaultEncoder();
  54. }
  55. }
  56. }
  57. return singletonInstance;
  58. }
  59. // Codecs
  60. private List codecs = new ArrayList();
  61. private HTMLEntityCodec htmlCodec = new HTMLEntityCodec();
  62. private XMLEntityCodec xmlCodec = new XMLEntityCodec();
  63. private PercentCodec percentCodec = new PercentCodec();
  64. private JavaScriptCodec javaScriptCodec = new JavaScriptCodec();
  65. private VBScriptCodec vbScriptCodec = new VBScriptCodec();
  66. private CSSCodec cssCodec = new CSSCodec();
  67. private final Logger logger = ESAPI.getLogger("Encoder");
  68. /**
  69. * Character sets that define characters (in addition to alphanumerics) that are
  70. * immune from encoding in various formats
  71. */
  72. private final static char[] IMMUNE_HTML = { ',', '.', '-', '_', ' ' };
  73. private final static char[] IMMUNE_HTMLATTR = { ',', '.', '-', '_' };
  74. private final static char[] IMMUNE_CSS = {};
  75. private final static char[] IMMUNE_JAVASCRIPT = { ',', '.', '_' };
  76. private final static char[] IMMUNE_VBSCRIPT = { ',', '.', '_' };
  77. private final static char[] IMMUNE_XML = { ',', '.', '-', '_', ' ' };
  78. private final static char[] IMMUNE_SQL = { ' ' };
  79. private final static char[] IMMUNE_OS = { '-' };
  80. private final static char[] IMMUNE_XMLATTR = { ',', '.', '-', '_' };
  81. private final static char[] IMMUNE_XPATH = { ',', '.', '-', '_', ' ' };
  82. /**
  83. * Instantiates a new DefaultEncoder
  84. */
  85. private DefaultEncoder() {
  86. codecs.add( htmlCodec );
  87. codecs.add( percentCodec );
  88. codecs.add( javaScriptCodec );
  89. }
  90. public DefaultEncoder( List<String> codecNames ) {
  91. for ( String clazz : codecNames ) {
  92. try {
  93. if ( clazz.indexOf( '.' ) == -1 ) clazz = "org.owasp.esapi.codecs." + clazz;
  94. codecs.add( Class.forName( clazz ).newInstance() );
  95. } catch ( Exception e ) {
  96. logger.warning( Logger.EVENT_FAILURE, "Codec " + clazz + " listed in ESAPI.properties not on classpath" );
  97. }
  98. }
  99. }
  100. /**
  101. * {@inheritDoc}
  102. */
  103. public String canonicalize( String input ) {
  104. if ( input == null ) {
  105. return null;
  106. }
  107. // Issue 231 - These are reverse boolean logic in the Encoder interface, so we need to invert these values - CS
  108. return canonicalize(input,
  109. !ESAPI.securityConfiguration().getAllowMultipleEncoding(),
  110. !ESAPI.securityConfiguration().getAllowMixedEncoding() );
  111. }
  112. /**
  113. * {@inheritDoc}
  114. */
  115. public String canonicalize( String input, boolean strict) {
  116. return canonicalize(input, strict, strict);
  117. }
  118. /**
  119. * {@inheritDoc}
  120. */
  121. public String canonicalize( String input, boolean restrictMultiple, boolean restrictMixed ) {
  122. if ( input == null ) {
  123. return null;
  124. }
  125. String working = input;
  126. Codec codecFound = null;
  127. int mixedCount = 1;
  128. int foundCount = 0;
  129. boolean clean = false;
  130. while( !clean ) {
  131. clean = true;
  132. // try each codec and keep track of which ones work
  133. Iterator i = codecs.iterator();
  134. while ( i.hasNext() ) {
  135. Codec codec = (Codec)i.next();
  136. String old = working;
  137. working = codec.decode( working );
  138. if ( !old.equals( working ) ) {
  139. if ( codecFound != null && codecFound != codec ) {
  140. mixedCount++;
  141. }
  142. codecFound = codec;
  143. if ( clean ) {
  144. foundCount++;
  145. }
  146. clean = false;
  147. }
  148. }
  149. }
  150. // do strict tests and handle if any mixed, multiple, nested encoding were found
  151. if ( foundCount >= 2 && mixedCount > 1 ) {
  152. if ( restrictMultiple || restrictMixed ) {
  153. throw new IntrusionException( "Input validation failure", "Multiple ("+ foundCount +"x) and mixed encoding ("+ mixedCount +"x) detected in " + input );
  154. } else {
  155. logger.warning( Logger.SECURITY_FAILURE, "Multiple ("+ foundCount +"x) and mixed encoding ("+ mixedCount +"x) detected in " + input );
  156. }
  157. }
  158. else if ( foundCount >= 2 ) {
  159. if ( restrictMultiple ) {
  160. throw new IntrusionException( "Input validation failure", "Multiple ("+ foundCount +"x) encoding detected in " + input );
  161. } else {
  162. logger.warning( Logger.SECURITY_FAILURE, "Multiple ("+ foundCount +"x) encoding detected in " + input );
  163. }
  164. }
  165. else if ( mixedCount > 1 ) {
  166. if ( restrictMixed ) {
  167. throw new IntrusionException( "Input validation failure", "Mixed encoding ("+ mixedCount +"x) detected in " + input );
  168. } else {
  169. logger.warning( Logger.SECURITY_FAILURE, "Mixed encoding ("+ mixedCount +"x) detected in " + input );
  170. }
  171. }
  172. return working;
  173. }
  174. /**
  175. * {@inheritDoc}
  176. */
  177. public String encodeForHTML(String input) {
  178. if( input == null ) {
  179. return null;
  180. }
  181. return htmlCodec.encode( IMMUNE_HTML, input);
  182. }
  183. /**
  184. * {@inheritDoc}
  185. */
  186. public String decodeForHTML(String input) {
  187. if( input == null ) {
  188. return null;
  189. }
  190. return htmlCodec.decode( input);
  191. }
  192. /**
  193. * {@inheritDoc}
  194. */
  195. public String encodeForHTMLAttribute(String input) {
  196. if( input == null ) {
  197. return null;
  198. }
  199. return htmlCodec.encode( IMMUNE_HTMLATTR, input);
  200. }
  201. /**
  202. * {@inheritDoc}
  203. */
  204. public String encodeForCSS(String input) {
  205. if( input == null ) {
  206. return null;
  207. }
  208. return cssCodec.encode( IMMUNE_CSS, input);
  209. }
  210. /**
  211. * {@inheritDoc}
  212. */
  213. public String encodeForJavaScript(String input) {
  214. if( input == null ) {
  215. return null;
  216. }
  217. return javaScriptCodec.encode(IMMUNE_JAVASCRIPT, input);
  218. }
  219. /**
  220. * {@inheritDoc}
  221. */
  222. public String encodeForVBScript(String input) {
  223. if( input == null ) {
  224. return null;
  225. }
  226. return vbScriptCodec.encode(IMMUNE_VBSCRIPT, input);
  227. }
  228. /**
  229. * {@inheritDoc}
  230. */
  231. public String encodeForSQL(Codec codec, String input) {
  232. if( input == null ) {
  233. return null;
  234. }
  235. return codec.encode(IMMUNE_SQL, input);
  236. }
  237. /**
  238. * {@inheritDoc}
  239. */
  240. public String encodeForOS(Codec codec, String input) {
  241. if( input == null ) {
  242. return null;
  243. }
  244. return codec.encode( IMMUNE_OS, input);
  245. }
  246. /**
  247. * {@inheritDoc}
  248. */
  249. public String encodeForLDAP(String input) {
  250. if( input == null ) {
  251. return null;
  252. }
  253. // TODO: replace with LDAP codec
  254. StringBuilder sb = new StringBuilder();
  255. for (int i = 0; i < input.length(); i++) {
  256. char c = input.charAt(i);
  257. switch (c) {
  258. case '\\':
  259. sb.append("\\5c");
  260. break;
  261. case '*':
  262. sb.append("\\2a");
  263. break;
  264. case '(':
  265. sb.append("\\28");
  266. break;
  267. case ')':
  268. sb.append("\\29");
  269. break;
  270. case '\0':
  271. sb.append("\\00");
  272. break;
  273. default:
  274. sb.append(c);
  275. }
  276. }
  277. return sb.toString();
  278. }
  279. /**
  280. * {@inheritDoc}
  281. */
  282. public String encodeForDN(String input) {
  283. if( input == null ) {
  284. return null;
  285. }
  286. // TODO: replace with DN codec
  287. StringBuilder sb = new StringBuilder();
  288. if ((input.length() > 0) && ((input.charAt(0) == ' ') || (input.charAt(0) == '#'))) {
  289. sb.append('\\'); // add the leading backslash if needed
  290. }
  291. for (int i = 0; i < input.length(); i++) {
  292. char c = input.charAt(i);
  293. switch (c) {
  294. case '\\':
  295. sb.append("\\\\");
  296. break;
  297. case ',':
  298. sb.append("\\,");
  299. break;
  300. case '+':
  301. sb.append("\\+");
  302. break;
  303. case '"':
  304. sb.append("\\\"");
  305. break;
  306. case '<':
  307. sb.append("\\<");
  308. break;
  309. case '>':
  310. sb.append("\\>");
  311. break;
  312. case ';':
  313. sb.append("\\;");
  314. break;
  315. default:
  316. sb.append(c);
  317. }
  318. }
  319. // add the trailing backslash if needed
  320. if ((input.length() > 1) && (input.charAt(input.length() - 1) == ' ')) {
  321. sb.insert(sb.length() - 1, '\\');
  322. }
  323. return sb.toString();
  324. }
  325. /**
  326. * {@inheritDoc}
  327. */
  328. public String encodeForXPath(String input) {
  329. if( input == null ) {
  330. return null;
  331. }
  332. return htmlCodec.encode( IMMUNE_XPATH, input);
  333. }
  334. /**
  335. * {@inheritDoc}
  336. */
  337. public String encodeForXML(String input) {
  338. if( input == null ) {
  339. return null;
  340. }
  341. return xmlCodec.encode( IMMUNE_XML, input);
  342. }
  343. /**
  344. * {@inheritDoc}
  345. */
  346. public String encodeForXMLAttribute(String input) {
  347. if( input == null ) {
  348. return null;
  349. }
  350. return xmlCodec.encode( IMMUNE_XMLATTR, input);
  351. }
  352. /**
  353. * {@inheritDoc}
  354. */
  355. public String encodeForURL(String input) throws EncodingException {
  356. if ( input == null ) {
  357. return null;
  358. }
  359. try {
  360. return URLEncoder.encode(input, ESAPI.securityConfiguration().getCharacterEncoding());
  361. } catch (UnsupportedEncodingException ex) {
  362. throw new EncodingException("Encoding failure", "Character encoding not supported", ex);
  363. } catch (Exception e) {
  364. throw new EncodingException("Encoding failure", "Problem URL encoding input", e);
  365. }
  366. }
  367. /**
  368. * {@inheritDoc}
  369. */
  370. public String decodeFromURL(String input) throws EncodingException {
  371. if ( input == null ) {
  372. return null;
  373. }
  374. String canonical = canonicalize(input);
  375. try {
  376. return URLDecoder.decode(canonical, ESAPI.securityConfiguration().getCharacterEncoding());
  377. } catch (UnsupportedEncodingException ex) {
  378. throw new EncodingException("Decoding failed", "Character encoding not supported", ex);
  379. } catch (Exception e) {
  380. throw new EncodingException("Decoding failed", "Problem URL decoding input", e);
  381. }
  382. }
  383. /**
  384. * {@inheritDoc}
  385. */
  386. public String encodeForBase64(byte[] input, boolean wrap) {
  387. if ( input == null ) {
  388. return null;
  389. }
  390. int options = 0;
  391. if ( !wrap ) {
  392. options |= Base64.DONT_BREAK_LINES;
  393. }
  394. return Base64.encodeBytes(input, options);
  395. }
  396. /**
  397. * {@inheritDoc}
  398. */
  399. public byte[] decodeFromBase64(String input) throws IOException {
  400. if ( input == null ) {
  401. return null;
  402. }
  403. return Base64.decode( input );
  404. }
  405. }