Improve performance of base64 validation
This commit is contained in:
parent
1c5a07b5a0
commit
b61887e841
|
@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.param;
|
|||
* Licensed 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.
|
||||
|
@ -20,25 +20,32 @@ package ca.uhn.fhir.rest.param;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterAnd;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterOr;
|
||||
import ca.uhn.fhir.rest.api.QualifiedParamList;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class BaseAndListParam<T extends IQueryParameterOr<?>> implements IQueryParameterAnd<T> {
|
||||
|
||||
private List<T> myValues=new ArrayList<T>();
|
||||
|
||||
private List<T> myValues = new ArrayList<>();
|
||||
|
||||
public abstract BaseAndListParam<T> addAnd(T theValue);
|
||||
|
||||
public BaseAndListParam<T> addValue(T theValue) {
|
||||
myValues.add(theValue);
|
||||
return this;
|
||||
}
|
||||
|
||||
public abstract BaseAndListParam<T> addAnd(T theValue);
|
||||
|
||||
@Override
|
||||
public List<T> getValuesAsQueryTokens() {
|
||||
return myValues;
|
||||
}
|
||||
|
||||
abstract T newInstance();
|
||||
|
||||
@Override
|
||||
public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, List<QualifiedParamList> theParameters) throws InvalidRequestException {
|
||||
|
@ -50,11 +57,9 @@ public abstract class BaseAndListParam<T extends IQueryParameterOr<?>> implement
|
|||
}
|
||||
}
|
||||
|
||||
abstract T newInstance();
|
||||
|
||||
@Override
|
||||
public List<T> getValuesAsQueryTokens() {
|
||||
return myValues;
|
||||
public String toString() {
|
||||
return myValues.toString();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.param;
|
|||
* Licensed 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.
|
||||
|
@ -20,23 +20,35 @@ package ca.uhn.fhir.rest.param;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterOr;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
import ca.uhn.fhir.rest.api.QualifiedParamList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
abstract class BaseOrListParam<MT extends BaseOrListParam<?, ?>, PT extends IQueryParameterType> implements IQueryParameterOr<PT> {
|
||||
|
||||
private List<PT> myList=new ArrayList<PT>();
|
||||
private List<PT> myList = new ArrayList<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public MT add(PT theParameter) {
|
||||
if (theParameter != null) {
|
||||
myList.add(theParameter);
|
||||
}
|
||||
return (MT) this;
|
||||
}
|
||||
|
||||
public abstract MT addOr(PT theParameter);
|
||||
|
||||
@Override
|
||||
public List<PT> getValuesAsQueryTokens() {
|
||||
return myList;
|
||||
}
|
||||
|
||||
abstract PT newInstance();
|
||||
|
||||
// public void addToken(T theParam) {
|
||||
// Validate.notNull(theParam,"Param can not be null");
|
||||
// myList.add(theParam);
|
||||
// }
|
||||
|
||||
@Override
|
||||
public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, QualifiedParamList theParameters) {
|
||||
myList.clear();
|
||||
|
@ -47,21 +59,9 @@ abstract class BaseOrListParam<MT extends BaseOrListParam<?, ?>, PT extends IQue
|
|||
}
|
||||
}
|
||||
|
||||
abstract PT newInstance();
|
||||
|
||||
public abstract MT addOr(PT theParameter);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public MT add(PT theParameter) {
|
||||
if (theParameter != null) {
|
||||
myList.add(theParameter);
|
||||
}
|
||||
return (MT) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PT> getValuesAsQueryTokens() {
|
||||
return myList;
|
||||
public String toString() {
|
||||
return myList.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -279,21 +279,23 @@ public abstract class RequestDetails {
|
|||
}
|
||||
|
||||
public Map<String, List<String>> getUnqualifiedToQualifiedNames() {
|
||||
for (String next : myParameters.keySet()) {
|
||||
for (int i = 0; i < next.length(); i++) {
|
||||
char nextChar = next.charAt(i);
|
||||
if (nextChar == ':' || nextChar == '.') {
|
||||
if (myUnqualifiedToQualifiedNames == null) {
|
||||
myUnqualifiedToQualifiedNames = new HashMap<>();
|
||||
if (myUnqualifiedToQualifiedNames == null) {
|
||||
for (String next : myParameters.keySet()) {
|
||||
for (int i = 0; i < next.length(); i++) {
|
||||
char nextChar = next.charAt(i);
|
||||
if (nextChar == ':' || nextChar == '.') {
|
||||
if (myUnqualifiedToQualifiedNames == null) {
|
||||
myUnqualifiedToQualifiedNames = new HashMap<>();
|
||||
}
|
||||
String unqualified = next.substring(0, i);
|
||||
List<String> list = myUnqualifiedToQualifiedNames.get(unqualified);
|
||||
if (list == null) {
|
||||
list = new ArrayList<>(4);
|
||||
myUnqualifiedToQualifiedNames.put(unqualified, list);
|
||||
}
|
||||
list.add(next);
|
||||
break;
|
||||
}
|
||||
String unqualified = next.substring(0, i);
|
||||
List<String> list = myUnqualifiedToQualifiedNames.get(unqualified);
|
||||
if (list == null) {
|
||||
list = new ArrayList<>(4);
|
||||
myUnqualifiedToQualifiedNames.put(unqualified, list);
|
||||
}
|
||||
list.add(next);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,7 +96,7 @@ public abstract class BaseQueryParameter implements IParameter {
|
|||
@Override
|
||||
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
|
||||
|
||||
List<QualifiedParamList> paramList = new ArrayList<QualifiedParamList>();
|
||||
List<QualifiedParamList> paramList = new ArrayList<>();
|
||||
String name = getName();
|
||||
parseParams(theRequest, paramList, name, null);
|
||||
|
||||
|
|
|
@ -165,7 +165,7 @@ public class SearchDefaultMethodDstu3Test {
|
|||
assertThat(ourLastParam1.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens(), hasSize(1));
|
||||
assertEquals("val1", ourLastParam1.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getValue());
|
||||
|
||||
assertThat(ourLastParam2.getValuesAsQueryTokens(), hasSize(2));
|
||||
assertThat(ourLastParam2.toString(), ourLastParam2.getValuesAsQueryTokens(), hasSize(2));
|
||||
assertThat(ourLastParam2.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens(), hasSize(1));
|
||||
assertEquals("val2", ourLastParam2.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getValue());
|
||||
assertEquals("val2e", ourLastParam2.getValuesAsQueryTokens().get(1).getValuesAsQueryTokens().get(0).getValue());
|
||||
|
|
|
@ -182,11 +182,21 @@
|
|||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<reporting>
|
||||
|
|
|
@ -15,6 +15,12 @@ import ca.uhn.fhir.model.primitive.IdDt;
|
|||
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
|
||||
import ca.uhn.fhir.rest.client.exceptions.FhirClientInappropriateForServerException;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.core.type.filter.AssignableTypeFilter;
|
||||
import org.springframework.core.type.filter.TypeFilter;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class ExceptionPropertiesTest {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExceptionPropertiesTest.class);
|
||||
|
@ -37,13 +43,15 @@ public class ExceptionPropertiesTest {
|
|||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void testExceptionsAreGood() throws Exception {
|
||||
ImmutableSet<ClassInfo> classes = ClassPath.from(Thread.currentThread().getContextClassLoader()).getTopLevelClasses(BaseServerResponseException.class.getPackage().getName());
|
||||
assertTrue(classes.size() > 5);
|
||||
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
|
||||
scanner.addIncludeFilter(new AssignableTypeFilter(BaseServerResponseException.class));
|
||||
Set<BeanDefinition> classes = scanner.findCandidateComponents(BaseServerResponseException.class.getPackage().getName());
|
||||
assertTrue(classes.toString(), classes.size() > 5);
|
||||
|
||||
for (ClassInfo classInfo : classes) {
|
||||
ourLog.info("Scanning {}", classInfo.getName());
|
||||
for (BeanDefinition classInfo : classes) {
|
||||
ourLog.info("Scanning {}", classInfo.getBeanClassName());
|
||||
|
||||
Class<?> next = Class.forName(classInfo.getName());
|
||||
Class<?> next = Class.forName(classInfo.getBeanClassName());
|
||||
assertNotNull(next);
|
||||
|
||||
if (next == getClass()) {
|
||||
|
@ -69,7 +77,7 @@ public class ExceptionPropertiesTest {
|
|||
try {
|
||||
next.getConstructor(String.class, IBaseOperationOutcome.class);
|
||||
} catch (NoSuchMethodException e) {
|
||||
fail(classInfo.getName() + " has no constructor with params: (String, IBaseOperationOutcome)");
|
||||
fail(classInfo.getBeanClassName() + " has no constructor with params: (String, IBaseOperationOutcome)");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1340,15 +1340,27 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
String encoded = e.primitiveValue();
|
||||
if (isNotBlank(encoded)) {
|
||||
/*
|
||||
* Note: Regex comes from: https://stackoverflow.com/questions/8571501/how-to-check-whether-the-string-is-base64-encoded-or-not
|
||||
*
|
||||
* Technically this is not bulletproof as some invalid base64 won't be caught,
|
||||
* but I think it's good enough. The original code used Java8 Base64 decoder
|
||||
* but I've replaced it with a regex for 2 reasons:
|
||||
* 1. This code will run on any version of Java
|
||||
* 2. This code doesn't actually decode, which is much easier on memory use for big payloads
|
||||
*/
|
||||
if (!encoded.matches("^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$")) {
|
||||
int charCount = 0;
|
||||
for (int i = 0; i < encoded.length(); i++) {
|
||||
char nextChar = encoded.charAt(i);
|
||||
if (Character.isWhitespace(nextChar)) {
|
||||
continue;
|
||||
}
|
||||
if (Character.isLetterOrDigit(nextChar)) {
|
||||
charCount++;
|
||||
}
|
||||
if (nextChar == '/' || nextChar == '=' || nextChar == '+') {
|
||||
charCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (charCount > 0 && charCount % 4 != 0) {
|
||||
String value = encoded.length() < 100 ? encoded : "(snip)";
|
||||
rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, "The value \"{0}\" is not a valid Base64 value", value);
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import java.io.InputStream;
|
|||
import java.util.*;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.validation.FhirValidator;
|
||||
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
||||
import ca.uhn.fhir.validation.SingleValidationMessage;
|
||||
|
@ -247,6 +248,15 @@ public class FhirInstanceValidatorR4Test {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLargeBase64() throws IOException {
|
||||
String input = IOUtils.toString(FhirInstanceValidatorR4Test.class.getResourceAsStream("/r4/diagnosticreport-example-gingival-mass.json"), Constants.CHARSET_UTF8);
|
||||
ValidationResult output = myVal.validateWithResult(input);
|
||||
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
|
||||
assertEquals(0, errors.size());
|
||||
}
|
||||
|
||||
|
||||
@Test @Ignore
|
||||
public void testValidateDocument() throws Exception {
|
||||
String vsContents = IOUtils.toString(FhirInstanceValidatorR4Test.class.getResourceAsStream("/sample-document.xml"), "UTF-8");
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue