| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.xml.security.c14n.implementations; |
| |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.w3c.dom.Attr; |
| |
| /** |
| * An XmlAttrStack that is shared between the Canonical XML 1.0 and 1.1 implementations. |
| */ |
| class XmlAttrStack { |
| |
| private static final org.slf4j.Logger LOG = |
| org.slf4j.LoggerFactory.getLogger(XmlAttrStack.class); |
| |
| static class XmlsStackElement { |
| int level; |
| boolean rendered = false; |
| List<Attr> nodes = new ArrayList<>(); |
| } |
| |
| private int currentLevel = 0; |
| private int lastlevel = 0; |
| private XmlsStackElement cur; |
| private List<XmlsStackElement> levels = new ArrayList<>(); |
| private boolean c14n11; |
| |
| public XmlAttrStack(boolean c14n11) { |
| this.c14n11 = c14n11; |
| } |
| |
| void push(int level) { |
| currentLevel = level; |
| if (currentLevel == -1) { |
| return; |
| } |
| cur = null; |
| while (lastlevel >= currentLevel) { |
| levels.remove(levels.size() - 1); |
| int newSize = levels.size(); |
| if (newSize == 0) { |
| lastlevel = 0; |
| return; |
| } |
| lastlevel = levels.get(newSize - 1).level; |
| } |
| } |
| |
| void addXmlnsAttr(Attr n) { |
| if (cur == null) { |
| cur = new XmlsStackElement(); |
| cur.level = currentLevel; |
| levels.add(cur); |
| lastlevel = currentLevel; |
| } |
| cur.nodes.add(n); |
| } |
| |
| void getXmlnsAttr(Collection<Attr> col) { |
| int size = levels.size() - 1; |
| if (cur == null) { |
| cur = new XmlsStackElement(); |
| cur.level = currentLevel; |
| lastlevel = currentLevel; |
| levels.add(cur); |
| } |
| boolean parentRendered = false; |
| XmlsStackElement e = null; |
| if (size == -1) { |
| parentRendered = true; |
| } else { |
| e = levels.get(size); |
| if (e.rendered && e.level + 1 == currentLevel) { |
| parentRendered = true; |
| } |
| } |
| if (parentRendered) { |
| col.addAll(cur.nodes); |
| cur.rendered = true; |
| return; |
| } |
| |
| Map<String, Attr> loa = new HashMap<>(); |
| if (c14n11) { |
| List<Attr> baseAttrs = new ArrayList<>(); |
| boolean successiveOmitted = true; |
| for (; size >= 0; size--) { |
| e = levels.get(size); |
| if (e.rendered) { |
| successiveOmitted = false; |
| } |
| Iterator<Attr> it = e.nodes.iterator(); |
| while (it.hasNext() && successiveOmitted) { |
| Attr n = it.next(); |
| if (n.getLocalName().equals("base") && !e.rendered) { |
| baseAttrs.add(n); |
| } else if (!loa.containsKey(n.getName())) { |
| loa.put(n.getName(), n); |
| } |
| } |
| } |
| if (!baseAttrs.isEmpty()) { |
| Iterator<Attr> it = col.iterator(); |
| String base = null; |
| Attr baseAttr = null; |
| while (it.hasNext()) { |
| Attr n = it.next(); |
| if (n.getLocalName().equals("base")) { |
| base = n.getValue(); |
| baseAttr = n; |
| break; |
| } |
| } |
| it = baseAttrs.iterator(); |
| while (it.hasNext()) { |
| Attr n = it.next(); |
| if (base == null) { |
| base = n.getValue(); |
| baseAttr = n; |
| } else { |
| try { |
| base = joinURI(n.getValue(), base); |
| } catch (URISyntaxException ue) { |
| LOG.debug(ue.getMessage(), ue); |
| } |
| } |
| } |
| if (base != null && base.length() != 0) { |
| baseAttr.setValue(base); |
| col.add(baseAttr); |
| } |
| } |
| } else { |
| for (; size >= 0; size--) { |
| e = levels.get(size); |
| Iterator<Attr> it = e.nodes.iterator(); |
| while (it.hasNext()) { |
| Attr n = it.next(); |
| if (!loa.containsKey(n.getName())) { |
| loa.put(n.getName(), n); |
| } |
| } |
| } |
| } |
| |
| cur.rendered = true; |
| col.addAll(loa.values()); |
| } |
| |
| private static String joinURI(String baseURI, String relativeURI) throws URISyntaxException { |
| String bscheme = null; |
| String bauthority = null; |
| String bpath = ""; |
| String bquery = null; |
| |
| // pre-parse the baseURI |
| if (baseURI != null) { |
| if (baseURI.endsWith("..")) { |
| baseURI = baseURI + "/"; |
| } |
| URI base = new URI(baseURI); |
| bscheme = base.getScheme(); |
| bauthority = base.getAuthority(); |
| bpath = base.getPath(); |
| bquery = base.getQuery(); |
| } |
| |
| URI r = new URI(relativeURI); |
| String rscheme = r.getScheme(); |
| String rauthority = r.getAuthority(); |
| String rpath = r.getPath(); |
| String rquery = r.getQuery(); |
| |
| String tscheme, tauthority, tpath, tquery; |
| if (rscheme != null && rscheme.equals(bscheme)) { |
| rscheme = null; |
| } |
| if (rscheme != null) { |
| tscheme = rscheme; |
| tauthority = rauthority; |
| tpath = removeDotSegments(rpath); |
| tquery = rquery; |
| } else { |
| if (rauthority != null) { |
| tauthority = rauthority; |
| tpath = removeDotSegments(rpath); |
| tquery = rquery; |
| } else { |
| if (rpath.length() == 0) { |
| tpath = bpath; |
| if (rquery != null) { |
| tquery = rquery; |
| } else { |
| tquery = bquery; |
| } |
| } else { |
| if (rpath.startsWith("/")) { |
| tpath = removeDotSegments(rpath); |
| } else { |
| if (bauthority != null && bpath.length() == 0) { |
| tpath = "/" + rpath; |
| } else { |
| int last = bpath.lastIndexOf('/'); |
| if (last == -1) { |
| tpath = rpath; |
| } else { |
| tpath = bpath.substring(0, last+1) + rpath; |
| } |
| } |
| tpath = removeDotSegments(tpath); |
| } |
| tquery = rquery; |
| } |
| tauthority = bauthority; |
| } |
| tscheme = bscheme; |
| } |
| return new URI(tscheme, tauthority, tpath, tquery, null).toString(); |
| } |
| |
| private static String removeDotSegments(String path) { |
| LOG.debug("STEP OUTPUT BUFFER\t\tINPUT BUFFER"); |
| |
| // 1. The input buffer is initialized with the now-appended path |
| // components then replace occurrences of "//" in the input buffer |
| // with "/" until no more occurrences of "//" are in the input buffer. |
| String input = path; |
| while (input.indexOf("//") > -1) { |
| input = input.replaceAll("//", "/"); |
| } |
| |
| // Initialize the output buffer with the empty string. |
| StringBuilder output = new StringBuilder(); |
| |
| // If the input buffer starts with a root slash "/" then move this |
| // character to the output buffer. |
| if (input.charAt(0) == '/') { |
| output.append("/"); |
| input = input.substring(1); |
| } |
| |
| printStep("1 ", output.toString(), input); |
| |
| // While the input buffer is not empty, loop as follows |
| while (input.length() != 0) { |
| // 2A. If the input buffer begins with a prefix of "./", |
| // then remove that prefix from the input buffer |
| // else if the input buffer begins with a prefix of "../", then |
| // if also the output does not contain the root slash "/" only, |
| // then move this prefix to the end of the output buffer else |
| // remove that prefix |
| if (input.startsWith("./")) { |
| input = input.substring(2); |
| printStep("2A", output.toString(), input); |
| } else if (input.startsWith("../")) { |
| input = input.substring(3); |
| if (!output.toString().equals("/")) { |
| output.append("../"); |
| } |
| printStep("2A", output.toString(), input); |
| // 2B. if the input buffer begins with a prefix of "/./" or "/.", |
| // where "." is a complete path segment, then replace that prefix |
| // with "/" in the input buffer; otherwise, |
| } else if (input.startsWith("/./")) { |
| input = input.substring(2); |
| printStep("2B", output.toString(), input); |
| } else if (input.equals("/.")) { |
| // FIXME: what is complete path segment? |
| input = input.replaceFirst("/.", "/"); |
| printStep("2B", output.toString(), input); |
| // 2C. if the input buffer begins with a prefix of "/../" or "/..", |
| // where ".." is a complete path segment, then replace that prefix |
| // with "/" in the input buffer and if also the output buffer is |
| // empty, last segment in the output buffer equals "../" or "..", |
| // where ".." is a complete path segment, then append ".." or "/.." |
| // for the latter case respectively to the output buffer else |
| // remove the last segment and its preceding "/" (if any) from the |
| // output buffer and if hereby the first character in the output |
| // buffer was removed and it was not the root slash then delete a |
| // leading slash from the input buffer; otherwise, |
| } else if (input.startsWith("/../")) { |
| input = input.substring(3); |
| if (output.length() == 0) { |
| output.append("/"); |
| } else if (output.toString().endsWith("../")) { |
| output.append(".."); |
| } else if (output.toString().endsWith("..")) { |
| output.append("/.."); |
| } else { |
| int index = output.lastIndexOf("/"); |
| if (index == -1) { |
| output = new StringBuilder(); |
| if (input.charAt(0) == '/') { |
| input = input.substring(1); |
| } |
| } else { |
| output = output.delete(index, output.length()); |
| } |
| } |
| printStep("2C", output.toString(), input); |
| } else if (input.equals("/..")) { |
| // FIXME: what is complete path segment? |
| input = input.replaceFirst("/..", "/"); |
| if (output.length() == 0) { |
| output.append("/"); |
| } else if (output.toString().endsWith("../")) { |
| output.append(".."); |
| } else if (output.toString().endsWith("..")) { |
| output.append("/.."); |
| } else { |
| int index = output.lastIndexOf("/"); |
| if (index == -1) { |
| output = new StringBuilder(); |
| if (input.charAt(0) == '/') { |
| input = input.substring(1); |
| } |
| } else { |
| output = output.delete(index, output.length()); |
| } |
| } |
| printStep("2C", output.toString(), input); |
| // 2D. if the input buffer consists only of ".", then remove |
| // that from the input buffer else if the input buffer consists |
| // only of ".." and if the output buffer does not contain only |
| // the root slash "/", then move the ".." to the output buffer |
| // else delte it.; otherwise, |
| } else if (input.equals(".")) { |
| input = ""; |
| printStep("2D", output.toString(), input); |
| } else if (input.equals("..")) { |
| if (!output.toString().equals("/")) { |
| output.append(".."); |
| } |
| input = ""; |
| printStep("2D", output.toString(), input); |
| // 2E. move the first path segment (if any) in the input buffer |
| // to the end of the output buffer, including the initial "/" |
| // character (if any) and any subsequent characters up to, but not |
| // including, the next "/" character or the end of the input buffer. |
| } else { |
| int end = -1; |
| int begin = input.indexOf('/'); |
| if (begin == 0) { |
| end = input.indexOf('/', 1); |
| } else { |
| end = begin; |
| begin = 0; |
| } |
| String segment; |
| if (end == -1) { |
| segment = input.substring(begin); |
| input = ""; |
| } else { |
| segment = input.substring(begin, end); |
| input = input.substring(end); |
| } |
| output.append(segment); |
| printStep("2E", output.toString(), input); |
| } |
| } |
| |
| // 3. Finally, if the only or last segment of the output buffer is |
| // "..", where ".." is a complete path segment not followed by a slash |
| // then append a slash "/". The output buffer is returned as the result |
| // of remove_dot_segments |
| if (output.toString().endsWith("..")) { |
| output.append("/"); |
| printStep("3 ", output.toString(), input); |
| } |
| |
| return output.toString(); |
| } |
| |
| private static void printStep(String step, String output, String input) { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug(" " + step + ": " + output); |
| if (output.length() == 0) { |
| LOG.debug("\t\t\t\t" + input); |
| } else { |
| LOG.debug("\t\t\t" + input); |
| } |
| } |
| } |
| } |