/**
* Copyright (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "config.h"
#if ENABLE(WML)
#include "WMLVariables.h"
#include "WMLDocument.h"
#include <wtf/ASCIICType.h>
namespace WebCore {
// WML variables specification, excluding the
// pre-WML 1.0 deprecated variable syntax
//
// varname = ("_" | alpha) ("_" | alpha | digit)*
// conv = ":" ("e" ("scape")? | "n" ("oesc")? | "u" ("nesc")?)
// var = ("$" varname) | ("$(" varname (conv)? ")")
static bool isValidFirstVariableNameCharacter(const UChar& character)
{
return WTF::isASCIIAlpha(character)
|| character == '_';
}
static bool isValidVariableNameCharacter(const UChar& character)
{
return WTF::isASCIIAlpha(character)
|| WTF::isASCIIDigit(character)
|| character == '_';
}
static bool isValidVariableEscapingModeString(const String& mode, WMLVariableEscapingMode& escapeMode)
{
if (mode == "e" || mode == "escape")
escapeMode = WMLVariableEscapingEscape;
else if (mode == "u" || mode == "unesc")
escapeMode = WMLVariableEscapingUnescape;
else if (mode == "n" || mode == "noesc")
escapeMode = WMLVariableEscapingNone;
else
return false;
return true;
}
bool isValidVariableName(const String& name)
{
if (name.isEmpty())
return false;
const UChar* characters = name.characters();
if (!isValidFirstVariableNameCharacter(characters[0]))
return false;
unsigned length = name.length();
for (unsigned i = 1; i < length; ++i) {
if (!isValidVariableNameCharacter(characters[i]))
return false;
}
return true;
}
bool containsVariableReference(const String& text, bool& isValid)
{
isValid = true;
bool foundReference = false;
bool finished = false;
int currentPosition = 0;
const UChar* characters = text.characters();
while (!finished) {
// Find beginning of variable reference
int referenceStartPosition = text.find('$', currentPosition);
if (referenceStartPosition == -1) {
finished = true;
break;
}
foundReference = true;
int nameStartPosition = referenceStartPosition + 1;
int nameEndPosition = -1;
if (characters[nameStartPosition] == '(') {
// If the input string contains an open brace, a close brace must exist as well
nameEndPosition = text.find(')', nameStartPosition + 1);
if (nameEndPosition == -1) {
finished = true;
isValid = false;
break;
}
++nameStartPosition;
} else {
int length = text.length();
for (nameEndPosition = nameStartPosition; nameEndPosition < length; ++nameEndPosition) {
if (!isValidVariableNameCharacter(text[nameEndPosition]))
break;
}
--nameEndPosition;
}
if (nameEndPosition < nameStartPosition) {
finished = true;
isValid = false;
break;
}
// Eventually split of conversion string, and check it's syntax afterwards
String conversionString;
String variableName = text.substring(nameStartPosition, nameEndPosition - nameStartPosition);
int conversionStringStart = variableName.find(':');
if (conversionStringStart != -1) {
conversionString = variableName.substring(conversionStringStart + 1, variableName.length() - (conversionStringStart + 1));
variableName = variableName.left(conversionStringStart);
}
isValid = isValidVariableName(variableName);
if (!isValid) {
finished = true;
break;
}
if (!conversionString.isEmpty()) {
isValid = isValidVariableName(conversionString);
if (!isValid) {
finished = true;
break;
}
WMLVariableEscapingMode escapeMode = WMLVariableEscapingNone;
isValid = isValidVariableEscapingModeString(conversionString, escapeMode);
if (!isValid) {
finished = true;
break;
}
}
currentPosition = nameEndPosition;
}
return foundReference;
}
String substituteVariableReferences(const String& reference, Document* document, WMLVariableEscapingMode escapeMode)
{
ASSERT(document);
if (reference.isEmpty())
return reference;
WMLPageState* pageState = wmlPageStateForDocument(document);
if (!pageState)
return reference;
bool isValid = true;
String remainingInput = reference;
String result;
while (!remainingInput.isEmpty()) {
ASSERT(isValid);
int start = remainingInput.find("$");
if (start == -1) {
// Consume all remaining characters, as there's nothing more to substitute
result += remainingInput;
break;
}
// Consume all characters until the variable reference beginning
result += remainingInput.left(start);
remainingInput.remove(0, start);
// Transform adjacent dollar signs into a single dollar sign as string literal
if (remainingInput[1] == '$') {
result += "$";
remainingInput.remove(0, 2);
continue;
}
String variableName;
String conversionMode;
if (remainingInput[1] == '(') {
int referenceEndPosition = remainingInput.find(")");
if (referenceEndPosition == -1) {
isValid = false;
break;
}
variableName = remainingInput.substring(2, referenceEndPosition - 2);
remainingInput.remove(0, referenceEndPosition + 1);
// Determine variable conversion mode string
int pos = variableName.find(':');
if (pos != -1) {
conversionMode = variableName.substring(pos + 1, variableName.length() - (pos + 1));
variableName = variableName.left(pos);
}
} else {
int length = remainingInput.length();
int referenceEndPosition = 1;
for (; referenceEndPosition < length; ++referenceEndPosition) {
if (!isValidVariableNameCharacter(remainingInput[referenceEndPosition]))
break;
}
variableName = remainingInput.substring(1, referenceEndPosition - 1);
remainingInput.remove(0, referenceEndPosition);
}
isValid = isValidVariableName(variableName);
if (!isValid)
break;
ASSERT(!variableName.isEmpty());
String variableValue = pageState->getVariable(variableName);
if (variableValue.isEmpty())
continue;
if (containsVariableReference(variableValue, isValid)) {
if (!isValid)
break;
variableValue = substituteVariableReferences(variableValue, document, escapeMode);
continue;
}
if (!conversionMode.isEmpty()) {
// Override default escape mode, if desired
WMLVariableEscapingMode specifiedEscapeMode = WMLVariableEscapingNone;
if (isValid = isValidVariableEscapingModeString(conversionMode, specifiedEscapeMode))
escapeMode = specifiedEscapeMode;
if (!isValid)
break;
}
switch (escapeMode) {
case WMLVariableEscapingNone:
break;
case WMLVariableEscapingEscape:
variableValue = encodeWithURLEscapeSequences(variableValue);
break;
case WMLVariableEscapingUnescape:
variableValue = decodeURLEscapeSequences(variableValue);
break;
}
result += variableValue;
ASSERT(isValid);
}
if (!isValid) {
reportWMLError(document, WMLErrorInvalidVariableReference);
return reference;
}
return result;
}
}
#endif