Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.google.api.server.spi.request;

import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.google.api.server.spi.EndpointMethod;
import com.google.api.server.spi.EndpointsContext;
import com.google.api.server.spi.IoUtil;
Expand All @@ -24,6 +25,8 @@
import com.google.api.server.spi.config.model.ApiParameterConfig;
import com.google.api.server.spi.config.model.ApiSerializationConfig;
import com.google.api.server.spi.response.BadRequestException;
import com.google.api.server.spi.types.DateAndTime;
import com.google.api.server.spi.types.SimpleDate;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;

Expand All @@ -39,12 +42,13 @@

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
Expand Down Expand Up @@ -159,12 +163,63 @@ public Object[] read() throws ServiceException {
}
}
return deserializeParams(body);
} catch (InvalidFormatException e) {
logger.atInfo().withCause(e).log("Unable to read request parameter(s)");
throw translate(e);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException
| IOException e) {
logger.atInfo().withCause(e).log("Unable to read request parameter(s)");
throw new BadRequestException(e);
throw new BadRequestException("Parse error", "parseError", "global");
}
}

private BadRequestException translate(InvalidFormatException e) {
String messagePattern = "Invalid {0} value \"{1}\".{2}";
String reason = "parseError";
if (e.getTargetType().isEnum()) {
return new BadRequestException(
MessageFormat.format(messagePattern, "enum",
e.getValue(),
" Valid values are " + Arrays.toString(e.getTargetType().getEnumConstants())),
reason
);
} else if (isNumber(e.getTargetType())) {
return new BadRequestException(
MessageFormat.format(messagePattern, "number", e.getValue(), ""),
reason
);
} else if (isBoolean(e.getTargetType())) {
return new BadRequestException(
MessageFormat.format(messagePattern,"boolean", e.getValue(), " Valid values are [true, false]"),
reason
);
} else if (isDate(e.getTargetType())) {
return new BadRequestException(
MessageFormat.format(messagePattern, "date", e.getValue(), ""),
reason
);
} else {
return new BadRequestException("Parse error", reason, "global");
}
}
private boolean isBoolean(Class<?> clazz) {
return Boolean.class.equals(clazz) || boolean.class.equals(clazz);
}

private boolean isDate(Class<?> clazz) {
return Date.class.isAssignableFrom(clazz)
|| DateAndTime.class.equals(clazz)
|| SimpleDate.class.equals(clazz);
}

private boolean isNumber(Class<?> clazz) {
return Number.class.isAssignableFrom(clazz)
|| byte.class.equals(clazz)
|| int.class.equals(clazz)
|| long.class.equals(clazz)
|| float.class.equals(clazz)
|| double.class.equals(clazz);
}

private static ImmutableMap<String, Class<?>> getParameterMap(EndpointMethod method)
throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.google.api.server.spi.request;

import com.fasterxml.jackson.core.Base64Variants;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.google.api.server.spi.ConfiguredObjectMapper;
import com.google.api.server.spi.EndpointMethod;
import com.google.api.server.spi.EndpointsContext;
Expand Down Expand Up @@ -61,8 +62,6 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -257,17 +256,26 @@ private static class DateDeserializer extends JsonDeserializer<Date> {
@Override
public Date deserialize(JsonParser jsonParser, DeserializationContext context)
throws IOException {
com.google.api.client.util.DateTime date =
new com.google.api.client.util.DateTime(jsonParser.readValueAs(String.class));
return new Date(date.getValue());
String value = jsonParser.readValueAs(String.class);
try {
com.google.api.client.util.DateTime date = new com.google.api.client.util.DateTime(value);
return new Date(date.getValue());
} catch (NumberFormatException e) {
throw InvalidFormatException.from(jsonParser, e.getMessage(), value, Date.class);
}
}
}

