View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.util;
20  
21  import java.io.FilterWriter;
22  import java.io.IOException;
23  import java.io.Writer;
24  import org.apache.myfaces.shared.util.ClassUtils;
25  import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
26  
27  /**
28   * There are unicodes outside the ranges defined in the
29   * <a href="https://www.w3.org/TR/REC-xml/#charsets">XML 1.0 specification</a> that break XML parsers
30   * and therefore must be filtered out when writing partial responses. Otherwise this may lead to
31   * Denial of Service attacks.
32   * @see https://issues.apache.org/jira/browse/MYFACES-4266
33   */
34  public class IllegalXmlCharacterFilterWriter extends FilterWriter
35  {
36      private static final char BLANK_CHAR = ' ';
37  
38      // Java 1.6 doesnt support Character.isSurrogate
39      private static boolean java16;
40      
41      static 
42      {
43          try
44          {
45              java16 = ClassUtils.classForName("java.util.Objects") == null;
46          }
47          catch (Throwable e)
48          {
49              java16 = true;
50          }
51  
52          if (java16)
53          {
54              System.err.println("Skip using " + IllegalXmlCharacterFilterWriter.class.getName()
55                      + ", it's only available in Java 1.6+");
56          }
57      }
58  
59      public IllegalXmlCharacterFilterWriter(Writer out)
60      {
61          super(out);
62      }
63  
64      @Override
65      public void write(int c) throws IOException 
66      {
67          if (java16)
68          {
69              super.write(c);
70              return;
71          }
72  
73          if (isInvalidChar(c))
74          {
75              super.write((int) BLANK_CHAR);
76          }
77          else
78          {
79              super.write(c);
80          }
81      }
82  
83      @Override
84      public void write(char[] cbuf, int off, int len) throws IOException 
85      {
86          if (java16)
87          {
88              super.write(cbuf, off, len);
89              return;
90          }
91  
92          super.write(encodeCharArray(cbuf, off, len), off, len);
93      }
94  
95      @Override
96      public void write(String str, int off, int len) throws IOException 
97      {
98          if (java16)
99          {
100             super.write(str, off, len);
101             return;
102         }
103 
104         super.write(encodeString(str, off, len), off, len);
105     }
106 
107     @IgnoreJRERequirement // Java 1.6 doesnt support Character.isSurrogate
108     private static String encodeString(String str, int off, int len)
109     {
110         if (str == null)
111         {
112             return null;
113         }
114         
115         int to = off + len;
116         boolean surrogateResolved = false;
117         char c;
118         int codePoint;
119         char cNext;
120         
121         char[] encoded = null;
122         for (int i = off; i < to; i++)
123         {
124             if (surrogateResolved == true)
125             {
126                 surrogateResolved = false;
127                 continue;
128             }
129             
130             c = str.charAt(i);
131             codePoint = c;
132 
133             if (Character.isHighSurrogate(c) && i + 1 < to)
134             {
135                 cNext = str.charAt(i + 1);
136                 if (Character.isLowSurrogate(cNext))
137                 {
138                     codePoint = Character.toCodePoint(c, cNext);
139                     surrogateResolved = true;
140                 }
141             }
142 
143             // try to resolve surrogate, this is required e.g. for emojis
144             if ((!surrogateResolved && Character.isSurrogate(c)) || isInvalidChar(codePoint))
145             {
146                 if (encoded == null)
147                 {
148                     encoded = str.toCharArray();
149                 }
150                 encoded[i] = BLANK_CHAR;
151                 
152                 if (surrogateResolved)
153                 {
154                     encoded[i + 1] = BLANK_CHAR;
155                 }
156             }
157         }
158 
159         if (encoded != null)
160         {
161             return String.valueOf(encoded);
162         }
163 
164         return str;
165     }
166 
167     @IgnoreJRERequirement // Java 1.6 doesnt support Character.isSurrogate
168     private static char[] encodeCharArray(char[] cbuf, int off, int len)
169     {
170         if (cbuf == null)
171         {
172             return null;
173         }
174 
175         int to = off + len;
176         
177         boolean surrogateResolved = false;
178         char c;
179         int codePoint;
180         char cNext;
181 
182         for (int i = off; i < to; i++)
183         {
184             if (surrogateResolved == true)
185             {
186                 surrogateResolved = false;
187                 continue;
188             }
189             
190             c = cbuf[i];
191             codePoint = c;
192 
193             // try to resolve surrogate, this is required e.g. for emojis
194             if (Character.isHighSurrogate(c) && i + 1 < to)
195             {
196                 cNext = cbuf[i + 1];
197                 if (Character.isLowSurrogate(cNext))
198                 {
199                     codePoint = Character.toCodePoint(c, cNext);
200                     surrogateResolved = true;
201                 }
202             }
203             
204             if ((!surrogateResolved && Character.isSurrogate(c)) || isInvalidChar(codePoint))
205             {
206                 cbuf[i] = BLANK_CHAR;
207                 
208                 if (surrogateResolved)
209                 {
210                     cbuf[i + 1] = BLANK_CHAR;
211                 }
212             }
213         }
214         return cbuf;
215     }
216 
217     private static boolean isInvalidChar(int codePoint)
218     {
219         if (codePoint == 1113088)
220         {
221             return true;
222         }
223 
224         if (codePoint == 0x9 || codePoint == 0xA || codePoint == 0xD) 
225         {
226             return false;
227         }
228         if (codePoint >= 0x20 && codePoint <= 0xD7FF) 
229         {
230             return false;
231         }
232         if (codePoint >= 0xE000 && codePoint <= 0xFFFD) 
233         {
234             return false;
235         }
236         if (codePoint >= 0x10000 && codePoint <= 0x10FFFF) 
237         {
238             return false;
239         }
240 
241         return true;
242     }
243 }