Adds support for the URL_ATTRIBUTE_ENTIRE parse context to HtmlTemplateParser. Review at http://gwt-code-reviews.appspot.com/1396803 Review by: jlabanca@google.com git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9945 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/safehtml/rebind/HtmlTemplateParser.java b/user/src/com/google/gwt/safehtml/rebind/HtmlTemplateParser.java index 316a524..54bcd6a 100644 --- a/user/src/com/google/gwt/safehtml/rebind/HtmlTemplateParser.java +++ b/user/src/com/google/gwt/safehtml/rebind/HtmlTemplateParser.java
@@ -32,7 +32,7 @@ * * <p> * This parser parses templates consisting of HTML markup, with template - * variables of the form {@code "{n}"}. For example, a template might look like, + * variables of the form {@code {n}}. For example, a template might look like, * * <pre> {@code * <span style="{0}"><a href="{1}/{2}">{3}</a></span> @@ -70,10 +70,13 @@ * <dt>{@link HtmlContext.Type#TEXT} * <dd>This context corresponds to basic inner text. In the above example, * parameter #3 would be tagged with this context. - * <dt>{@link HtmlContext.Type#URL_START} + * <dt>{@link HtmlContext.Type#URL_ATTRIBUTE_START} * <dd>This context corresponds to a parameter that appears at the very start of * a URL-valued HTML attribute's value; in the above example this applies to * parameter #1. + * <dt>{@link HtmlContext.Type#URL_ATTRIBUTE_ENTIRE} + * <dd>This context corresponds to a parameter that comprises an entire + * URL-valued attribute, for example in {@code <img src='{0}'/>}. * <dt>{@link HtmlContext.Type#CSS_ATTRIBUTE_START} * <dd>This context corresponds to a parameter that appears at the very * beginning of a {@code style} attribute's value; in the above example this @@ -135,6 +138,18 @@ private int parsePosition; /** + * The character preceding a template parameter, at the time a template + * parameter is being parsed. + */ + private char lookBehind; + + /** + * The character succeeding a template parameter, at the time a template + * parameter is being parsed. + */ + private char lookAhead; + + /** * Creates a {@link HtmlTemplateParser}. * * @param logger the {@link TreeLogger} to log to @@ -163,7 +178,9 @@ // @VisibleForTesting void parseTemplate(String template) throws UnableToCompleteException { this.template = template; - this.parsePosition = 0; + parsePosition = 0; + lookBehind = 0; + lookAhead = 0; Matcher match = TEMPLATE_PARAM_PATTERN.matcher(template); int endOfPreviousMatch = 0; @@ -174,10 +191,16 @@ parseAndAppendTemplateSegment( template.substring(endOfPreviousMatch, match.start())); parsePosition = match.start(); + lookBehind = template.charAt(parsePosition - 1); } int paramIndex = Integer.parseInt(match.group(1)); parsePosition = match.end(); + if (parsePosition < template.length()) { + lookAhead = template.charAt(parsePosition); + } else { + lookAhead = 0; + } parsedTemplate.addParameter( new ParameterChunk(getHtmlContextFromParseState(), paramIndex)); @@ -255,8 +278,29 @@ + getTemplateParsedSoFar()); throw new UnableToCompleteException(); } + if ("meta".equals(tag) && "content".equals(attribute)) { + logger.log(TreeLogger.ERROR, + "Template variables in content attribute of meta tag are not supported: " + + getTemplateParsedSoFar()); + throw new UnableToCompleteException(); + } if (streamHtmlParser.isUrlStart()) { - return new HtmlContext(HtmlContext.Type.URL_START, tag, attribute); + // Note that we have established above that the attribute is quoted. + // Furthermore, we have ruled out template variables in the content + // attribute of a meta tag, which is the only case where isUrlStart() + // is true and the URL does not appear at the very beginning of the + // attribute. + Preconditions.checkState(lookBehind == '"' || lookBehind == '\'', + "At the start of a quoted attribute, lookBehind should be a quote character; at %s", + getTemplateParsedSoFar()); + // If the the character immediately succeeding the template parameter is + // a quote that matches the one that started the attribute, we know + // that the parameter comprises the entire attribute. + if (lookAhead == lookBehind) { + return new HtmlContext(HtmlContext.Type.URL_ATTRIBUTE_ENTIRE, tag, attribute); + } else { + return new HtmlContext(HtmlContext.Type.URL_ATTRIBUTE_START, tag, attribute); + } } else if (streamHtmlParser.inCss()) { if (streamHtmlParser.getValueIndex() == 0) { return new HtmlContext(HtmlContext.Type.CSS_ATTRIBUTE_START, tag, attribute);
diff --git a/user/src/com/google/gwt/safehtml/rebind/ParsedHtmlTemplate.java b/user/src/com/google/gwt/safehtml/rebind/ParsedHtmlTemplate.java index dd44da7..7567c16 100644 --- a/user/src/com/google/gwt/safehtml/rebind/ParsedHtmlTemplate.java +++ b/user/src/com/google/gwt/safehtml/rebind/ParsedHtmlTemplate.java
@@ -56,7 +56,11 @@ /** * At the very start of a URL-valued attribute. */ - URL_START, + URL_ATTRIBUTE_START, + /** + * A template parameter that comprises an entire URL-valued attribute. + */ + URL_ATTRIBUTE_ENTIRE, /** * CSS (style) context. */
diff --git a/user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesImplMethodCreator.java b/user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesImplMethodCreator.java index 479dac5..871ef53 100644 --- a/user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesImplMethodCreator.java +++ b/user/src/com/google/gwt/safehtml/rebind/SafeHtmlTemplatesImplMethodCreator.java
@@ -144,7 +144,8 @@ expression = "String.valueOf(" + expression + ")"; } - if ((htmlContext.getType() == HtmlContext.Type.URL_START)) { + if ((htmlContext.getType() == HtmlContext.Type.URL_ATTRIBUTE_START) || + (htmlContext.getType() == HtmlContext.Type.URL_ATTRIBUTE_ENTIRE)) { expression = URI_UTILS_FQCN + ".sanitizeUri(" + expression + ")"; } @@ -253,7 +254,8 @@ emitAttributeContextParameterExpression(logger, htmlContext, formalParameterName, parameterType); break; - case URL_START: + case URL_ATTRIBUTE_START: + case URL_ATTRIBUTE_ENTIRE: case ATTRIBUTE_VALUE: emitAttributeContextParameterExpression(logger, htmlContext, formalParameterName, parameterType);
diff --git a/user/test/com/google/gwt/safehtml/rebind/HtmlTemplateParserTest.java b/user/test/com/google/gwt/safehtml/rebind/HtmlTemplateParserTest.java index 8ef9f53..7e6e058 100644 --- a/user/test/com/google/gwt/safehtml/rebind/HtmlTemplateParserTest.java +++ b/user/test/com/google/gwt/safehtml/rebind/HtmlTemplateParserTest.java
@@ -111,15 +111,20 @@ + "L(</span>)]"), "<span>foo&bar<b>{1}</b><![CDATA[foo-cdata <baz>]]>{0}</span>"); - // Check correct handling of ATTRIBUTE_VALUE vs URL_START context. - assertParseTemplateResult(("[L(<a href=\"), P((URL_START,a,href),0), " + // Check correct handling of ATTRIBUTE_VALUE vs URL_ATTRIBUTE_START and + // URL_ATTRIBUTE_ENTIRE context. + assertParseTemplateResult(("[L(<a href=\"), P((URL_ATTRIBUTE_ENTIRE,a,href),0), " + "L(\">), P((TEXT,null,null),1), L(</a>)]"), "<a href=\"{0}\">{1}</a>"); + // Single quotes work too: + assertParseTemplateResult(("[L(<a href='), P((URL_ATTRIBUTE_ENTIRE,a,href),0), " + + "L('>), P((TEXT,null,null),1), L(</a>)]"), + "<a href='{0}'>{1}</a>"); assertParseTemplateResult( ("[L(<a href=\"http://), P((ATTRIBUTE_VALUE,a,href),0), " + "L(\">), P((TEXT,null,null),1), L(</a>)]"), "<a href=\"http://{0}\">{1}</a>"); - assertParseTemplateResult(("[L(<a href=\"), P((URL_START,a,href),0), " + assertParseTemplateResult(("[L(<a href=\"), P((URL_ATTRIBUTE_START,a,href),0), " + "L(/), P((ATTRIBUTE_VALUE,a,href),1), " + "L(\">), P((TEXT,null,null),2), L(</a>)]"), "<a href=\"{0}/{1}\">{2}</a>"); @@ -173,6 +178,12 @@ "<div class=\"{0}\" foo=bar>{1}<a"); assertParsingTemplateEndingInNonInnerHtmlContextFails( "<div class=\"{0}\" foo=bar>{1}<a href="); + + // Check that parseTemplate doesn't walk off the end of the string when + // extracting lookAhead: We should be getting an error that the template + // ends in non-inner-HTML context, and not an IndexOutOfBoundsException. + assertParsingTemplateEndingInNonInnerHtmlContextFails("<a href='{0}'"); + assertParsingTemplateEndingInNonInnerHtmlContextFails("<a href='{0}"); } private void assertTemplateVariableInUnquotedAttributeFails( @@ -247,6 +258,11 @@ "<div style=\"{0}\" foo{1}=\"{2}\">", "<div style=\"{0}\" foo{1}"); } + public void testParseTemplate_templateVariableInMetaContentFails() { + assertParseFails("Template variables in content attribute of meta tag are not supported: ", + "<meta http-equiv=\"{0}\" content=\"{1}\">", "<meta http-equiv=\"{0}\" content=\"{1}"); + } + private void assertParseFails( String expectedError, final String template, final String failAtPrefix) { UnitTestTreeLogger.Builder loggerBuilder = new UnitTestTreeLogger.Builder();
diff --git a/user/test/com/google/gwt/safehtml/rebind/ParsedHtmlTemplateTest.java b/user/test/com/google/gwt/safehtml/rebind/ParsedHtmlTemplateTest.java index d5d1334..dd8d4ce 100644 --- a/user/test/com/google/gwt/safehtml/rebind/ParsedHtmlTemplateTest.java +++ b/user/test/com/google/gwt/safehtml/rebind/ParsedHtmlTemplateTest.java
@@ -49,7 +49,7 @@ HtmlContext.Type.TEXT), 0)); List<TemplateChunk> chunks = parsed.getChunks(); - + ParameterChunk chunk = (ParameterChunk) chunks.get(0); assertEquals(TemplateChunk.Kind.PARAMETER, chunk.getKind()); assertEquals(HtmlContext.Type.TEXT, chunk.getContext().getType()); @@ -108,8 +108,8 @@ /** * Tests that calling addParameter(), addLiteral(), addLiteral(), * addParameter() in sequence results in the expected ParsedHtmlTemplate. - * - * <p>In particular, two calls to addLiteral() in sequence should result in + * + * <p>In particular, two calls to addLiteral() in sequence should result in * only a single LiteralChunk. */ public void testAddParameterAddLiteralSequence() { @@ -120,7 +120,7 @@ parsed.addLiteral("<a"); parsed.addLiteral(" href=\""); parsed.addParameter(new ParameterChunk(new HtmlContext( - HtmlContext.Type.URL_START, "a", "href"), 1)); + HtmlContext.Type.URL_ATTRIBUTE_START, "a", "href"), 1)); List<TemplateChunk> chunks = parsed.getChunks(); assertEquals(3, chunks.size()); @@ -145,15 +145,15 @@ paramChunk = (ParameterChunk) it.next(); assertEquals(TemplateChunk.Kind.PARAMETER, paramChunk.getKind()); assertEquals( - HtmlContext.Type.URL_START, paramChunk.getContext().getType()); + HtmlContext.Type.URL_ATTRIBUTE_START, paramChunk.getContext().getType()); assertEquals("a", paramChunk.getContext().getTag()); assertEquals("href", paramChunk.getContext().getAttribute()); assertEquals(1, paramChunk.getParameterIndex()); - assertEquals("P((URL_START,a,href),1)", paramChunk.toString()); + assertEquals("P((URL_ATTRIBUTE_START,a,href),1)", paramChunk.toString()); assertEquals( "[P((TEXT,null,null),0), L(<a href=\"), " - + "P((URL_START,a,href),1)]", + + "P((URL_ATTRIBUTE_START,a,href),1)]", parsed.toString()); } }