1 /*
2  * reserved comment block
3  * DO NOT REMOVE OR ALTER!
4  */
5 /**
6  * Licensed to the Apache Software Foundation (ASF) under one
7  * or more contributor license agreements. See the NOTICE file
8  * distributed with this work for additional information
9  * regarding copyright ownership. The ASF licenses this file
10  * to you under the Apache License, Version 2.0 (the
11  * "License"); you may not use this file except in compliance
12  * with the License. You may obtain a copy of the License at
13  *
14  * http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing,
17  * software distributed under the License is distributed on an
18  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19  * KIND, either express or implied. See the License for the
20  * specific language governing permissions and limitations
21  * under the License.
22  */
23 package com.sun.org.apache.xml.internal.security.c14n.implementations;
24 
25 import java.net.URI;
26 import java.net.URISyntaxException;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Map;
33 
34 import org.w3c.dom.Attr;
35 
36 /**
37  * An XmlAttrStack that is shared between the Canonical XML 1.0 and 1.1 implementations.
38  */
39 class XmlAttrStack {
40 
41     private static final com.sun.org.slf4j.internal.Logger LOG =
42         com.sun.org.slf4j.internal.LoggerFactory.getLogger(XmlAttrStack.class);
43 
44     static class XmlsStackElement {
45         int level;
46         boolean rendered = false;
47         List<Attr> nodes = new ArrayList<>();
48     }
49 
50     private int currentLevel = 0;
51     private int lastlevel = 0;
52     private XmlsStackElement cur;
53     private List<XmlsStackElement> levels = new ArrayList<>();
54     private boolean c14n11;
55 
XmlAttrStack(boolean c14n11)56     public XmlAttrStack(boolean c14n11) {
57         this.c14n11 = c14n11;
58     }
59 
push(int level)60     void push(int level) {
61         currentLevel = level;
62         if (currentLevel == -1) {
63             return;
64         }
65         cur = null;
66         while (lastlevel >= currentLevel) {
67             levels.remove(levels.size() - 1);
68             int newSize = levels.size();
69             if (newSize == 0) {
70                 lastlevel = 0;
71                 return;
72             }
73             lastlevel = levels.get(newSize - 1).level;
74         }
75     }
76 
addXmlnsAttr(Attr n)77     void addXmlnsAttr(Attr n) {
78         if (cur == null) {
79             cur = new XmlsStackElement();
80             cur.level = currentLevel;
81             levels.add(cur);
82             lastlevel = currentLevel;
83         }
84         cur.nodes.add(n);
85     }
86 
getXmlnsAttr(Collection<Attr> col)87     void getXmlnsAttr(Collection<Attr> col) {
88         int size = levels.size() - 1;
89         if (cur == null) {
90             cur = new XmlsStackElement();
91             cur.level = currentLevel;
92             lastlevel = currentLevel;
93             levels.add(cur);
94         }
95         boolean parentRendered = false;
96         XmlsStackElement e = null;
97         if (size == -1) {
98             parentRendered = true;
99         } else {
100             e = levels.get(size);
101             if (e.rendered && e.level + 1 == currentLevel) {
102                 parentRendered = true;
103             }
104         }
105         if (parentRendered) {
106             col.addAll(cur.nodes);
107             cur.rendered = true;
108             return;
109         }
110 
111         Map<String, Attr> loa = new HashMap<>();
112         if (c14n11) {
113             List<Attr> baseAttrs = new ArrayList<>();
114             boolean successiveOmitted = true;
115             for (; size >= 0; size--) {
116                 e = levels.get(size);
117                 if (e.rendered) {
118                     successiveOmitted = false;
119                 }
120                 Iterator<Attr> it = e.nodes.iterator();
121                 while (it.hasNext() && successiveOmitted) {
122                     Attr n = it.next();
123                     if (n.getLocalName().equals("base") && !e.rendered) {
124                         baseAttrs.add(n);
125                     } else if (!loa.containsKey(n.getName())) {
126                         loa.put(n.getName(), n);
127                     }
128                 }
129             }
130             if (!baseAttrs.isEmpty()) {
131                 Iterator<Attr> it = col.iterator();
132                 String base = null;
133                 Attr baseAttr = null;
134                 while (it.hasNext()) {
135                     Attr n = it.next();
136                     if (n.getLocalName().equals("base")) {
137                         base = n.getValue();
138                         baseAttr = n;
139                         break;
140                     }
141                 }
142                 it = baseAttrs.iterator();
143                 while (it.hasNext()) {
144                     Attr n = it.next();
145                     if (base == null) {
146                         base = n.getValue();
147                         baseAttr = n;
148                     } else {
149                         try {
150                             base = joinURI(n.getValue(), base);
151                         } catch (URISyntaxException ue) {
152                             LOG.debug(ue.getMessage(), ue);
153                         }
154                     }
155                 }
156                 if (base != null && base.length() != 0) {
157                     baseAttr.setValue(base);
158                     col.add(baseAttr);
159                 }
160             }
161         } else {
162             for (; size >= 0; size--) {
163                 e = levels.get(size);
164                 Iterator<Attr> it = e.nodes.iterator();
165                 while (it.hasNext()) {
166                     Attr n = it.next();
167                     if (!loa.containsKey(n.getName())) {
168                         loa.put(n.getName(), n);
169                     }
170                 }
171             }
172         }
173 
174         cur.rendered = true;
175         col.addAll(loa.values());
176     }
177 
joinURI(String baseURI, String relativeURI)178     private static String joinURI(String baseURI, String relativeURI) throws URISyntaxException {
179         String bscheme = null;
180         String bauthority = null;
181         String bpath = "";
182         String bquery = null;
183 
184         // pre-parse the baseURI
185         if (baseURI != null) {
186             if (baseURI.endsWith("..")) {
187                 baseURI = baseURI + "/";
188             }
189             URI base = new URI(baseURI);
190             bscheme = base.getScheme();
191             bauthority = base.getAuthority();
192             bpath = base.getPath();
193             bquery = base.getQuery();
194         }
195 
196         URI r = new URI(relativeURI);
197         String rscheme = r.getScheme();
198         String rauthority = r.getAuthority();
199         String rpath = r.getPath();
200         String rquery = r.getQuery();
201 
202         String tscheme, tauthority, tpath, tquery;
203         if (rscheme != null && rscheme.equals(bscheme)) {
204             rscheme = null;
205         }
206         if (rscheme != null) {
207             tscheme = rscheme;
208             tauthority = rauthority;
209             tpath = removeDotSegments(rpath);
210             tquery = rquery;
211         } else {
212             if (rauthority != null) {
213                 tauthority = rauthority;
214                 tpath = removeDotSegments(rpath);
215                 tquery = rquery;
216             } else {
217                 if (rpath.length() == 0) {
218                     tpath = bpath;
219                     if (rquery != null) {
220                         tquery = rquery;
221                     } else {
222                         tquery = bquery;
223                     }
224                 } else {
225                     if (rpath.startsWith("/")) {
226                         tpath = removeDotSegments(rpath);
227                     } else {
228                         if (bauthority != null && bpath.length() == 0) {
229                             tpath = "/" + rpath;
230                         } else {
231                             int last = bpath.lastIndexOf('/');
232                             if (last == -1) {
233                                 tpath = rpath;
234                             } else {
235                                 tpath = bpath.substring(0, last+1) + rpath;
236                             }
237                         }
238                         tpath = removeDotSegments(tpath);
239                     }
240                     tquery = rquery;
241                 }
242                 tauthority = bauthority;
243             }
244             tscheme = bscheme;
245         }
246         return new URI(tscheme, tauthority, tpath, tquery, null).toString();
247     }
248 
removeDotSegments(String path)249     private static String removeDotSegments(String path) {
250         LOG.debug("STEP OUTPUT BUFFER\t\tINPUT BUFFER");
251 
252         // 1. The input buffer is initialized with the now-appended path
253         // components then replace occurrences of "//" in the input buffer
254         // with "/" until no more occurrences of "//" are in the input buffer.
255         String input = path;
256         while (input.indexOf("//") > -1) {
257             input = input.replaceAll("//", "/");
258         }
259 
260         // Initialize the output buffer with the empty string.
261         StringBuilder output = new StringBuilder();
262 
263         // If the input buffer starts with a root slash "/" then move this
264         // character to the output buffer.
265         if (input.charAt(0) == '/') {
266             output.append("/");
267             input = input.substring(1);
268         }
269 
270         printStep("1 ", output.toString(), input);
271 
272         // While the input buffer is not empty, loop as follows
273         while (input.length() != 0) {
274             // 2A. If the input buffer begins with a prefix of "./",
275             // then remove that prefix from the input buffer
276             // else if the input buffer begins with a prefix of "../", then
277             // if also the output does not contain the root slash "/" only,
278             // then move this prefix to the end of the output buffer else
279             // remove that prefix
280             if (input.startsWith("./")) {
281                 input = input.substring(2);
282                 printStep("2A", output.toString(), input);
283             } else if (input.startsWith("../")) {
284                 input = input.substring(3);
285                 if (!output.toString().equals("/")) {
286                     output.append("../");
287                 }
288                 printStep("2A", output.toString(), input);
289                 // 2B. if the input buffer begins with a prefix of "/./" or "/.",
290                 // where "." is a complete path segment, then replace that prefix
291                 // with "/" in the input buffer; otherwise,
292             } else if (input.startsWith("/./")) {
293                 input = input.substring(2);
294                 printStep("2B", output.toString(), input);
295             } else if (input.equals("/.")) {
296                 // FIXME: what is complete path segment?
297                 input = input.replaceFirst("/.", "/");
298                 printStep("2B", output.toString(), input);
299                 // 2C. if the input buffer begins with a prefix of "/../" or "/..",
300                 // where ".." is a complete path segment, then replace that prefix
301                 // with "/" in the input buffer and if also the output buffer is
302                 // empty, last segment in the output buffer equals "../" or "..",
303                 // where ".." is a complete path segment, then append ".." or "/.."
304                 // for the latter case respectively to the output buffer else
305                 // remove the last segment and its preceding "/" (if any) from the
306                 // output buffer and if hereby the first character in the output
307                 // buffer was removed and it was not the root slash then delete a
308                 // leading slash from the input buffer; otherwise,
309             } else if (input.startsWith("/../")) {
310                 input = input.substring(3);
311                 if (output.length() == 0) {
312                     output.append("/");
313                 } else if (output.toString().endsWith("../")) {
314                     output.append("..");
315                 } else if (output.toString().endsWith("..")) {
316                     output.append("/..");
317                 } else {
318                     int index = output.lastIndexOf("/");
319                     if (index == -1) {
320                         output = new StringBuilder();
321                         if (input.charAt(0) == '/') {
322                             input = input.substring(1);
323                         }
324                     } else {
325                         output = output.delete(index, output.length());
326                     }
327                 }
328                 printStep("2C", output.toString(), input);
329             } else if (input.equals("/..")) {
330                 // FIXME: what is complete path segment?
331                 input = input.replaceFirst("/..", "/");
332                 if (output.length() == 0) {
333                     output.append("/");
334                 } else if (output.toString().endsWith("../")) {
335                     output.append("..");
336                 } else if (output.toString().endsWith("..")) {
337                     output.append("/..");
338                 } else {
339                     int index = output.lastIndexOf("/");
340                     if (index == -1) {
341                         output = new StringBuilder();
342                         if (input.charAt(0) == '/') {
343                             input = input.substring(1);
344                         }
345                     } else {
346                         output = output.delete(index, output.length());
347                     }
348                 }
349                 printStep("2C", output.toString(), input);
350                 // 2D. if the input buffer consists only of ".", then remove
351                 // that from the input buffer else if the input buffer consists
352                 // only of ".." and if the output buffer does not contain only
353                 // the root slash "/", then move the ".." to the output buffer
354                 // else delte it.; otherwise,
355             } else if (input.equals(".")) {
356                 input = "";
357                 printStep("2D", output.toString(), input);
358             } else if (input.equals("..")) {
359                 if (!output.toString().equals("/")) {
360                     output.append("..");
361                 }
362                 input = "";
363                 printStep("2D", output.toString(), input);
364                 // 2E. move the first path segment (if any) in the input buffer
365                 // to the end of the output buffer, including the initial "/"
366                 // character (if any) and any subsequent characters up to, but not
367                 // including, the next "/" character or the end of the input buffer.
368             } else {
369                 int end = -1;
370                 int begin = input.indexOf('/');
371                 if (begin == 0) {
372                     end = input.indexOf('/', 1);
373                 } else {
374                     end = begin;
375                     begin = 0;
376                 }
377                 String segment;
378                 if (end == -1) {
379                     segment = input.substring(begin);
380                     input = "";
381                 } else {
382                     segment = input.substring(begin, end);
383                     input = input.substring(end);
384                 }
385                 output.append(segment);
386                 printStep("2E", output.toString(), input);
387             }
388         }
389 
390         // 3. Finally, if the only or last segment of the output buffer is
391         // "..", where ".." is a complete path segment not followed by a slash
392         // then append a slash "/". The output buffer is returned as the result
393         // of remove_dot_segments
394         if (output.toString().endsWith("..")) {
395             output.append("/");
396             printStep("3 ", output.toString(), input);
397         }
398 
399         return output.toString();
400     }
401 
printStep(String step, String output, String input)402     private static void printStep(String step, String output, String input) {
403         if (LOG.isDebugEnabled()) {
404             LOG.debug(" " + step + ":   " + output);
405             if (output.length() == 0) {
406                 LOG.debug("\t\t\t\t" + input);
407             } else {
408                 LOG.debug("\t\t\t" + input);
409             }
410         }
411     }
412 }
413