View | Details | Raw Unified | Return to bug 49565
Collapse All | Expand All

(-)impl/src/test/java/org/apache/taglibs/standard/functions/TestFunctions.java (+8 lines)
Lines 111-114 Link Here
111
            Assert.assertEquals(Resources.getMessage("PARAM_BAD_VALUE"), e.getMessage());
111
            Assert.assertEquals(Resources.getMessage("PARAM_BAD_VALUE"), e.getMessage());
112
        }
112
        }
113
    }
113
    }
114
115
    @Test
116
    public void testEscapeXML() {
117
        Assert.assertEquals("Hello", escapeXml("Hello"));
118
        Assert.assertEquals("&lt;Hello msg=&#034;world&#034;/&gt;", escapeXml("<Hello msg=\"world\"/>"));
119
        Assert.assertEquals("&lt;Hello msg=&#039;world&#039;/&gt;", escapeXml("<Hello msg='world'/>"));
120
        Assert.assertEquals("cats &amp; dogs", escapeXml("cats & dogs"));
121
    }
114
}
122
}
(-)impl/src/test/java/org/apache/taglibs/standard/util/EscapeXMLTest.java (+220 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 * 
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 * 
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package org.apache.taglibs.standard.util;
18
19
import org.junit.Assert;
20
import org.junit.Before;
21
import org.junit.Test;
22
23
import javax.servlet.jsp.JspWriter;
24
25
import java.io.IOException;
26
import java.io.Reader;
27
import java.io.StringReader;
28
29
import static org.apache.taglibs.standard.util.EscapeXML.*;
30
import static org.easymock.EasyMock.createMock;
31
import static org.easymock.EasyMock.eq;
32
import static org.easymock.EasyMock.expect;
33
import static org.easymock.EasyMock.isA;
34
import static org.easymock.EasyMock.replay;
35
import static org.easymock.EasyMock.verify;
36
37
/**
38
 */
39
public class EscapeXMLTest {
40
    private JspWriter writer;
41
42
    @Before
43
    public void setup() {
44
        writer = createMock(JspWriter.class);
45
    }
46
47
    @Test
48
    public void testEscapeString() {
49
        Assert.assertEquals("Hello", escape("Hello"));
50
        Assert.assertEquals("&lt;Hello msg=&#034;world&#034;/&gt;", escape("<Hello msg=\"world\"/>"));
51
        Assert.assertEquals("&lt;Hello msg=&#039;world&#039;/&gt;", escape("<Hello msg='world'/>"));
52
        Assert.assertEquals("cats &amp; dogs", escape("cats & dogs"));
53
    }
54
55
    @Test
56
    public void testEmitInteger() throws IOException {
57
        writer.write("1234");
58
        replay(writer);
59
        emit(1234, false, writer);
60
        verify(writer);
61
    }
62
63
    @Test
64
    public void testEmitNull() throws IOException {
65
        writer.write("null");
66
        replay(writer);
67
        emit((Object) null, false, writer);
68
        verify(writer);
69
    }
70
71
    @Test
72
    public void testEmitStringEscaped() throws IOException {
73
        String s = "cats & dogs";
74
        writer.write(s, 0, 5);
75
        writer.write("&amp;");
76
        writer.write(s, 6, 5);
77
        replay(writer);
78
        emit(s, true, writer);
79
        verify(writer);
80
    }
81
82
    @Test
83
    public void testEmitStringUnescaped() throws IOException {
84
        String s = "cats & dogs";
85
        writer.write(s);
86
        replay(writer);
87
        emit(s, false, writer);
88
        verify(writer);
89
    }
90
91
    @Test
92
    public void testEmitEntireString() throws IOException {
93
        writer.write("Hello World", 0, 11);
94
        replay(writer);
95
        emit("Hello World", writer);
96
        verify(writer);
97
    }
98
99
    @Test
100
    public void testEmitEscapedStringMiddle() throws IOException {
101
        String s = "cats & dogs";
102
        writer.write(s, 0, 5);
103
        writer.write("&amp;");
104
        writer.write(s, 6, 5);
105
        replay(writer);
106
        emit(s, writer);
107
        verify(writer);
108
    }
109
110
    @Test
111
    public void testEmitEscapedStringBeginning() throws IOException {
112
        String s = "& dogs";
113
        writer.write("&amp;");
114
        writer.write(s, 1, 5);
115
        replay(writer);
116
        emit(s, writer);
117
        verify(writer);
118
    }
119
120
    @Test
121
    public void testEmitEscapedStringEnd() throws IOException {
122
        String s = "cats &";
123
        writer.write(s, 0, 5);
124
        writer.write("&amp;");
125
        replay(writer);
126
        emit(s, writer);
127
        verify(writer);
128
    }
129
130
    @Test
131
    public void testEmitEscapedStringAdjacent() throws IOException {
132
        String s = "'cats'&\"dogs\"";
133
        writer.write("&#039;");
134
        writer.write(s, 1, 4);
135
        writer.write("&#039;");
136
        writer.write("&amp;");
137
        writer.write("&#034;");
138
        writer.write(s, 8, 4);
139
        writer.write("&#034;");
140
        replay(writer);
141
        emit(s, writer);
142
        verify(writer);
143
    }
144
145
    @Test
146
    public void testEmitEmptyString() throws IOException {
147
        replay(writer);
148
        emit("", writer);
149
        verify(writer);
150
    }
151
152
    @Test
153
    public void testEmitChars() throws IOException {
154
        char[] chars = "Hello World".toCharArray();
155
        writer.write(chars, 2, 5);
156
        replay(writer);
157
        emit(chars, 2, 5, writer);
158
        verify(writer);
159
    }
160
161
    @Test
162
    public void testEmitEscapedChars() throws IOException {
163
        char[] chars = "'cats'&\"dogs\"".toCharArray();
164
        writer.write("&#039;");
165
        writer.write(chars, 1, 4);
166
        writer.write("&#039;");
167
        writer.write("&amp;");
168
        writer.write("&#034;");
169
        writer.write(chars, 8, 4);
170
        writer.write("&#034;");
171
        replay(writer);
172
        emit(chars, 0, chars.length, writer);
173
        verify(writer);
174
    }
175
176
    @Test
177
    public void testEmitReaderUnescaped() throws IOException {
178
        Reader reader = new StringReader("'cats'&\"dogs\"");
179
        expect(writer.getBufferSize()).andStubReturn(0);
180
        writer.write(isA(char[].class), eq(0), eq(13));
181
        replay(writer);
182
        emit(reader, false, writer);
183
        verify(writer);
184
    }
185
186
    @Test
187
    public void testEmitReaderUnbuffered() throws IOException {
188
        Reader reader = new StringReader("'cats'&\"dogs\"");
189
        expect(writer.getBufferSize()).andStubReturn(0);
190
        writer.write("&#039;");
191
        writer.write(isA(char[].class), eq(1), eq(4));
192
        writer.write("&#039;");
193
        writer.write("&amp;");
194
        writer.write("&#034;");
195
        writer.write(isA(char[].class), eq(8), eq(4));
196
        writer.write("&#034;");
197
        replay(writer);
198
        emit(reader, true, writer);
199
        verify(writer);
200
    }
201
202
    @Test
203
    public void testEmitReaderBufferWrap() throws IOException {
204
        Reader reader = new StringReader("'cats'&\"dogs\"");
205
        expect(writer.getBufferSize()).andStubReturn(2);
206
        writer.write("&#039;");
207
        writer.write(isA(char[].class), eq(1), eq(1)); // 'c'
208
        writer.write(isA(char[].class), eq(0), eq(2)); // 'at'
209
        writer.write(isA(char[].class), eq(0), eq(1)); // 's'
210
        writer.write("&#039;");
211
        writer.write("&amp;");
212
        writer.write("&#034;");
213
        writer.write(isA(char[].class), eq(0), eq(2)); // 'do'
214
        writer.write(isA(char[].class), eq(0), eq(2)); // 'gs'
215
        writer.write("&#034;");
216
        replay(writer);
217
        emit(reader, true, writer);
218
        verify(writer);
219
    }
220
}
(-)impl/src/main/java/org/apache/taglibs/standard/functions/Functions.java (-2 / +2 lines)
Lines 27-33 Link Here
27
import javax.servlet.jsp.JspTagException;
27
import javax.servlet.jsp.JspTagException;
28
28
29
import org.apache.taglibs.standard.resources.Resources;
29
import org.apache.taglibs.standard.resources.Resources;
30
import org.apache.taglibs.standard.tag.common.core.Util;
30
import org.apache.taglibs.standard.util.EscapeXML;
31
31
32
/**
32
/**
33
 * Static functions that extend the Expression Language with standardized behaviour
33
 * Static functions that extend the Expression Language with standardized behaviour
Lines 207-213 Link Here
207
     * @return escaped string
207
     * @return escaped string
208
     */
208
     */
209
    public static String escapeXml(String input) {
209
    public static String escapeXml(String input) {
210
        return Util.escapeXml(input);
210
        return EscapeXML.escape(input);
211
    }
211
    }
212
212
213
    /**
213
    /**
(-)impl/src/main/java/org/apache/taglibs/standard/tag/common/xml/ExprSupport.java (-7 / +10 lines)
Lines 17-29 Link Here
17
17
18
package org.apache.taglibs.standard.tag.common.xml;
18
package org.apache.taglibs.standard.tag.common.xml;
19
19
20
import org.apache.taglibs.standard.util.EscapeXML;
21
20
import javax.servlet.jsp.JspException;
22
import javax.servlet.jsp.JspException;
21
import javax.servlet.jsp.JspTagException;
23
import javax.servlet.jsp.JspTagException;
22
import javax.servlet.jsp.tagext.TagSupport;
24
import javax.servlet.jsp.tagext.TagSupport;
23
25
24
/**
26
/**
25
 * <p>Tag handler for &lt;expr&gt; in JSTL's XML library.</p>
27
 * <p>Tag handler for &lt;out&gt; in JSTL's XML library.</p>
26
 *
28
 *
29
 * TODO: should we rename this to OutSupport to match the tag name?
30
 *
27
 * @author Shawn Bayern
31
 * @author Shawn Bayern
28
 */
32
 */
29
33
Lines 61-73 Link Here
61
    // applies XPath expression from 'select' and prints the result
65
    // applies XPath expression from 'select' and prints the result
62
    public int doStartTag() throws JspException {
66
    public int doStartTag() throws JspException {
63
        try {
67
        try {
64
	    XPathUtil xu = new XPathUtil(pageContext);
68
            XPathUtil xu = new XPathUtil(pageContext);
65
	    String result = xu.valueOf(XPathUtil.getContext(this), select);
69
            String result = xu.valueOf(XPathUtil.getContext(this), select);
66
	    org.apache.taglibs.standard.tag.common.core.OutSupport.out(
70
            EscapeXML.emit(result, escapeXml, pageContext.getOut());
67
              pageContext, escapeXml, result);
71
    	    return SKIP_BODY;
68
	    return SKIP_BODY;
69
        } catch (java.io.IOException ex) {
72
        } catch (java.io.IOException ex) {
70
	    throw new JspTagException(ex.toString(), ex);
73
            throw new JspTagException(ex.toString(), ex);
71
        }
74
        }
72
    }
75
    }
73
76
(-)impl/src/main/java/org/apache/taglibs/standard/tag/common/core/OutSupport.java (-117 / +32 lines)
Lines 17-28 Link Here
17
17
18
package org.apache.taglibs.standard.tag.common.core;
18
package org.apache.taglibs.standard.tag.common.core;
19
19
20
import org.apache.taglibs.standard.util.EscapeXML;
21
20
import java.io.IOException;
22
import java.io.IOException;
21
import java.io.Reader;
22
23
23
import javax.servlet.jsp.JspException;
24
import javax.servlet.jsp.JspException;
24
import javax.servlet.jsp.JspWriter;
25
import javax.servlet.jsp.JspTagException;
25
import javax.servlet.jsp.PageContext;
26
import javax.servlet.jsp.tagext.BodyTagSupport;
26
import javax.servlet.jsp.tagext.BodyTagSupport;
27
27
28
/**
28
/**
Lines 47-53 Link Here
47
    protected Object value;                     // tag attribute
47
    protected Object value;                     // tag attribute
48
    protected String def;			// tag attribute
48
    protected String def;			// tag attribute
49
    protected boolean escapeXml;		// tag attribute
49
    protected boolean escapeXml;		// tag attribute
50
    private boolean needBody;			// non-space body needed?
50
    private Object output;
51
51
52
    //*********************************************************************
52
    //*********************************************************************
53
    // Construction and initialization
53
    // Construction and initialization
Lines 65-72 Link Here
65
    // resets local state
65
    // resets local state
66
    private void init() {
66
    private void init() {
67
        value = def = null;
67
        value = def = null;
68
        output = null;
68
        escapeXml = true;
69
        escapeXml = true;
69
	needBody = false;
70
    }
70
    }
71
71
72
    // Releases any resources we may have (or inherit)
72
    // Releases any resources we may have (or inherit)
Lines 79-205 Link Here
79
    //*********************************************************************
79
    //*********************************************************************
80
    // Tag logic
80
    // Tag logic
81
81
82
    // evaluates 'value' and determines if the body should be evaluted
82
    @Override
83
    public int doStartTag() throws JspException {
83
    public int doStartTag() throws JspException {
84
84
85
      needBody = false;			// reset state related to 'default'
85
        this.bodyContent = null;  // clean-up body (just in case container is pooling tag handlers)
86
      this.bodyContent = null;  // clean-up body (just in case container is pooling tag handlers)
87
      
88
      try {
89
	// print value if available; otherwise, try 'default'
90
	if (value != null) {
91
            out(pageContext, escapeXml, value);
92
	    return SKIP_BODY;
93
	} else {
94
	    // if we don't have a 'default' attribute, just go to the body
95
	    if (def == null) {
96
		needBody = true;
97
		return EVAL_BODY_BUFFERED;
98
	    }
99
86
100
	    // if we do have 'default', print it
87
        // output value if not null
101
	    if (def != null) {
88
        if (value != null) {
102
		// good 'default'
89
            output = value;
103
                out(pageContext, escapeXml, def);
90
            return SKIP_BODY;
104
	    }
91
        }
105
	    return SKIP_BODY;
106
	}
107
      } catch (IOException ex) {
108
	throw new JspException(ex.toString(), ex);
109
      }
110
    }
111
92
112
    // prints the body if necessary; reports errors
93
        // output default if supplied
113
    public int doEndTag() throws JspException {
94
        if (def != null ) {
114
      try {
95
            output = def;
115
	if (!needBody)
96
            return SKIP_BODY;
116
	    return EVAL_PAGE;		// nothing more to do
97
        }
117
98
118
	// trim and print out the body
99
        // output body as default
119
	if (bodyContent != null && bodyContent.getString() != null)
100
        output = ""; // need to reset as doAfterBody will not be called with an empty tag
120
            out(pageContext, escapeXml, bodyContent.getString().trim());
101
        // TODO: to avoid buffering, can we wrap out in a filter that performs escaping and use EVAL_BODY_INCLUDE?
121
	return EVAL_PAGE;
102
        return EVAL_BODY_BUFFERED;
122
      } catch (IOException ex) {
123
	throw new JspException(ex.toString(), ex);
124
      }
125
    }
103
    }
126
104
127
105
    @Override
128
    //*********************************************************************
106
    public int doAfterBody() throws JspException {
129
    // Public utility methods
107
        output = bodyContent.getString().trim();
130
108
        return SKIP_BODY;
131
    /**
132
     * Outputs <tt>text</tt> to <tt>pageContext</tt>'s current JspWriter.
133
     * If <tt>escapeXml</tt> is true, performs the following substring
134
     * replacements (to facilitate output to XML/HTML pages):
135
     *
136
     *    & -> &amp;
137
     *    < -> &lt;
138
     *    > -> &gt;
139
     *    " -> &#034;
140
     *    ' -> &#039;
141
     *
142
     * See also Util.escapeXml().
143
     */
144
    public static void out(PageContext pageContext,
145
                           boolean escapeXml,
146
                           Object obj) throws IOException {
147
        JspWriter w = pageContext.getOut();
148
	if (!escapeXml) {
149
            // write chars as is
150
            if (obj instanceof Reader) {
151
                Reader reader = (Reader)obj;
152
                char[] buf = new char[4096];
153
                int count;
154
                while ((count=reader.read(buf, 0, 4096)) != -1) {
155
                    w.write(buf, 0, count);
156
                }
157
            } else {
158
                w.write(obj.toString());
159
            }
160
        } else {
161
            // escape XML chars
162
            if (obj instanceof Reader) {
163
                Reader reader = (Reader)obj;
164
                char[] buf = new char[4096];
165
                int count;
166
                while ((count = reader.read(buf, 0, 4096)) != -1) {
167
                    writeEscapedXml(buf, count, w);
168
                }
169
            } else {
170
                String text = obj.toString();
171
                writeEscapedXml(text.toCharArray(), text.length(), w);
172
            }
173
        }
174
    }
109
    }