private static class DateAndTimeDeserializer extends JsonDeserializer<DateAndTime> {
@Override
public DateAndTime deserialize(JsonParser jsonParser, DeserializationContext context)
throws IOException {
return DateAndTime.parseRfc3339String(jsonParser.readValueAs(String.class));
String value = jsonParser.readValueAs(String.class);
try {
return DateAndTime.parseRfc3339String(value);
} catch (IllegalArgumentException e) {
throw InvalidFormatException.from(jsonParser, e.getMessage(), value, DateAndTime.class);
}
}
}

Expand All @@ -283,12 +291,22 @@ public SimpleDate deserialize(JsonParser jsonParser, DeserializationContext cont
int year = Integer.parseInt(matcher.group(1));
int month = Integer.parseInt(matcher.group(2));
int day = Integer.parseInt(matcher.group(3));
return new SimpleDate(year, month, day);
try {
return new SimpleDate(year, month, day);
} catch (IllegalArgumentException e) {
throw buildException(jsonParser, value);
}
} else {
throw new IllegalArgumentException(
"String is not an RFC3339 formated date (yyyy-mm-dd): " + value);
throw buildException(jsonParser, value);
}
}

private InvalidFormatException buildException(JsonParser jsonParser, String value) {
return InvalidFormatException.from(jsonParser,
"String is not an RFC3339 formated date (yyyy-mm-dd)",
value,
SimpleDate.class);
}
}

private static class BlobDeserializer extends JsonDeserializer<Blob> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.google.api.server.spi.EndpointMethod;
import com.google.api.server.spi.EndpointsContext;
import com.google.api.server.spi.ServiceContext;
import com.google.api.server.spi.ServiceException;
import com.google.api.server.spi.TypeLoader;
import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
Expand All @@ -39,7 +40,9 @@

import java.util.ArrayList;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.springframework.mock.web.MockHttpServletRequest;
Expand All @@ -48,6 +51,7 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand All @@ -67,6 +71,9 @@ public class RestServletRequestParamReaderTest {
private ApiSerializationConfig serializationConfig;
private ApiConfig apiConfig;
private ApiMethodConfig methodConfig;

@Rule
public ExpectedException thrown = ExpectedException.none();

@Before
public void setUp() throws Exception {
Expand Down Expand Up @@ -169,7 +176,127 @@ public void nonObjectRequest() throws Exception {
// expected
}
}

@Test
public void parseIntegerError() throws ServiceException {
request.setContent("{\"objInt\":\"invalid\"}".getBytes(StandardCharsets.UTF_8));
RestServletRequestParamReader reader = createReader(ImmutableMap.of("path", "1234"));

thrown.expect(BadRequestException.class);
thrown.expectMessage("Invalid number");
reader.read();
}

@Test
public void parseIntError() throws ServiceException {
request.setContent("{\"simpleInt\":\"invalid\"}".getBytes(StandardCharsets.UTF_8));
RestServletRequestParamReader reader = createReader(ImmutableMap.of("path", "1234"));

thrown.expect(BadRequestException.class);
thrown.expectMessage("Invalid number value \"invalid\"");
reader.read();
}

@Test
public void parseLongError() throws ServiceException {
request.setContent("{\"objLong\":\"invalid\"}".getBytes(StandardCharsets.UTF_8));
RestServletRequestParamReader reader = createReader(ImmutableMap.of("path", "1234"));

thrown.expect(BadRequestException.class);
thrown.expectMessage("Invalid number");
reader.read();
}

@Test
public void parsePrimitiveLongError() throws ServiceException {
request.setContent("{\"simpleLong\":\"invalid\"}".getBytes(StandardCharsets.UTF_8));
RestServletRequestParamReader reader = createReader(ImmutableMap.of("path", "1234"));

thrown.expect(BadRequestException.class);
thrown.expectMessage("Invalid number value \"invalid\"");
reader.read();
}

@Test
public void parseFloatError() throws ServiceException {
request.setContent("{\"objFloat\":\"invalid\"}".getBytes(StandardCharsets.UTF_8));
RestServletRequestParamReader reader = createReader(ImmutableMap.of("path", "1234"));

thrown.expect(BadRequestException.class);
thrown.expectMessage("Invalid number");
reader.read();
}

@Test
public void parsePrimitiveFloatError() throws ServiceException {
request.setContent("{\"simpleFloat\":\"invalid\"}".getBytes(StandardCharsets.UTF_8));
RestServletRequestParamReader reader = createReader(ImmutableMap.of("path", "1234"));

thrown.expect(BadRequestException.class);
thrown.expectMessage("Invalid number value \"invalid\"");
reader.read();
}

@Test
public void parseDoubleError() throws ServiceException {
request.setContent("{\"objDouble\":\"invalid\"}".getBytes(StandardCharsets.UTF_8));
RestServletRequestParamReader reader = createReader(ImmutableMap.of("path", "1234"));

thrown.expect(BadRequestException.class);
thrown.expectMessage("Invalid number");
reader.read();
}

@Test
public void parsePrimitiveDoubleError() throws ServiceException {
request.setContent("{\"simpleDouble\":\"invalid\"}".getBytes(StandardCharsets.UTF_8));
RestServletRequestParamReader reader = createReader(ImmutableMap.of("path", "1234"));

thrown.expect(BadRequestException.class);
thrown.expectMessage("Invalid number value \"invalid\"");
reader.read();
}

@Test
public void parseBooleanError() throws ServiceException {
request.setContent("{\"objBoolean\":\"invalid\"}".getBytes(StandardCharsets.UTF_8));
RestServletRequestParamReader reader = createReader(ImmutableMap.of("path", "1234"));

thrown.expect(BadRequestException.class);
thrown.expectMessage("Invalid boolean value");
reader.read();
}

@Test
public void parsePrimitiveBooleanError() throws ServiceException {
request.setContent("{\"simpleBoolean\":\"invalid\"}".getBytes(StandardCharsets.UTF_8));
RestServletRequestParamReader reader = createReader(ImmutableMap.of("path", "1234"));

thrown.expect(BadRequestException.class);
thrown.expectMessage("Invalid boolean value \"invalid\"");
reader.read();
}

@Test
public void parseEnumError() throws ServiceException {
request.setContent("{\"simpleEnum\":\"invalidEnum\"}".getBytes(StandardCharsets.UTF_8));
RestServletRequestParamReader reader = createReader(ImmutableMap.of("path", "1234"));

thrown.expect(BadRequestException.class);
thrown.expectMessage("Invalid enum value \"invalidEnum\". Valid values are [One, Two, Three]");
reader.read();
}

@Test
public void parseDateError() throws ServiceException {
request.setContent("{\"objDate\":\"invalidDate\"}".getBytes(StandardCharsets.UTF_8));
RestServletRequestParamReader reader = createReader(ImmutableMap.of("path", "1234"));

thrown.expect(BadRequestException.class);
thrown.expectMessage("Invalid date value \"invalidDate\".");
reader.read();
}

@Test
public void gzippedRequest() throws Exception {
request.addParameter("path", "1234");
Expand Down Expand Up @@ -237,9 +364,29 @@ private RestServletRequestParamReader createReader(Map<String, String> rawPathPa
serializationConfig, methodConfig);
}

public enum TestEnum {
One, Two, Three
}

public static class TestResource {
public SimpleDate query;


public int simpleInt;
public long simpleLong;
public float simpleFloat;
public double simpleDouble;
public boolean simpleBoolean;

public Integer objInt;
public Long objLong;
public Float objFloat;
public Double objDouble;
public Boolean objBoolean;

public Date objDate;

public TestEnum simpleEnum;

public TestResource() {}

public TestResource(SimpleDate query) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -901,8 +901,8 @@ private void verifySimpleDateSerializationFails(String simpleDateString)
try {
readParameters(
"{" + TestEndpoint.NAME_DATE_AND_TIME + ":\"" + simpleDateString + "\"}", method);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {}
fail("Expected BadRequestException");
} catch (BadRequestException expected) {}
}

private Calendar getCalendarFromDate(Date date) {
Expand Down