blob: ae8abc8a6eaf0f25ebc49d51f16509cf0ca8264f [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.servicecomb.common.rest.codec.fix;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.concurrent.Callable;
import org.apache.servicecomb.foundation.common.utils.RestObjectMapper;
import org.apache.servicecomb.foundation.test.scaffolding.model.Color;
import org.junit.jupiter.api.Assertions;
import com.fasterxml.jackson.core.exc.InputCoercionException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.google.common.base.Strings;
import org.junit.jupiter.api.Test;
public class TestDoSFix {
static final ObjectMapper mapper = new RestObjectMapper();
static final String invalidNum = Strings.repeat("9", 100_0000);
static final String invalidStr = "\"" + invalidNum + "\"";
static final String invalidArrNum = "[" + invalidNum + "]";
static final String invalidArrStr = "[\"" + invalidNum + "\"]";
public static class Model {
public Color color;
public char cValue;
public Character cObjValue;
public byte bValue;
public Byte bObjValue;
public short sValue;
public Short sObjValue;
public int iValue;
public Integer iObjValue;
public long lValue;
public Long lObjValue;
public float fValue;
public Float fObjValue;
public double dValue;
public Double dObjValue;
}
void fastFail(Callable<?> callable, Class<?> eCls) {
long start = System.currentTimeMillis();
try {
Object ret = callable.call();
Assertions.fail("expect failed, but succes to be " + ret);
} catch (AssertionError e) {
throw e;
} catch (Throwable e) {
if (eCls != e.getClass()) {
e.printStackTrace();
}
Assertions.assertEquals(eCls, e.getClass());
}
long time = System.currentTimeMillis() - start;
Assertions.assertTrue(time < 1000, "did not fix DoS problem, time:" + time);
}
void fastFail(String input, Class<?> cls, Class<?> eCls) {
fastFail(() -> mapper.readValue(input, cls), eCls);
fastFail(() -> mapper.readValue(new ByteArrayInputStream(input.getBytes()), cls), eCls);
}
void batFastFail(Class<?> cls, Class<?> e1, Class<?> e2) {
fastFail(invalidNum, cls, e1);
fastFail(invalidStr, cls, e2);
fastFail(invalidArrNum, cls, e1);
fastFail(invalidArrStr, cls, e2);
}
void batFastFail(Class<?> cls) {
batFastFail(cls, InputCoercionException.class, InvalidFormatException.class);
}
void batFastFail(String fieldName, Class<?> e1, Class<?> e2) {
fastFail("{\"" + fieldName + "\":" + invalidNum + "}", Model.class, e1);
fastFail("{\"" + fieldName + "\":\"" + invalidNum + "\"}", Model.class, e2);
fastFail("{\"" + fieldName + "\":[" + invalidNum + "]}", Model.class, e1);
fastFail("{\"" + fieldName + "\":[\"" + invalidNum + "\"]}", Model.class, e2);
}
void batFastFail(String fieldName) {
batFastFail(fieldName, JsonMappingException.class, InvalidFormatException.class);
}
@Test
public void testEnum() {
batFastFail(Color.class);
batFastFail("color");
}
@Test
public void testChar() {
batFastFail(char.class, InputCoercionException.class, InvalidFormatException.class);
batFastFail(Character.class, InputCoercionException.class, InvalidFormatException.class);
batFastFail("cValue", JsonMappingException.class, InvalidFormatException.class);
batFastFail("cObjValue", JsonMappingException.class, InvalidFormatException.class);
}
@Test
public void testByte() {
batFastFail(byte.class);
batFastFail(Byte.class);
batFastFail("bValue");
batFastFail("bObjValue");
}
@Test
public void testShort() {
batFastFail(short.class);
batFastFail(Short.class);
batFastFail("sValue");
batFastFail("sObjValue");
}
@Test
public void testInt() {
batFastFail(int.class);
batFastFail(Integer.class);
batFastFail("iValue");
batFastFail("iObjValue");
}
@Test
public void testLong() {
batFastFail(long.class);
batFastFail(Long.class);
batFastFail("lValue");
batFastFail("lObjValue");
}
Object fastSucc(Callable<?> callable) {
long start = System.currentTimeMillis();
try {
Object ret = callable.call();
Assertions.assertTrue(System.currentTimeMillis() - start < 1000);
return ret;
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
Object fastSucc(String input, Class<?> cls) {
return fastSucc(() -> mapper.readValue(input, cls));
}
Object fastSucc(InputStream input, Class<?> cls) {
return fastSucc(() -> {
input.reset();
return mapper.readValue(input, cls);
});
}
void batFastSucc(Class<?> cls, Object expected) {
Assertions.assertEquals(expected, fastSucc(invalidNum, cls));
Assertions.assertEquals(expected, fastSucc(new ByteArrayInputStream(invalidNum.getBytes()), cls));
Assertions.assertEquals(expected, fastSucc(invalidStr, cls));
Assertions.assertEquals(expected, fastSucc(new ByteArrayInputStream(invalidStr.getBytes()), cls));
Assertions.assertEquals(expected, fastSucc(invalidArrNum, cls));
Assertions.assertEquals(expected, fastSucc(new ByteArrayInputStream(invalidArrNum.getBytes()), cls));
Assertions.assertEquals(expected, fastSucc(invalidArrStr, cls));
Assertions.assertEquals(expected, fastSucc(new ByteArrayInputStream(invalidArrStr.getBytes()), cls));
}
void checkField(Model model, String fieldName, Object expected) {
try {
Field field = Model.class.getField(fieldName);
Object value = field.get(model);
Assertions.assertEquals(expected, value);
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
void batFastSucc(String fieldName, Object expected) {
checkField((Model) fastSucc("{\"" + fieldName + "\":" + invalidNum + "}", Model.class), fieldName, expected);
checkField((Model) fastSucc("{\"" + fieldName + "\":\"" + invalidNum + "\"}", Model.class), fieldName, expected);
checkField((Model) fastSucc("{\"" + fieldName + "\":[" + invalidNum + "]}", Model.class), fieldName, expected);
checkField((Model) fastSucc("{\"" + fieldName + "\":[\"" + invalidNum + "\"]}", Model.class), fieldName, expected);
}
@Test
public void testFloat() {
batFastSucc(float.class, Float.POSITIVE_INFINITY);
batFastSucc(Float.class, Float.POSITIVE_INFINITY);
batFastSucc("fValue", Float.POSITIVE_INFINITY);
batFastSucc("fObjValue", Float.POSITIVE_INFINITY);
}
@Test
public void testDouble() {
batFastSucc(double.class, Double.POSITIVE_INFINITY);
batFastSucc(Double.class, Double.POSITIVE_INFINITY);
batFastSucc("dValue", Double.POSITIVE_INFINITY);
batFastSucc("dObjValue", Double.POSITIVE_INFINITY);
}
}