175
110
176
   /**
111
    @Override
177
     *
112
    public int doEndTag() throws JspException {
178
     *  Optimized to create no extra objects and write directly
113
        try {
179
     *  to the JspWriter using blocks of escaped and unescaped characters
114
            EscapeXML.emit(output, escapeXml, pageContext.getOut());
180
     *
115
        } catch (IOException e) {
181
     */
116
            throw new JspTagException(e);
182
    private static void writeEscapedXml(char[] buffer, int length, JspWriter w) throws IOException{
183
        int start = 0;
184
185
        for (int i = 0; i < length; i++) {
186
            char c = buffer[i];
187
            if (c <= Util.HIGHEST_SPECIAL) {
188
                char[] escaped = Util.specialCharactersRepresentation[c];
189
                if (escaped != null) {
190
                    // add unescaped portion
191
                    if (start < i) {
192
                        w.write(buffer,start,i-start);
193
                    }
194
                    // add escaped xml
195
                    w.write(escaped);
196
                    start = i + 1;
197
                }
198
            }
199
        }
117
        }
200
        // add rest of unescaped portion
118
        return EVAL_PAGE;
201
        if (start < length) {
202
            w.write(buffer,start,length-start);
203
        }
204
    }
119
    }
205
}
120
}
(-)impl/src/main/java/org/apache/taglibs/standard/tag/common/core/Util.java (-62 lines)
Lines 17-25 Link Here
17
17
18
package org.apache.taglibs.standard.tag.common.core;
18
package org.apache.taglibs.standard.tag.common.core;
19
19
20
import java.io.ByteArrayOutputStream;
21
import java.io.IOException;
22
import java.io.OutputStreamWriter;
23
import java.text.DateFormat;
20
import java.text.DateFormat;
24
import java.util.Enumeration;
21
import java.util.Enumeration;
25
import java.util.Vector;
22
import java.util.Vector;
Lines 46-61 Link Here
46
    private static final String LONG = "long";
43
    private static final String LONG = "long";
47
    private static final String FULL = "full";
44
    private static final String FULL = "full";
48
45
49
    public static final int HIGHEST_SPECIAL = '>';
50
    public static char[][] specialCharactersRepresentation = new char[HIGHEST_SPECIAL + 1][];
51
    static {
52
        specialCharactersRepresentation['&'] = "&amp;".toCharArray();
53
        specialCharactersRepresentation['<'] = "&lt;".toCharArray();
54
        specialCharactersRepresentation['>'] = "&gt;".toCharArray();
55
        specialCharactersRepresentation['"'] = "&#034;".toCharArray();
56
        specialCharactersRepresentation['\''] = "&#039;".toCharArray();
57
    }
58
59
    /*
46
    /*
60
     * Converts the given string description of a scope to the corresponding
47
     * Converts the given string description of a scope to the corresponding
61
     * PageContext constant.
48
     * PageContext constant.
Lines 113-170 Link Here
113
100
114
	return ret;
101
	return ret;
115
    }
102
    }
116
    
117
103
118
104
119
    /**
105
    /**
120
     * Performs the following substring replacements
121
     * (to facilitate output to XML/HTML pages):
122
     *
123
     *    & -> &amp;
124
     *    < -> &lt;
125
     *    > -> &gt;
126
     *    " -> &#034;
127
     *    ' -> &#039;
128
     *
129
     * See also OutSupport.writeEscapedXml().
130
     */
