blob: 8fa9179a5a0f36e1760798efc681f38ac1684cb3 [file] [log] [blame]
/**
* 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);
}
}
}
}