From b22625215bf018243582f268db1742086332eaaa Mon Sep 17 00:00:00 2001 From: Andy McCright Date: Fri, 18 Sep 2020 17:28:34 -0500 Subject: [PATCH] [CXF-8347] URI processing/caching/extending changes Includes: - URITemplate cache - Extension to allow extenders to wrap runnables from HTTPConduit - Numeric handling in URIs - URITemplate processes parameters - Add checking for form post when content-type includes charsets, etc. Signed-off-by: Andy McCright --- .../common/util/RunnableWrapperFactory.java | 32 ++ .../cxf/service/model/EndpointInfo.java | 29 +- .../apache/cxf/jaxrs/model/URITemplate.java | 104 +++- .../org/apache/cxf/jaxrs/utils/FormUtils.java | 3 +- .../org/apache/cxf/jaxrs/utils/HttpUtils.java | 119 +++- .../apache/cxf/jaxrs/utils/ResourceUtils.java | 163 ++++-- .../apache/cxf/jaxrs/utils/UriEncoder.java | 516 ++++++++++++++++++ .../apache/cxf/jaxrs/utils/FormUtilsTest.java | 15 + .../cxf/transport/http/HTTPConduit.java | 42 +- 9 files changed, 932 insertions(+), 91 deletions(-) create mode 100644 core/src/main/java/org/apache/cxf/common/util/RunnableWrapperFactory.java create mode 100644 rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/UriEncoder.java diff --git a/core/src/main/java/org/apache/cxf/common/util/RunnableWrapperFactory.java b/core/src/main/java/org/apache/cxf/common/util/RunnableWrapperFactory.java new file mode 100644 index 00000000000..6c4e09b460d --- /dev/null +++ b/core/src/main/java/org/apache/cxf/common/util/RunnableWrapperFactory.java @@ -0,0 +1,32 @@ +/** + * 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.cxf.common.util; + +import org.apache.cxf.message.Message; + +/** + * Intended to allow extenders to wrap a Runnable by extending this class. + * The default implementation simply returns the passed-in Runnable. + */ +public class RunnableWrapperFactory { + + public Runnable wrap(Runnable innerRunnable, Message m) { + return innerRunnable; + } +} diff --git a/core/src/main/java/org/apache/cxf/service/model/EndpointInfo.java b/core/src/main/java/org/apache/cxf/service/model/EndpointInfo.java index 90c054aad88..a2420b40d87 100644 --- a/core/src/main/java/org/apache/cxf/service/model/EndpointInfo.java +++ b/core/src/main/java/org/apache/cxf/service/model/EndpointInfo.java @@ -32,7 +32,11 @@ public class EndpointInfo extends AbstractDescriptionElement implements NamedIte ServiceInfo service; BindingInfo binding; QName name; - EndpointReferenceType address; + private volatile EndpointReferenceType lastAddressSet; // Keep last address set for when threadlocal val may be null + + // Store address in a theadLocal to avoid issue where redirected URL is mismatched when accessed from both + // IP address and machine name. + private final ThreadLocal threadLocal = new ThreadLocal(); public EndpointInfo() { } @@ -89,27 +93,38 @@ public void setBinding(BindingInfo b) { } public String getAddress() { + EndpointReferenceType address = threadLocal.get(); + if (address == null) { + address = lastAddressSet; + } return (null != address && null != address.getAddress()) ? address.getAddress().getValue() : null; } public void setAddress(String addr) { + EndpointReferenceType address = threadLocal.get(); if (null == address) { - // address = EndpointReferenceUtils.getEndpointReference(addr); loads jaxb and we want to avoid it final EndpointReferenceType reference = new EndpointReferenceType(); final AttributedURIType a = new AttributedURIType(); a.setValue(addr); reference.setAddress(a); address = reference; + threadLocal.set(address); } else { final AttributedURIType a = new AttributedURIType(); a.setValue(addr); address.setAddress(a); - // EndpointReferenceUtils.setAddress(address, addr); } + lastAddressSet = address; } public void setAddress(EndpointReferenceType endpointReference) { - address = endpointReference; + threadLocal.set(endpointReference); + lastAddressSet = endpointReference; + } + + //When finished the threadlocal must be cleared. + public void resetAddress() { + threadLocal.remove(); } @Override @@ -134,6 +149,10 @@ public T getTraversedExtensor(T defaultValue, Class type) { } public EndpointReferenceType getTarget() { + EndpointReferenceType address = threadLocal.get(); + if (address == null) { + address = lastAddressSet; + } return address; } @@ -154,4 +173,4 @@ public String toString() { + ", ServiceQName=" + (binding.getService() == null ? "" : binding.getService().getName()))) + ", QName=" + name; } -} +} \ No newline at end of file diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/model/URITemplate.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/model/URITemplate.java index d64640753b0..d426aa371cb 100644 --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/model/URITemplate.java +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/model/URITemplate.java @@ -43,13 +43,16 @@ public final class URITemplate { public static final String LIMITED_REGEX_SUFFIX = "(/.*)?"; public static final String FINAL_MATCH_GROUP = "FINAL_MATCH_GROUP"; private static final String DEFAULT_PATH_VARIABLE_REGEX = "([^/]+?)"; + private static final Pattern INTEGER_PATH_VARIABLE_REGEX_PATTERN = Pattern.compile("\\-?[0-9]+"); + private static final Pattern DECIMAL_PATH_VARIABLE_REGEX_PATTERN = + Pattern.compile("[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?"); private static final String CHARACTERS_TO_ESCAPE = ".*+$()"; private static final String SLASH = "/"; private static final String SLASH_QUOTE = "/;"; private static final int MAX_URI_TEMPLATE_CACHE_SIZE = SystemPropertyAction.getInteger("org.apache.cxf.jaxrs.max_uri_template_cache_size", 2000); private static final Map URI_TEMPLATE_CACHE = new ConcurrentHashMap<>(); - + private final String template; private final List variables = new ArrayList<>(); private final List customVariables = new ArrayList<>(); @@ -58,14 +61,18 @@ public final class URITemplate { private final List uriChunks; public URITemplate(String theTemplate) { - template = theTemplate; + this(theTemplate, Collections. emptyList()); + } + + public URITemplate(String theTemplate, List params) { + template = theTemplate == null ? "" : theTemplate; StringBuilder literalChars = new StringBuilder(); StringBuilder patternBuilder = new StringBuilder(); CurlyBraceTokenizer tok = new CurlyBraceTokenizer(template); uriChunks = new ArrayList<>(); while (tok.hasNext()) { String templatePart = tok.next(); - UriChunk chunk = UriChunk.createUriChunk(templatePart); + UriChunk chunk = UriChunk.createUriChunk(templatePart, params); uriChunks.add(chunk); if (chunk instanceof Literal) { String encodedValue = HttpUtils.encodePartiallyEncoded(chunk.getValue(), false); @@ -359,32 +366,65 @@ public String encodeLiteralCharacters(boolean isQuery) { return sb.toString(); } + public static URITemplate createTemplate(Path path, List params, String classNameandPath) { + return createTemplate(path == null ? null : path.value(), params, classNameandPath); + } + + public static URITemplate createTemplate(Path path, List params) { + return createTemplate(path == null ? null : path.value(), params); + } + public static URITemplate createTemplate(Path path) { + return createTemplate(path == null ? null : path.value(), Collections. emptyList()); + } - return createTemplate(path == null ? null : path.value()); + public static URITemplate createTemplate(Path path, String classNameandPath) { + return createTemplate(path == null ? null + : path.value(), Collections. emptyList(), classNameandPath); } public static URITemplate createTemplate(String pathValue) { + return createTemplate(pathValue, Collections. emptyList(), pathValue); + } + + public static URITemplate createTemplate(String pathValue, String classNameandPath) { + return createTemplate(pathValue, Collections. emptyList(), classNameandPath); + } + + public static URITemplate createTemplate(String pathValue, List params) { + return createExactTemplate(pathValue, params, pathValue); + } + + public static URITemplate createTemplate(String pathValue, List params, String classNameandPath) { if (pathValue == null) { pathValue = "/"; } else if (!pathValue.startsWith("/")) { pathValue = "/" + pathValue; } - return createExactTemplate(pathValue); + return createExactTemplate(pathValue, params, classNameandPath); } - + public static URITemplate createExactTemplate(String pathValue) { - URITemplate template = URI_TEMPLATE_CACHE.get(pathValue); + return createExactTemplate(pathValue, Collections. emptyList()); + } + + public static URITemplate createExactTemplate(String pathValue, List params) { + return createExactTemplate(pathValue, params, pathValue); + } + + public static URITemplate createExactTemplate(String pathValue, List params, String classNameandPath) { + classNameandPath = classNameandPath == null ? "" : classNameandPath; + URITemplate template = URI_TEMPLATE_CACHE.get(classNameandPath); if (template == null) { - template = new URITemplate(pathValue); + template = new URITemplate(pathValue, params); if (URI_TEMPLATE_CACHE.size() >= MAX_URI_TEMPLATE_CACHE_SIZE) { URI_TEMPLATE_CACHE.clear(); } - URI_TEMPLATE_CACHE.put(pathValue, template); + URI_TEMPLATE_CACHE.put(classNameandPath, template); } return template; } - + public static int compareTemplates(URITemplate t1, URITemplate t2) { int l1 = t1.getLiteralChars().length(); int l2 = t2.getLiteralChars().length(); @@ -422,11 +462,11 @@ private abstract static class UriChunk { * @return If param has variable form then {@link Variable} instance is created, otherwise chunk is * treated as {@link Literal}. */ - public static UriChunk createUriChunk(String uriChunk) { + public static UriChunk createUriChunk(String uriChunk, List params) { if (uriChunk == null || "".equals(uriChunk)) { throw new IllegalArgumentException("uriChunk is empty"); } - UriChunk uriChunkRepresentation = Variable.create(uriChunk); + UriChunk uriChunkRepresentation = Variable.create(uriChunk, params); if (uriChunkRepresentation == null) { uriChunkRepresentation = Literal.create(uriChunk); } @@ -482,8 +522,7 @@ private Variable() { * @param uriChunk uriChunk chunk that depicts variable * @return Variable if variable was successfully created; null if uriChunk was not a variable */ - public static Variable create(String uriChunk) { - Variable newVariable = new Variable(); + public static Variable create(String uriChunk, List params) { if (uriChunk == null || "".equals(uriChunk)) { return null; } @@ -491,10 +530,29 @@ public static Variable create(String uriChunk) { uriChunk = CurlyBraceTokenizer.stripBraces(uriChunk).trim(); Matcher matcher = VARIABLE_PATTERN.matcher(uriChunk); if (matcher.matches()) { + Variable newVariable = new Variable(); newVariable.name = matcher.group(1).trim(); if (matcher.group(2) != null && matcher.group(3) != null) { String patternExpression = matcher.group(3).trim(); newVariable.pattern = Pattern.compile(patternExpression); + } else { + //check parameter types + for (Parameter p : params) { + if (p.getName() != null && p.getName().equals(newVariable.name)) { + Class paramType = p.getJavaType(); + //CHECKSTYLE:OFF + if (paramType.isPrimitive()) { + if (int.class.equals(paramType) || short.class.equals(paramType) + || long.class.equals(paramType)) { + newVariable.pattern = INTEGER_PATH_VARIABLE_REGEX_PATTERN; + } else if (double.class.equals(paramType) || float.class.equals(paramType)) { + newVariable.pattern = DECIMAL_PATH_VARIABLE_REGEX_PATTERN; + } + } + //CHECKSTYLE:ON + break; + } + } } return newVariable; } @@ -548,7 +606,7 @@ public String getValue() { */ static class CurlyBraceTokenizer { - private List tokens = new ArrayList<>(); + private final List tokens = new ArrayList<>(); private int tokenIdx; CurlyBraceTokenizer(String string) { @@ -556,8 +614,10 @@ static class CurlyBraceTokenizer { int level = 0; int lastIdx = 0; int idx; - for (idx = 0; idx < string.length(); idx++) { - if (string.charAt(idx) == '{') { + int length = string.length(); + for (idx = 0; idx < length; idx++) { + char c = string.charAt(idx); + if (c == '{') { if (outside) { if (lastIdx < idx) { tokens.add(string.substring(lastIdx, idx)); @@ -567,12 +627,12 @@ static class CurlyBraceTokenizer { } else { level++; } - } else if (string.charAt(idx) == '}' && !outside) { + } else if (c == '}' && !outside) { if (level > 0) { level--; } else { if (lastIdx < idx) { - tokens.add(string.substring(lastIdx, idx + 1)); + tokens.add(lastIdx == 0 && idx + 1 == length ? string : string.substring(lastIdx, idx + 1)); } lastIdx = idx + 1; outside = true; @@ -580,7 +640,7 @@ static class CurlyBraceTokenizer { } } if (lastIdx < idx) { - tokens.add(string.substring(lastIdx, idx)); + tokens.add(lastIdx == 0 ? string : string.substring(lastIdx, idx)); } } @@ -621,6 +681,4 @@ public String next() { throw new IllegalStateException("no more elements"); } } -} - - +} \ No newline at end of file diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/FormUtils.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/FormUtils.java index 979b7035cd7..1f6318a686b 100644 --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/FormUtils.java +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/FormUtils.java @@ -285,7 +285,8 @@ private static void checkNumberOfParts(Message m, int numberOfParts) { } public static boolean isFormPostRequest(Message m) { - return MediaType.APPLICATION_FORM_URLENCODED.equals(m.get(Message.CONTENT_TYPE)) + String contentType = (String) m.get(Message.CONTENT_TYPE); + return (contentType != null && contentType.toLowerCase().startsWith(MediaType.APPLICATION_FORM_URLENCODED)) && HttpMethod.POST.equals(m.get(Message.HTTP_REQUEST_METHOD)); } } diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/HttpUtils.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/HttpUtils.java index 2aa164e2cf3..3fd8205e898 100644 --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/HttpUtils.java +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/HttpUtils.java @@ -21,7 +21,6 @@ import java.io.UnsupportedEncodingException; import java.net.URI; -import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -467,18 +466,95 @@ public static String getProtocolHeader(Message m, String name, String defaultVal public static String getBaseAddress(Message m) { String endpointAddress = getEndpointAddress(m); - try { - URI uri = new URI(endpointAddress); - String path = uri.getRawPath(); - String scheme = uri.getScheme(); - if (scheme != null && !scheme.startsWith(HttpUtils.HTTP_SCHEME) + + String[] addr = parseURI(endpointAddress, false); + if (addr == null) { + return endpointAddress; + } + String scheme = addr[0]; + String path = addr[1]; + if (scheme != null && !scheme.startsWith(HttpUtils.HTTP_SCHEME) && HttpUtils.isHttpRequest(m)) { - path = HttpUtils.toAbsoluteUri(path, m).getRawPath(); + path = HttpUtils.toAbsoluteUri(path, m).getRawPath(); + } + return (path == null || path.length() == 0) ? "/" : path; + } + + public static String[] parseURI(String addr, boolean parseAuthority) { + String scheme = null; + String parsedAuthorityOrRawPath = null; + int n = addr.length(); + int p = scan(addr, 0, n, "/?#", ":"); + try { + if ((p >= 0) && at(addr, p, n, ':')) { + if (p == 0) { + return null; + } + scheme = addr.substring(0, p); + p++; + if (at(addr, p, n, '/')) { + parsedAuthorityOrRawPath = postSchemeParse(addr, p, n, parseAuthority); + } else { + int q = scan(addr, p, n, null, "#"); + if (q <= p) { + return null; + } + } + } else { + parsedAuthorityOrRawPath = postSchemeParse(addr, 0, n, parseAuthority); } - return (path == null || path.length() == 0) ? "/" : path; - } catch (URISyntaxException ex) { - return endpointAddress; + } catch (IllegalArgumentException e) { + return null; + } + return new String[] {scheme, parsedAuthorityOrRawPath }; + } + + private static String postSchemeParse(String addr, int start, int n, boolean parseAuthority) { + int p = start; + int q = -1; + if (at(addr, p, n, '/') && at(addr, p + 1, n, '/')) { + p += 2; + q = scan(addr, p, n, null, "/"); + if (q > p) { + if (!parseAuthority) { + p = q; + } + } else if (q >= n) { + if (parseAuthority || p == n) { + throw new IllegalArgumentException(); + } else { + return null; + } + } + } + + if (!parseAuthority) { + q = scan(addr, p, n, null, "?#"); + } else if (p >= q) { + // empty authority should be null + return null; } + + return addr.substring(p, q); + } + + private static boolean at(String addr, int start, int end, char c) { + return (start < end) && (addr.charAt(start) == c); + } + + private static int scan(String addr, int start, int end, String err, String stop) { + int p = start; + while (p < end) { + char c = addr.charAt(p); + if (err != null && err.indexOf(c) >= 0) { + return -1; + } + if (stop.indexOf(c) >= 0) { + break; + } + p++; + } + return p; } public static String getEndpointAddress(Message m) { @@ -492,11 +568,11 @@ public static String getEndpointAddress(Message m) { ? request.getAttribute("org.apache.cxf.transport.endpoint.address") : null; address = property != null ? property.toString() : ei.getAddress(); } else { - address = m.containsKey(Message.BASE_PATH) - ? (String)m.get(Message.BASE_PATH) : d.getAddress().getAddress().getValue(); + String basePath = (String) m.get(Message.BASE_PATH); + address = (basePath != null) ? basePath : d.getAddress().getAddress().getValue(); } } else { - address = (String)m.get(Message.ENDPOINT_ADDRESS); + address = (String) m.get(Message.ENDPOINT_ADDRESS); } if (address.startsWith("http") && address.endsWith("//")) { address = address.substring(0, address.length() - 1); @@ -521,6 +597,8 @@ public static void updatePath(Message m, String path) { public static String getPathToMatch(String path, String address, boolean addSlash) { + String origPath = path; + int ind = path.indexOf(address); if (ind == -1 && address.equals(path + "/")) { path += "/"; @@ -533,6 +611,21 @@ public static String getPathToMatch(String path, String address, boolean addSlas path = "/" + path; } + if (path.equals(origPath) && origPath.contains("%")) { + address = UriEncoder.encodeString(address).replace("%2F", "/"); + path = origPath; + ind = path.indexOf(address); + if (ind == -1 && address.equals(path + "/")) { + path += "/"; + ind = 0; + } + if (ind == 0) { + path = path.substring(address.length()); + } + if (addSlash && !path.startsWith("/")) { + path = "/" + path; + } + } return path; } diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java index 10119d76f26..a30959faf0d 100644 --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java @@ -29,10 +29,13 @@ import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; +import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.security.AccessController; import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -78,6 +81,7 @@ import org.apache.cxf.common.classloader.ClassLoaderUtils; import org.apache.cxf.common.i18n.BundleUtils; import org.apache.cxf.common.logging.LogUtils; +import org.apache.cxf.common.util.ReflectionUtil; import org.apache.cxf.common.util.StringUtils; import org.apache.cxf.feature.Feature; import org.apache.cxf.helpers.CastUtils; @@ -144,27 +148,28 @@ public Method[] run() { public static Method findPostConstructMethod(Class c) { return findPostConstructMethod(c, null); } - public static Method findPostConstructMethod(Class c, String name) { + + public static Method findPostConstructMethod(final Class c, String name) { if (Object.class == c || null == c) { return null; } for (Method m : getDeclaredMethods(c)) { if (name != null) { if (m.getName().equals(name)) { - return m; + return ReflectionUtil.setAccessible(m); } } else if (m.getAnnotation(PostConstruct.class) != null) { - return m; + return ReflectionUtil.setAccessible(m); } } Method m = findPostConstructMethod(c.getSuperclass(), name); if (m != null) { - return m; + return ReflectionUtil.setAccessible(m); } for (Class i : c.getInterfaces()) { m = findPostConstructMethod(i, name); if (m != null) { - return m; + return ReflectionUtil.setAccessible(m); } } return null; @@ -181,20 +186,20 @@ public static Method findPreDestroyMethod(Class c, String name) { for (Method m : getDeclaredMethods(c)) { if (name != null) { if (m.getName().equals(name)) { - return m; + return ReflectionUtil.setAccessible(m); } } else if (m.getAnnotation(PreDestroy.class) != null) { - return m; + return ReflectionUtil.setAccessible(m); } } Method m = findPreDestroyMethod(c.getSuperclass(), name); if (m != null) { - return m; + return ReflectionUtil.setAccessible(m); } for (Class i : c.getInterfaces()) { m = findPreDestroyMethod(i, name); if (m != null) { - return m; + return ReflectionUtil.setAccessible(m); } } return null; @@ -206,7 +211,7 @@ public static ClassResourceInfo createClassResourceInfo( boolean isRoot, boolean enableStatic, Bus bus) { final boolean isDefaultClass = defaultClass != null; - Class sClass = !isDefaultClass ? loadClass(model.getName()) : defaultClass; + Class sClass = !isDefaultClass ? loadClass(model.getName()) : defaultClass; return createServiceClassResourceInfo(resources, model, sClass, isRoot, enableStatic, bus); } @@ -216,10 +221,12 @@ public static ClassResourceInfo createServiceClassResourceInfo( if (model == null) { throw new RuntimeException("Resource class " + sClass.getName() + " has no model info"); } + ClassResourceInfo cri = new ClassResourceInfo(sClass, sClass, isRoot, enableStatic, true, model.getConsumes(), model.getProduces(), bus); - URITemplate t = URITemplate.createTemplate(model.getPath()); + String classNameandPath = getClassNameandPath(cri.getResourceClass().getName(), model.getPath()); + URITemplate t = URITemplate.createTemplate(model.getPath(), classNameandPath); cri.setURITemplate(t); MethodDispatcher md = new MethodDispatcher(); @@ -244,8 +251,10 @@ public static ClassResourceInfo createServiceClassResourceInfo( if (actualMethod == null) { continue; } + String classNameandPath2 = getClassNameandPath(cri.getResourceClass().getName(), op.getPath()); + URITemplate t2 = URITemplate.createTemplate(op.getPath(), classNameandPath2); OperationResourceInfo ori = - new OperationResourceInfo(actualMethod, cri, URITemplate.createTemplate(op.getPath()), + new OperationResourceInfo(actualMethod, cri, t2, op.getVerb(), op.getConsumes(), op.getProduces(), op.getParameters(), op.isOneway()); @@ -310,7 +319,8 @@ public static ClassResourceInfo createClassResourceInfo(final Class rClass, cri.setParent(parent); if (root) { - URITemplate t = URITemplate.createTemplate(cri.getPath()); + String classNameandPath = getClassNameandPath(cri.getResourceClass().getName(), cri.getPath()); + URITemplate t = URITemplate.createTemplate(cri.getPath(), classNameandPath); cri.setURITemplate(t); } @@ -400,11 +410,10 @@ private static boolean checkAsyncResponse(Method m) { reportInvalidResourceMethod(m, NOT_SUSPENDED_ASYNC_MESSAGE_ID, Level.FINE); return false; } - if (m.getReturnType() == Void.TYPE || m.getReturnType() == Void.class) { - return true; + if (m.getReturnType() != Void.TYPE && m.getReturnType() != Void.class) { + reportInvalidResourceMethod(m, NO_VOID_RETURN_ASYNC_MESSAGE_ID, Level.WARNING); } - reportInvalidResourceMethod(m, NO_VOID_RETURN_ASYNC_MESSAGE_ID, Level.WARNING); - return false; + return true; } } return true; @@ -423,16 +432,21 @@ private static ClassResourceInfo getAncestorWithSameServiceClass(ClassResourceIn public static Constructor findResourceConstructor(Class resourceClass, boolean perRequest) { List> cs = new LinkedList<>(); for (Constructor c : resourceClass.getConstructors()) { + Annotation[] anna = c.getDeclaredAnnotations(); + boolean hasInject = hasInjectAnnotation(anna); Class[] params = c.getParameterTypes(); Annotation[][] anns = c.getParameterAnnotations(); boolean match = true; for (int i = 0; i < params.length; i++) { if (!perRequest) { - if (AnnotationUtils.getAnnotation(anns[i], Context.class) == null) { + //annotation is not null and not equals context + if (AnnotationUtils.getAnnotation(anns[i], Context.class) == null + && !isInjectionPara(hasInject, anns[i])) { match = false; break; } - } else if (!AnnotationUtils.isValidParamAnnotations(anns[i])) { + } else if ((!AnnotationUtils.isValidParamAnnotations(anns[i])) + && !isInjectionPara(hasInject, anns[i])) { match = false; break; } @@ -443,6 +457,7 @@ public static Constructor findResourceConstructor(Class resourceClass, boo } Collections.sort(cs, new Comparator>() { + @Override public int compare(Constructor c1, Constructor c2) { int p1 = c1.getParameterTypes().length; int p2 = c2.getParameterTypes().length; @@ -453,6 +468,30 @@ public int compare(Constructor c1, Constructor c2) { return cs.isEmpty() ? null : cs.get(0); } + /** + * @param hasInject + * @param anns + * @param i + * @return + */ + private static boolean isInjectionPara(boolean hasInject, Annotation[] anns) { + return anns.length == 0 && hasInject; + } + + /** + * @param anna + * @return + */ + private static boolean hasInjectAnnotation(Annotation[] anna) { + for (Annotation a : anna) { + String annotationName = a.annotationType().getCanonicalName(); + if ("javax.inject.Inject".equalsIgnoreCase(annotationName)) { + return true; + } + } + return false; + } + public static List getParameters(Method resourceMethod) { Annotation[][] paramAnns = resourceMethod.getParameterAnnotations(); if (paramAnns.length == 0) { @@ -486,7 +525,9 @@ public static Parameter getParameter(int index, Annotation[] anns, Class type PathParam a = AnnotationUtils.getAnnotation(anns, PathParam.class); if (a != null) { - return new Parameter(ParameterType.PATH, index, a.value(), isEncoded, dValue); + Parameter p = new Parameter(ParameterType.PATH, index, a.value(), isEncoded, dValue); + p.setJavaType(type); + return p; } QueryParam q = AnnotationUtils.getAnnotation(anns, QueryParam.class); if (q != null) { @@ -518,14 +559,39 @@ public static Parameter getParameter(int index, Annotation[] anns, Class type //CHECKSTYLE:ON private static OperationResourceInfo createOperationInfo(Method m, Method annotatedMethod, - ClassResourceInfo cri, Path path, String httpMethod) { + ClassResourceInfo cri, Path path, String httpMethod) { OperationResourceInfo ori = new OperationResourceInfo(m, annotatedMethod, cri); - URITemplate t = URITemplate.createTemplate(path); + String classNameandPath = getClassNameandPath(cri.getResourceClass().getName(), path); + URITemplate t = URITemplate.createTemplate(path, ori.getParameters(), classNameandPath); ori.setURITemplate(t); ori.setHttpMethod(httpMethod); return ori; } + private static String getClassNameandPath(String className, Path path) { + if (path == null) { + return getClassNameandPath(className, "/"); + } else { + return getClassNameandPath(className, path.value()); + } + } + + private static String getClassNameandPath(String className, String pathValue) { + int pathLength = pathValue == null ? 0 : pathValue.length(); + StringBuilder sb = new StringBuilder(className.length() + pathLength + 1); + sb.append('/').append(className); + + if (pathLength == 0) { + sb.append('/'); + } else { + if (pathValue.charAt(0) != '/') { + sb.append('/'); + } + sb.append(pathValue); + } + + return sb.toString(); + } private static boolean checkMethodDispatcher(ClassResourceInfo cr) { if (cr.getMethodDispatcher().getOperationResourceInfos().isEmpty()) { @@ -537,7 +603,6 @@ private static boolean checkMethodDispatcher(ClassResourceInfo cr) { return true; } - private static Class loadClass(String cName) { try { return ClassLoaderUtils.loadClass(cName.trim(), ResourceUtils.class); @@ -546,7 +611,6 @@ private static Class loadClass(String cName) { } } - public static List getUserResources(String loc, Bus bus) { try (InputStream is = ResourceUtils.getResourceStream(loc, bus)) { if (is == null) { @@ -565,24 +629,41 @@ public static InputStream getResourceStream(String loc, Bus bus) throws IOExcept return url == null ? null : url.openStream(); } - public static URL getResourceURL(String loc, Bus bus) throws IOException { + public static URL getResourceURL(final String loc, final Bus bus) throws IOException { URL url = null; if (loc.startsWith(CLASSPATH_PREFIX)) { String path = loc.substring(CLASSPATH_PREFIX.length()); url = ResourceUtils.getClasspathResourceURL(path, ResourceUtils.class, bus); } else { try { - url = new URL(loc); - } catch (Exception ex) { - // it can be either a classpath or file resource without a scheme - url = ResourceUtils.getClasspathResourceURL(loc, ResourceUtils.class, bus); - if (url == null) { - File file = new File(loc); - if (file.exists()) { - url = file.toURI().toURL(); + url = AccessController.doPrivileged(new PrivilegedExceptionAction() { + + @Override + public URL run() throws MalformedURLException { + URL url; + try { + url = new URL(loc); + } catch (Exception ex) { + // it can be either a classpath or file resource without a scheme + url = ResourceUtils.getClasspathResourceURL(loc, ResourceUtils.class, bus); + if (url == null) { + File file = new File(loc); + if (file.exists()) { + url = file.toURI().toURL(); + } + } + } + return url; } + }); + } catch (PrivilegedActionException pae) { + Exception ex = pae.getException(); + if (ex instanceof IOException) { + throw (IOException) ex; } + throw new IOException(ex); } + } if (url == null) { LOG.warning("No resource " + loc + " is available"); @@ -638,7 +719,6 @@ public static List getResourcesFromElement(Element modelEl) { return resources; } - public static ResourceTypes getAllRequestResponseTypes(List cris, boolean jaxbOnly) { return getAllRequestResponseTypes(cris, jaxbOnly, null); @@ -656,7 +736,7 @@ public static ResourceTypes getAllRequestResponseTypes(List c public static Class getActualJaxbType(Class type, Method resourceMethod, boolean inbound) { ElementClass element = resourceMethod.getAnnotation(ElementClass.class); - if (element != null) { + if (element != null) { Class cls = inbound ? element.request() : element.response(); if (cls != Object.class) { return cls; @@ -740,8 +820,8 @@ private static void checkJaxbType(Class serviceClass, if (type == Object.class && !(genericType instanceof Class) || genericType instanceof TypeVariable) { Type theType = InjectionUtils.processGenericTypeIfNeeded(serviceClass, - Object.class, - genericType); + Object.class, + genericType); type = InjectionUtils.getActualType(theType); } if (type == null @@ -785,7 +865,7 @@ private static UserResource getResourceFromElement(Element e) { resource.setProduces(e.getAttribute("produces")); List operEls = DOMUtils.findAllElementsByTagNameNS(e, - "http://cxf.apache.org/jaxrs", "operation"); + "http://cxf.apache.org/jaxrs", "operation"); List opers = new ArrayList<>(operEls.size()); for (Element operEl : operEls) { opers.add(getOperationFromElement(operEl)); @@ -804,7 +884,7 @@ private static UserOperation getOperationFromElement(Element e) { op.setProduces(e.getAttribute("produces")); List paramEls = DOMUtils.findAllElementsByTagNameNS(e, - "http://cxf.apache.org/jaxrs", "param"); + "http://cxf.apache.org/jaxrs", "param"); List params = new ArrayList<>(paramEls.size()); for (int i = 0; i < paramEls.size(); i++) { Element paramEl = paramEls.get(i); @@ -873,7 +953,7 @@ public static Object[] createConstructorArguments(Constructor c, // is guaranteed to have only Context parameters, if any, for singletons Parameter p = ResourceUtils.getParameter(i, anns[i], params[i]); values[i] = JAXRSUtils.createHttpParameterValue( - p, params[i], genericTypes[i], anns[i], m, templateValues, null); + p, params[i], genericTypes[i], anns[i], m, templateValues, null); } } return values; @@ -1040,5 +1120,4 @@ private static boolean isValidApplicationClass(Class c, Set singleton } return true; } - -} +} \ No newline at end of file diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/UriEncoder.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/UriEncoder.java new file mode 100644 index 00000000000..27738c59863 --- /dev/null +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/UriEncoder.java @@ -0,0 +1,516 @@ +/** + * 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.cxf.jaxrs.utils; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.Arrays; + +/** + * URI Encoding and Decoding + */ +public final class UriEncoder { + + private static final Charset CHARSET_UTF_8 = Charset.forName("UTF-8"); //$NON-NLS-1$ + + /** Hexadecimal digits for escaping. */ + private static final char[] HEX_DIGITS = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + private static final byte[] NORMALIZED_HEX_DIGITS = new byte[128]; + + private static final boolean[] IS_HEX_DIGIT = new boolean[128]; + + /** + * Unreserved characters according to RFC 3986. Each character below ASCII + * 128 has single array item with true if it is unreserved and false if it + * is reserved. + */ + private static final boolean[] UNRESERVED_CHARS = new boolean[128]; + private static final boolean[] USER_INFO_CHARS = new boolean[128]; + private static final boolean[] SEGMENT_CHARS = new boolean[128]; + private static final boolean[] MATRIX_CHARS = new boolean[128]; + private static final boolean[] PATH_CHARS = new boolean[128]; + private static final boolean[] QUERY_CHARS = new boolean[128]; + private static final boolean[] QUERY_PARAM_CHARS = new boolean[128]; + private static final boolean[] FRAGMENT_CHARS = new boolean[128]; + private static final boolean[] URI_CHARS = new boolean[128]; + private static final boolean[] URI_TEMPLATE_CHARS = new boolean[128]; + + static { + // unreserved - ALPHA / DIGIT / "-" / "." / "_" / "~" + Arrays.fill(UNRESERVED_CHARS, false); + Arrays.fill(UNRESERVED_CHARS, 'a', 'z' + 1, true); + Arrays.fill(UNRESERVED_CHARS, 'A', 'Z' + 1, true); + Arrays.fill(UNRESERVED_CHARS, '0', '9' + 1, true); + UNRESERVED_CHARS['-'] = true; + UNRESERVED_CHARS['_'] = true; + UNRESERVED_CHARS['.'] = true; + UNRESERVED_CHARS['~'] = true; + + // sub delimiters - "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," + // / ";" / "=" + // user info chars - *( unreserved / pct-encoded / sub-delims / ":" ) + System.arraycopy(UNRESERVED_CHARS, 0, USER_INFO_CHARS, 0, 128); + USER_INFO_CHARS['!'] = true; + USER_INFO_CHARS['$'] = true; + USER_INFO_CHARS['&'] = true; + USER_INFO_CHARS['\''] = true; + USER_INFO_CHARS['('] = true; + USER_INFO_CHARS[')'] = true; + USER_INFO_CHARS['*'] = true; + USER_INFO_CHARS['+'] = true; + USER_INFO_CHARS[','] = true; + USER_INFO_CHARS[';'] = true; + USER_INFO_CHARS['='] = true; + USER_INFO_CHARS[':'] = true; + + // segment - *(unreserved / pct-encoded / sub-delims / ":" / "@") + System.arraycopy(USER_INFO_CHARS, 0, SEGMENT_CHARS, 0, 128); + SEGMENT_CHARS['@'] = true; + + // matrix - *(unreserved / pct-encoded / sub-delims / ":" / "@") without + // "=" and ";" + System.arraycopy(SEGMENT_CHARS, 0, MATRIX_CHARS, 0, 128); + MATRIX_CHARS['='] = false; + MATRIX_CHARS[';'] = false; + + // path - *(unreserved / pct-encoded / sub-delims / ":" / "@" / "/") + System.arraycopy(SEGMENT_CHARS, 0, PATH_CHARS, 0, 128); + PATH_CHARS['/'] = true; + + // query - *(unreserved / pct-encoded / sub-delims / ":" / "@" / "/" / + // "?") + System.arraycopy(PATH_CHARS, 0, QUERY_CHARS, 0, 128); + QUERY_CHARS['?'] = true; + + // fragment - *(unreserved / pct-encoded / sub-delims / ":" / "@" / "/" + // / "?") + System.arraycopy(QUERY_CHARS, 0, FRAGMENT_CHARS, 0, 128); + + // query param - *(unreserved / pct-encoded / sub-delims / ":" / "@" / + // "/" / "?") without + // "&" and "=" + System.arraycopy(QUERY_CHARS, 0, QUERY_PARAM_CHARS, 0, 128); + QUERY_PARAM_CHARS['&'] = false; + QUERY_PARAM_CHARS['='] = false; + + // uri - *(unreserved / pct-encoded / sub-delims / ":" / "@" / "/" / "?" + // / "#" / "[" / "]" ) + System.arraycopy(QUERY_CHARS, 0, URI_CHARS, 0, 128); + URI_CHARS['#'] = true; + URI_CHARS['['] = true; + URI_CHARS[']'] = true; + + // uri template - *(unreserved / pct-encoded / sub-delims / ":" / "@" / + // "/" / "?" / "#" / + // "[" / "]" / "{" / "}" ) + System.arraycopy(URI_CHARS, 0, URI_TEMPLATE_CHARS, 0, 128); + URI_TEMPLATE_CHARS['{'] = true; + URI_TEMPLATE_CHARS['}'] = true; + + // fill the isHex array + Arrays.fill(IS_HEX_DIGIT, false); + Arrays.fill(IS_HEX_DIGIT, '0', '9' + 1, true); + Arrays.fill(IS_HEX_DIGIT, 'a', 'f' + 1, true); + Arrays.fill(IS_HEX_DIGIT, 'A', 'F' + 1, true); + + // fill the NORMALIZED_HEX_DIGITS array + NORMALIZED_HEX_DIGITS['0'] = '0'; + NORMALIZED_HEX_DIGITS['1'] = '1'; + NORMALIZED_HEX_DIGITS['2'] = '2'; + NORMALIZED_HEX_DIGITS['3'] = '3'; + NORMALIZED_HEX_DIGITS['4'] = '4'; + NORMALIZED_HEX_DIGITS['5'] = '5'; + NORMALIZED_HEX_DIGITS['6'] = '6'; + NORMALIZED_HEX_DIGITS['7'] = '7'; + NORMALIZED_HEX_DIGITS['8'] = '8'; + NORMALIZED_HEX_DIGITS['9'] = '9'; + NORMALIZED_HEX_DIGITS['A'] = 'A'; + NORMALIZED_HEX_DIGITS['B'] = 'B'; + NORMALIZED_HEX_DIGITS['C'] = 'C'; + NORMALIZED_HEX_DIGITS['D'] = 'D'; + NORMALIZED_HEX_DIGITS['E'] = 'E'; + NORMALIZED_HEX_DIGITS['F'] = 'F'; + NORMALIZED_HEX_DIGITS['a'] = 'A'; + NORMALIZED_HEX_DIGITS['b'] = 'B'; + NORMALIZED_HEX_DIGITS['c'] = 'C'; + NORMALIZED_HEX_DIGITS['d'] = 'D'; + NORMALIZED_HEX_DIGITS['e'] = 'E'; + NORMALIZED_HEX_DIGITS['f'] = 'F'; + } + + private UriEncoder() { + // no instances + } + + private static int decodeHexDigit(char c) { + // Decode single hexadecimal digit. On error returns 0 (ignores errors). + if (c >= '0' && c <= '9') { + return c - '0'; + } else if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } else { + return 0; + } + } + + /** + * Encode all characters other than unreserved according to RFC 3986. + * + * @param string string to encode + * @return encoded US-ASCII string + */ + public static String encodeString(String string) { + return encode(string, false, UNRESERVED_CHARS); + } + + /** + * Encode user info according to RFC 3986. + * + * @param userInfo the user info to encode + * @param relax if true, then any sequence of chars in the input string that + * have the form '%XX', where XX are two HEX digits, will not be + * encoded + * @return encoded user info string + */ + public static String encodeUserInfo(String userInfo, boolean relax) { + return encode(userInfo, relax, USER_INFO_CHARS); + } + + /** + * Encode a path segment (without matrix parameters) according to RFC 3986. + * + * @param segment the segment (without matrix parameters) to encode + * @param relax if true, then any sequence of chars in the input string that + * have the form '%XX', where XX are two HEX digits, will not be + * encoded + * @return encoded segment string + */ + public static String encodePathSegment(String segment, boolean relax) { + return encode(segment, relax, SEGMENT_CHARS); + } + + /** + * Encode a matrix parameter (name or value) according to RFC 3986. + * + * @param matrix the matrix parameter (name or value) to encode + * @param relax if true, then any sequence of chars in the input string that + * have the form '%XX', where XX are two HEX digits, will not be + * encoded + * @return encoded matrix string + */ + public static String encodeMatrix(String matrix, boolean relax) { + return encode(matrix, relax, MATRIX_CHARS); + } + + /** + * Encode a complete path string according to RFC 3986. + * + * @param path the path string to encode + * @param relax if true, then any sequence of chars in the input string that + * have the form '%XX', where XX are two HEX digits, will not be + * encoded + * @return encoded path string + */ + public static String encodePath(String path, boolean relax) { + return encode(path, relax, PATH_CHARS); + } + + /** + * Encode a query parameter (name or value) according to RFC 3986. + * + * @param queryParam the query parameter string to encode + * @param relax if true, then any sequence of chars in the input string that + * have the form '%XX', where XX are two HEX digits, will not be + * encoded + * @return encoded query parameter string + */ + public static String encodeQueryParam(String queryParam, boolean relax) { + boolean[] unreserved = QUERY_PARAM_CHARS; + String string = queryParam; + + if (queryParam == null) { + return null; + } + + if (!needsEncoding(queryParam, false, unreserved)) { + return string; + } + + // Encode to UTF-8 + ByteBuffer buffer = CHARSET_UTF_8.encode(string); + // Prepare string buffer + StringBuilder sb = new StringBuilder(buffer.remaining()); + // Now encode the characters + while (buffer.hasRemaining()) { + int c = buffer.get(); + + if ((c == '%') && relax && (buffer.remaining() >= 2)) { + int position = buffer.position(); + if (isHex(buffer.get(position)) && isHex(buffer.get(position + 1))) { + sb.append((char)c); + continue; + } + } + + if (c >= ' ' && unreserved[c]) { + sb.append((char)c); + } else if (c == ' ') { + sb.append('+'); + } else { + sb.append('%'); + sb.append(HEX_DIGITS[(c & 0xf0) >> 4]); + sb.append(HEX_DIGITS[c & 0xf]); + } + } + + return sb.toString(); + } + + /** + * Encode a complete query string according to RFC 3986. + * + * @param query the query string to encode + * @param relax if true, then any sequence of chars in the input string that + * have the form '%XX', where XX are two HEX digits, will not be + * encoded + * @return encoded query string + */ + public static String encodeQuery(String query, boolean relax) { + return encode(query, relax, QUERY_CHARS); + } + + /** + * Encode a fragment string according to RFC 3986. + * + * @param fragment the fragment string to encode + * @param relax if true, then any sequence of chars in the input string that + * have the form '%XX', where XX are two HEX digits, will not be + * encoded + * @return encoded fragment string + */ + public static String encodeFragment(String fragment, boolean relax) { + return encode(fragment, relax, FRAGMENT_CHARS); + } + + /** + * Encode a uri according to RFC 3986, escaping all + * reserved characters. + * + * @param uri string to encode + * @param relax if true, then any sequence of chars in the input of the form + * '%XX', where XX are two HEX digits, will not be encoded. + * @return encoded US-ASCII string + */ + public static String encodeUri(String uri, boolean relax) { + return encode(uri, relax, URI_CHARS); + } + + /** + * Encode a uri template according to RFC 3986, escaping all + * reserved characters, except for '{' and '}'. + * + * @param uriTemplate template to encode + * @param relax if true, then any sequence of chars in the input of the form + * '%XX', where XX are two HEX digits, will not be encoded. + * @return encoded US-ASCII string + */ + public static String encodeUriTemplate(String uriTemplate, boolean relax) { + return encode(uriTemplate, relax, URI_TEMPLATE_CHARS); + } + + /** + * Encode a string according to RFC 3986, escaping all + * characters where unreserved[char] == false, where + * char is a single character such as 'a'. + * + * @param string string to encode + * @param relax if true, then any sequence of chars in the input string that + * have the form '%XX', where XX are two HEX digits, will not be + * encoded. + * @param unreserved an array of booleans that indicates which characters + * are considered unreserved. a character is considered + * unreserved if unreserved[char] == true, in which + * case it will not be encoded + * @return encoded US-ASCII string + */ + private static String encode(String string, boolean relax, boolean[] unreserved) { + if (string == null) { + return null; + } + + if (!needsEncoding(string, false, unreserved)) { + return string; + } + + // Encode to UTF-8 + ByteBuffer buffer = CHARSET_UTF_8.encode(string); + // Prepare string buffer + StringBuilder sb = new StringBuilder(buffer.remaining()); + // Now encode the characters + while (buffer.hasRemaining()) { + int c = buffer.get(); + + if ((c == '%') && relax && (buffer.remaining() >= 2)) { + int position = buffer.position(); + if (isHex(buffer.get(position)) && isHex(buffer.get(position + 1))) { + sb.append((char)c); + continue; + } + } + + if (c >= ' ' && unreserved[c]) { + sb.append((char)c); + } else { + sb.append('%'); + sb.append(HEX_DIGITS[(c & 0xf0) >> 4]); + sb.append(HEX_DIGITS[c & 0xf]); + } + } + + return sb.toString(); + } + + private static boolean isHex(int c) { + return IS_HEX_DIGIT[c]; + } + + /** + * Determines if the input string contains any invalid URI characters that + * require encoding + * + * @param uri the string to test + * @return true if the the input string contains only valid URI characters + */ + private static boolean needsEncoding(String s, boolean relax, boolean[] unreserved) { + int len = s.length(); + for (int i = 0; i < len; ++i) { + char c = s.charAt(i); + if (c == '%' && relax) { + continue; + } + if (c > unreserved.length || !unreserved[c]) { + return true; + } + } + return false; + } + + /** + * Decode US-ASCII uri according to RFC 3986 and replaces all + * occurrences of the '+' sign with spaces. + * + * @param string query string to decode + * @return decoded query + */ + public static String decodeQuery(String string) { + return decodeString(string, true, null); + } + + /** + * Decode US-ASCII uri according to RFC 3986. + * + * @param string US-ASCII uri to decode + * @return decoded uri + */ + public static String decodeString(String string) { + return decodeString(string, false, null); + } + + /** + * Decodes only the unreserved chars, according to RFC 3986 section 6.2.2.2 + * + * @param string US-ASCII uri to decode + * @return decoded uri + */ + public static String normalize(String string) { + return decodeString(string, false, UNRESERVED_CHARS); + } + + private static String decodeString(String string, boolean query, boolean[] decodeChars) { + if (string == null) { + return null; + } + + if (!needsDecoding(string, query)) { + return string; + } + + int len = string.length(); + // Prepare byte buffer + ByteBuffer buffer = ByteBuffer.allocate(len); + // decode string into byte buffer + for (int i = 0; i < len; ++i) { + char c = string.charAt(i); + if (c == '%' && (i + 2 < len)) { + int v = 0; + int d1 = decodeHexDigit(string.charAt(i + 1)); + int d2 = decodeHexDigit(string.charAt(i + 2)); + if (d1 >= 0 && d2 >= 0) { + v = d1; + v = v << 4 | d2; + if (decodeChars != null && (v >= decodeChars.length || !decodeChars[v])) { + buffer.put((byte)string.charAt(i)); + buffer.put(NORMALIZED_HEX_DIGITS[string.charAt(i + 1)]); + buffer.put(NORMALIZED_HEX_DIGITS[string.charAt(i + 2)]); + } else { + buffer.put((byte)v); + } + i += 2; + } else { + buffer.put((byte)c); + } + } else { + if (query && c == '+') { + c = ' '; + } + buffer.put((byte)c); + } + } + // Decode byte buffer from UTF-8 + buffer.flip(); + return CHARSET_UTF_8.decode(buffer).toString(); + } + + private static boolean needsDecoding(String s, boolean query) { + boolean needs = s.indexOf('%') != -1; + if (!needs && query) { + needs = s.indexOf('+') != -1; + } + return needs; + } +} diff --git a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/FormUtilsTest.java b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/FormUtilsTest.java index d712cbff596..c1db012c4ee 100644 --- a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/FormUtilsTest.java +++ b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/FormUtilsTest.java @@ -25,15 +25,19 @@ import java.util.Enumeration; import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.HttpMethod; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import org.apache.cxf.jaxrs.impl.MetadataMap; import org.apache.cxf.message.Message; +import org.apache.cxf.message.MessageImpl; import org.easymock.EasyMock; import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class FormUtilsTest { @@ -91,6 +95,17 @@ public void populateMapFromStringFromBody() { assertEquals(FORM_PARAM_VALUE2, params.get(FORM_PARAM2).iterator().next()); } + @Test + public void testIsFormPostRequest() { + Message m = new MessageImpl(); + m.put(Message.HTTP_REQUEST_METHOD, HttpMethod.POST); + m.put(Message.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED); + assertTrue(FormUtils.isFormPostRequest(m)); + + m.put(Message.CONTENT_TYPE, + MediaType.APPLICATION_FORM_URLENCODED_TYPE.withCharset(StandardCharsets.UTF_16BE.name()).toString()); + assertTrue(FormUtils.isFormPostRequest(m)); + } private void mockObjects(String formPropertyValue) { mockMessage = EasyMock.createMock(Message.class); diff --git a/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java b/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java index c2d104e9831..722857e7d80 100644 --- a/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java +++ b/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java @@ -48,6 +48,7 @@ import org.apache.cxf.common.injection.NoJSR250Annotations; import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.common.util.PropertyUtils; +import org.apache.cxf.common.util.RunnableWrapperFactory; import org.apache.cxf.configuration.Configurable; import org.apache.cxf.configuration.jsse.TLSClientParameters; import org.apache.cxf.configuration.security.AuthorizationPolicy; @@ -274,6 +275,7 @@ public abstract class HTTPConduit private volatile boolean clientSidePolicyCalced; + private final RunnableWrapperFactory runnableWrapperFactory; /** * Constructor @@ -309,6 +311,8 @@ public HTTPConduit(Bus b, } proxyFactory = new ProxyFactory(); cookies = new Cookies(); + RunnableWrapperFactory rwf = bus.getExtension(RunnableWrapperFactory.class); + runnableWrapperFactory = rwf != null ? rwf : new RunnableWrapperFactory(); } /** @@ -350,6 +354,7 @@ private void updateClientPolicy() { /** * This method returns the registered Logger for this conduit. */ + @Override protected Logger getLogger() { return LOG; } @@ -487,6 +492,7 @@ protected abstract void setupConnection(Message message, Address address, HTTPCl * * @param message The message to be sent. */ + @Override public void prepare(Message message) throws IOException { // This call can possibly change the conduit endpoint address and // protocol from the default set in EndpointInfo that is associated @@ -652,6 +658,7 @@ protected static int determineConnectionTimeout(Message message, return (int)ctimeout; } + @Override public void close(Message msg) throws IOException { InputStream in = msg.getContent(InputStream.class); try { @@ -668,7 +675,12 @@ public void close(Message msg) throws IOException { } } } finally { - super.close(msg); + try { + super.close(msg); + } finally { + //clean up address within threadlocal of EndPointInfo + endpointInfo.resetAddress(); + } } } @@ -723,9 +735,15 @@ private Address setupAddress(Message message) throws URISyntaxException { /** * Close the conduit */ + @Override public void close() { - if (clientSidePolicy != null) { - clientSidePolicy.removePropertyChangeListener(this); + try { + if (clientSidePolicy != null) { + clientSidePolicy.removePropertyChangeListener(this); + } + } finally { + //clean up address within threadlocal of EndPointInfo + endpointInfo.resetAddress(); } } @@ -823,6 +841,7 @@ protected void setHeadersByAuthorizationPolicy( * configuration from spring injection. */ // REVISIT:What happens when the endpoint/bean name is null? + @Override public String getBeanName() { if (endpointInfo.getName() != null) { return endpointInfo.getName().toString() + ".http-conduit"; @@ -1039,6 +1058,7 @@ protected class InterposedMessageObserver implements MessageObserver { * * @param inMessage */ + @Override public void onMessage(Message inMessage) { // disposable exchange, swapped with real Exchange on correlation inMessage.setExchange(new ExchangeImpl()); @@ -1075,15 +1095,18 @@ protected void logStackTrace(Throwable ex) { LOG.warning(sw.toString()); } + @Override public void assertMessage(Message message) { PolicyDataEngine policyDataEngine = bus.getExtension(PolicyDataEngine.class); policyDataEngine.assertMessage(message, getClient(), new ClientPolicyCalculator()); } + @Override public boolean canAssert(QName type) { return type.equals(new QName("http://cxf.apache.org/transports/http/configuration", "client")); } + @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getSource() == clientSidePolicy && "decoupledEndpoint".equals(evt.getPropertyName())) { @@ -1186,7 +1209,8 @@ protected void handleNoOutput() throws IOException { protected void handleResponseOnWorkqueue(boolean allowCurrentThread, boolean forceWQ) throws IOException { - Runnable runnable = new Runnable() { + Runnable runnable = runnableWrapperFactory.wrap(new Runnable() { + @Override public void run() { try { handleResponseInternal(); @@ -1201,7 +1225,7 @@ public void run() { mo.onMessage(outMessage); } } - }; + }, outMessage); HTTPClientPolicy policy = getClient(outMessage); boolean exceptionSet = outMessage.getContent(Exception.class) != null; if (!exceptionSet) { @@ -1211,6 +1235,7 @@ public void run() { final Executor ex2 = ex; final Runnable origRunnable = runnable; runnable = new Runnable() { + @Override public void run() { outMessage.getExchange().put(Executor.class.getName() + ".USING_SPECIFIED", Boolean.TRUE); @@ -1237,6 +1262,9 @@ public void run() { } else { outMessage.getExchange().put(Executor.class.getName() + ".USING_SPECIFIED", Boolean.TRUE); + if (LOG.isLoggable(Level.FINEST)) { + LOG.log(Level.FINEST, "Executing with " + ex); + } ex.execute(runnable); } } catch (RejectedExecutionException rex) { @@ -1347,6 +1375,7 @@ protected void handleHeadersTrustCaching() throws IOException { /** * Perform any actions required on stream closure (handle response etc.) */ + @Override public void close() throws IOException { try { if (buffer != null && buffer.size() > 0) { @@ -1661,7 +1690,6 @@ protected void handleResponseInternal() throws IOException { } } exchange.put("IN_CHAIN_COMPLETE", Boolean.TRUE); - exchange.setInMessage(inMessage); return; } @@ -1950,4 +1978,4 @@ private static void detectAuthorizationLoop(String conduitName, Message message, throw new IOException(logMessage); } } -} +} \ No newline at end of file