131
    public static String escapeXml(String buffer) {
132
        int start = 0;
133
        int length = buffer.length();
134
        char[] arrayBuffer = buffer.toCharArray();
135
        StringBuffer escapedBuffer = null;
136
137
        for (int i = 0; i < length; i++) {
138
            char c = arrayBuffer[i];
139
            if (c <= HIGHEST_SPECIAL) {
140
                char[] escaped = specialCharactersRepresentation[c];
141
                if (escaped != null) {
142
                    // create StringBuffer to hold escaped xml string
143
                    if (start == 0) {
144
                        escapedBuffer = new StringBuffer(length + 5);
145
                    }
146
                    // add unescaped portion
147
                    if (start < i) {
148
                        escapedBuffer.append(arrayBuffer,start,i-start);
149
                    }
150
                    start = i + 1;
151
                    // add escaped xml
152
                    escapedBuffer.append(escaped);
153
                }
154
            }
155
        }
156
        // no xml escaping was necessary
157
        if (start == 0) {
158
            return buffer;
159
        }
160
        // add rest of unescaped portion
161
        if (start < length) {
162
            escapedBuffer.append(arrayBuffer,start,length-start);
163
        }
164
        return escapedBuffer.toString();
165
    }
166
167
    /**
168
     * Get the value associated with a content-type attribute.
106
     * Get the value associated with a content-type attribute.
169
     * Syntax defined in RFC 2045, section 5.1.
107
     * Syntax defined in RFC 2045, section 5.1.
170
     */
