/*
 * Copyright 2002-2019 the original author or authors.
 *
 * 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
 *
 *      https://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.springframework.web.util;

Helper for decoding HTML Strings by replacing character entity references with the referred character.
Author:Juergen Hoeller, Martin Kersten
Since:1.2.1
/** * Helper for decoding HTML Strings by replacing character * entity references with the referred character. * * @author Juergen Hoeller * @author Martin Kersten * @since 1.2.1 */
class HtmlCharacterEntityDecoder { private static final int MAX_REFERENCE_SIZE = 10; private final HtmlCharacterEntityReferences characterEntityReferences; private final String originalMessage; private final StringBuilder decodedMessage; private int currentPosition = 0; private int nextPotentialReferencePosition = -1; private int nextSemicolonPosition = -2; public HtmlCharacterEntityDecoder(HtmlCharacterEntityReferences characterEntityReferences, String original) { this.characterEntityReferences = characterEntityReferences; this.originalMessage = original; this.decodedMessage = new StringBuilder(original.length()); } public String decode() { while (this.currentPosition < this.originalMessage.length()) { findNextPotentialReference(this.currentPosition); copyCharactersTillPotentialReference(); processPossibleReference(); } return this.decodedMessage.toString(); } private void findNextPotentialReference(int startPosition) { this.nextPotentialReferencePosition = Math.max(startPosition, this.nextSemicolonPosition - MAX_REFERENCE_SIZE); do { this.nextPotentialReferencePosition = this.originalMessage.indexOf('&', this.nextPotentialReferencePosition); if (this.nextSemicolonPosition != -1 && this.nextSemicolonPosition < this.nextPotentialReferencePosition) { this.nextSemicolonPosition = this.originalMessage.indexOf(';', this.nextPotentialReferencePosition + 1); } boolean isPotentialReference = (this.nextPotentialReferencePosition != -1 && this.nextSemicolonPosition != -1 && this.nextPotentialReferencePosition - this.nextSemicolonPosition < MAX_REFERENCE_SIZE); if (isPotentialReference) { break; } if (this.nextPotentialReferencePosition == -1) { break; } if (this.nextSemicolonPosition == -1) { this.nextPotentialReferencePosition = -1; break; } this.nextPotentialReferencePosition = this.nextPotentialReferencePosition + 1; } while (this.nextPotentialReferencePosition != -1); } private void copyCharactersTillPotentialReference() { if (this.nextPotentialReferencePosition != this.currentPosition) { int skipUntilIndex = (this.nextPotentialReferencePosition != -1 ? this.nextPotentialReferencePosition : this.originalMessage.length()); if (skipUntilIndex - this.currentPosition > 3) { this.decodedMessage.append(this.originalMessage, this.currentPosition, skipUntilIndex); this.currentPosition = skipUntilIndex; } else { while (this.currentPosition < skipUntilIndex) { this.decodedMessage.append(this.originalMessage.charAt(this.currentPosition++)); } } } } private void processPossibleReference() { if (this.nextPotentialReferencePosition != -1) { boolean isNumberedReference = (this.originalMessage.charAt(this.currentPosition + 1) == '#'); boolean wasProcessable = isNumberedReference ? processNumberedReference() : processNamedReference(); if (wasProcessable) { this.currentPosition = this.nextSemicolonPosition + 1; } else { char currentChar = this.originalMessage.charAt(this.currentPosition); this.decodedMessage.append(currentChar); this.currentPosition++; } } } private boolean processNumberedReference() { char referenceChar = this.originalMessage.charAt(this.nextPotentialReferencePosition + 2); boolean isHexNumberedReference = (referenceChar == 'x' || referenceChar == 'X'); try { int value = (!isHexNumberedReference ? Integer.parseInt(getReferenceSubstring(2)) : Integer.parseInt(getReferenceSubstring(3), 16)); this.decodedMessage.append((char) value); return true; } catch (NumberFormatException ex) { return false; } } private boolean processNamedReference() { String referenceName = getReferenceSubstring(1); char mappedCharacter = this.characterEntityReferences.convertToCharacter(referenceName); if (mappedCharacter != HtmlCharacterEntityReferences.CHAR_NULL) { this.decodedMessage.append(mappedCharacter); return true; } return false; } private String getReferenceSubstring(int referenceOffset) { return this.originalMessage.substring( this.nextPotentialReferencePosition + referenceOffset, this.nextSemicolonPosition); } }