108
     */
(-)impl/src/main/java/org/apache/taglibs/standard/util/EscapeXML.java (+205 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 * 
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 * 
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package org.apache.taglibs.standard.util;
18
19
import javax.servlet.jsp.JspWriter;
20
import java.io.IOException;
21
import java.io.Reader;
22
23
/**
24
 * Handles escaping of characters that could be interpreted as XML markup.
25
 *
26
 * The specification for <code>&lt;c:out&gt;</code> defines the following
27
 * character conversions to be applied:
28
 *
29
 * <table rules="all" frame="border">
30
 * <thead align="center">
31
 * <tr><th>Character</th><th>Character Entity Code</th></tr>
32
 * </thead>
33
 * <tbody align="center">
34
 * <tr><td>&lt;</td><td>&amp;lt;</td></tr>
35
 * <tr><td>&gt;</td><td>&amp;gt;</td></tr>
36
 * <tr><td>&amp;</td><td>&amp;amp;</td></tr>
37
 * <tr><td>&#039;</td><td>&amp;#039;</td></tr>
38
 * <tr><td>&#034;</td><td>&amp;#034;</td></tr>
39
 * </tbody>
40
 * </table>
41
 */
42
public class EscapeXML {
43
44
    private static final String[] ESCAPES;
45
    static {
46
        int size = '>' + 1; // '>' is the largest escaped value
47
        ESCAPES = new String[size];
48
        ESCAPES['<'] = "&lt;";
49
        ESCAPES['>'] = "&gt;";
50
        ESCAPES['&'] = "&amp;";
51
        ESCAPES['\''] = "&#039;";
52
        ESCAPES['"'] = "&#034;";
53
    }
54
55
    private static String getEscape(char c) {
56
        if (c < ESCAPES.length) {
57
            return ESCAPES[c];
58
        } else {
59
            return null;
60
        }
61
    }
62
63
    /**
64
     * Escape a string.
65
     *
66
     * @param src the string to escape; must not be null
67
     * @return the escaped string
68
     */
69
    public static String escape(String src) {
70
        // first pass to determine the length of the buffer so we only allocate once
71
        int length = 0;
72
        for (int i = 0; i < src.length(); i++) {
73
            char c = src.charAt(i);
74
            String escape = getEscape(c);
75
            if (escape != null) {
76
                length += escape.length();
77
            } else {
78
                length += 1;
79
            }
80
        }
81
82
        // skip copy if no escaping is needed
83
        if (length == src.length()) {
84
            return src;
85
        }
86
87
        // second pass to build the escaped string
88
        StringBuilder buf = new StringBuilder(length);
89
        for (int i = 0; i < src.length(); i++) {
90
            char c = src.charAt(i);
91
            String escape = getEscape(c);
92
            if (escape != null) {
93
                buf.append(escape);
94
            } else {
95
                buf.append(c);
96
            }
97
        }
98
        return buf.toString();
99
    }
100
101
    /**
102
     * Emit the supplied object to the specified writer, escaping characters if needed.
103
     * @param src the object to write
104
     * @param escapeXml if true, escape unsafe characters before writing
105
     * @param out the JspWriter to emit to
106
     * @throws IOException if there was a problem emitting the content
107
     */
108
    public static void emit(Object src, boolean escapeXml, JspWriter out) throws IOException {
109
        if (src instanceof Reader) {
110
            emit((Reader) src, escapeXml, out);
111
        } else {
112
            emit(String.valueOf(src), escapeXml, out);
113
        }
114
    }
115
116
    /**
117
     * Emit the supplied String to the specified writer, escaping characters if needed.
118
     * @param src the String to write
119
     * @param escapeXml if true, escape unsafe characters before writing
120
     * @param out the JspWriter to emit to
121
     * @throws IOException if there was a problem emitting the content
122
     */
123
    public static void emit(String src, boolean escapeXml, JspWriter out) throws IOException {
124
        if (escapeXml) {
125
            emit(src, out);
126
        } else {
127
            out.write(src);
128
        }
129
    }
130
131
    /**
132
     * Emit escaped content into the specified JSPWriter.
133
     *
134
     * @param src the string to escape; must not be null
135
     * @param out the JspWriter to emit to
136
     * @throws IOException if there was a problem emitting the content
137
     */
138
    public static void emit(String src, JspWriter out) throws IOException {
139
        int end = src.length();
140
        int from = 0;
141
        for (int to = from ; to < end; to++) {
142
            String escape = getEscape(src.charAt(to));
143
            if (escape != null) {
144
                if (to != from) {
145
                    out.write(src, from, to-from);
146
                }
147
                out.write(escape);
148
                from = to + 1;
149
            }
150
        }
151
        if (from != end) {
152
            out.write(src, from, end-from);
153
        }
154
    }
155
156
    /**
157
     * Copy the content of a Reader into the specified JSPWriter escaping characters if needed.
158
     *
159
     * @param src the Reader to read from
160
     * @param escapeXml if true, escape characters
161
     * @param out the JspWriter to emit to
162
     * @throws IOException if there was a problem emitting the content
163
     */
164
    public static void emit(Reader src, boolean escapeXml, JspWriter out) throws IOException {
165
        int bufferSize = out.getBufferSize();
166
        if (bufferSize == 0) {
167
            bufferSize = 4096;
168
        }
169
        char[] buffer = new char[bufferSize];
170
        int count;
171
        while ((count = src.read(buffer)) > 0) {
172
            if (escapeXml) {
173
                emit(buffer, 0, count, out);
174
            } else {
175
                out.write(buffer, 0, count);
176
            }
177
        }
178
    }
179
180
    /**
181
     * Emit escaped content into the specified JSPWriter.
182
     *
183
     * @param buffer characters to escape
184
     * @param from start position in the buffer
185
     * @param count number of characters to emit
186
     * @param out the JspWriter to emit to
187
     * @throws IOException if there was a problem emitting the content
188
     */
189
    public static void emit(char[] buffer, int from, int count, JspWriter out) throws IOException {
190
        int end = from + count;
191
        for (int to = from ; to < end; to++) {
192
            String escape = getEscape(buffer[to]);
193
            if (escape != null) {
194
                if (to != from) {
195
                    out.write(buffer, from, to-from);
196
                }
197
                out.write(escape);
198
                from = to + 1;
199
            }
200
        }
201
        if (from != end) {
202
            out.write(buffer, from, end-from);
203
        }
204
    }
205
}
(-)standard-test/src/test/java/org/apache/taglibs/standard/tag/el/core/TestOutTag.java (+40 lines)
Line 0 Link Here
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 * 
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 * 
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package org.apache.taglibs.standard.tag.el.core;
18
19
import org.apache.cactus.ServletTestCase;
20
import org.apache.cactus.WebResponse;
21
import org.apache.taglibs.standard.testutil.TestUtil;
22
23
import javax.servlet.RequestDispatcher;
24
import java.io.StringReader;
25
26
/**
27
 */
28
public class TestOutTag extends ServletTestCase {
29
30
    public void testValue() throws Exception {
31
        request.setAttribute("cats", new StringReader("cats & dogs"));
32
        request.setAttribute("dogs", new StringReader("cats & dogs"));
33
        RequestDispatcher rd = config.getServletContext().getRequestDispatcher(TestUtil.getTestJsp(this));
34
        rd.forward(request, response);
35
    }
36
37
    public void endValue(WebResponse response) throws Exception {
38
        assertEquals(TestUtil.loadResource(this), response.getText());
39
    }
40
}
(-)standard-test/src/test/java/org/apache/taglibs/standard/testutil/TestUtil.java (+22 lines)
Lines 17-22 Link Here
17
17
18
package org.apache.taglibs.standard.testutil;
18
package org.apache.taglibs.standard.testutil;
19
19
20
import java.io.IOException;
21
import java.io.InputStream;
22
import java.io.InputStreamReader;
23
import java.io.Reader;
24
20
/**
25
/**
21
 * Helper class for Cactus tests
26
 * Helper class for Cactus tests
22
 */
27
 */
Lines 35-38 Link Here
35
         String baseName = className.replace('.', '/');
40
         String baseName = className.replace('.', '/');
36
         return "/" + baseName + ".jsp";
41
         return "/" + baseName + ".jsp";
37
     }
42
     }
43
44
    public static String loadResource(Object obj) throws IOException {
45
        Class clazz = obj.getClass();
46
        InputStream is = clazz.getResourceAsStream(clazz.getSimpleName() + ".txt");
47
        Reader reader = new InputStreamReader(is);
48
        try {
49
            char[] buffer = new char[1024];
50
            StringBuilder s = new StringBuilder();
51
            int count;
52
            while ((count = reader.read(buffer)) > 0) {
53
                s.append(buffer, 0, count);
54
            }
55
            return s.toString();        
56
        } finally {
57
            reader.close();
58
        }
59
    }
38
}
60
}
(-)standard-test/src/test/resources/org/apache/taglibs/standard/tag/el/core/TestOutTag.txt (+15 lines)
Line 0 Link Here
1
2
Start
3
Hello World
4
cats &amp; dogs
5
cats & dogs
6
5
7
8
default
9
Default from Body
10
&lt;b&gt;cats &amp; dogs&lt;/b&gt;
11
<b>cats & dogs</b>
12
Reader
13
cats &amp; dogs
14
cats & dogs
15
End
(-)standard-test/src/main/webapp/org/apache/taglibs/standard/tag/el/core/TestOutTag.jsp (+17 lines)
Line 0 Link Here
1
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
2
Start
3
<c:out value="Hello World"/>
4
<c:out value="cats & dogs"/>
5
<c:out value="cats & dogs" escapeXml="false"/>
6
<c:out value="${5}"/>
7
<c:out value="${null}"/>
8
<c:out value="${null}" default="default"/>
9
<c:out value="${null}"><%-- this will be trimmed --%>
10
    Default from Body
11
</c:out>
12
<c:out value="${null}" escapeXml="true"><b>cats & dogs</b></c:out>
13
<c:out value="${null}" escapeXml="false"><b>cats & dogs</b></c:out>
14
Reader
15
<c:out value="${cats}"/>
16
<c:out value="${dogs}" escapeXml="false"/>
17
End
(-)standard-test/pom.xml (-4 / +3 lines)
Lines 69-76 Link Here
69
      <version>1.2-SNAPSHOT</version>
69
      <version>1.2-SNAPSHOT</version>
70
    </dependency>
70
    </dependency>
71
71
72
    <!-- dependencies of the above - consider cleaning -->
72
    <!-- dependencies of the above -->
73
    <!--
74
    <dependency>
73
    <dependency>
75
      <groupId>javax.servlet</groupId>
74
      <groupId>javax.servlet</groupId>
76
      <artifactId>servlet-api</artifactId>
75
      <artifactId>servlet-api</artifactId>
Lines 89-95 Link Here
89
      <version>1.0</version>
88
      <version>1.0</version>
90
      <scope>provided</scope>
89
      <scope>provided</scope>
91
    </dependency>
90
    </dependency>
92
    -->
91
      
93
    <dependency>
92
    <dependency>
94
      <groupId>xalan</groupId>
93
      <groupId>xalan</groupId>
95
      <artifactId>xalan</artifactId>
94
      <artifactId>xalan</artifactId>
Lines 192-198 Link Here
192
      <plugin>
191
      <plugin>
193
        <groupId>org.mortbay.jetty</groupId>
192
        <groupId>org.mortbay.jetty</groupId>
194
        <artifactId>maven-jetty-plugin</artifactId>
193
        <artifactId>maven-jetty-plugin</artifactId>
195
        <version>6.1.10</version>   <!-- appears to be required to enable daemon=true behaviour -->
194
        <version>6.1.24</version>   <!-- appears to be required to enable daemon=true behaviour -->
196
        <configuration>
195
        <configuration>
197
          <scanIntervalSeconds>10</scanIntervalSeconds>
196
          <scanIntervalSeconds>10</scanIntervalSeconds>
198
          <stopPort>9999</stopPort>
197
          <stopPort>9999</stopPort>

Return to bug 49565