Skip to content
Merged
2 changes: 1 addition & 1 deletion src/main/java/com/skyflow/logs/InfoLogs.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public enum InfoLogs {

// Bearer token generation
EMPTY_BEARER_TOKEN("Bearer token is empty."),
BEARER_TOKEN_EXPIRED("Bearer token is expired."),
BEARER_TOKEN_EXPIRED("Bearer token is invalid or expired."),
GET_BEARER_TOKEN_TRIGGERED("getBearerToken method triggered."),
GET_BEARER_TOKEN_SUCCESS("Bearer token generated."),
GET_SIGNED_DATA_TOKENS_TRIGGERED("getSignedDataTokens method triggered."),
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/com/skyflow/utils/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public final class Constants {
public static final String ERROR_FROM_CLIENT_HEADER_KEY = "error-from-client";
public static final String PROCESSED_FILE_NAME_PREFIX = "processed-";
public static final String DEIDENTIFIED_FILE_PREFIX = "deidentified";
public static final String CURLY_PLACEHOLDER = "{%s}";

public static final class HttpHeader {
public static final String CONTENT_TYPE = "content-type";
public static final String CONTENT_TYPE_JSON = "application/json";
Expand All @@ -60,6 +62,13 @@ private EncodingType() {
// Utility class constructor
}
}
public static final String EMPTY_STRING = "";
public static final String QUOTE = "\"";
public static final class HttpUtilityExtra {
public static final String RAW_BODY_KEY = "__raw_body__";
public static final String SDK_GENERATED_PREFIX = "SDK-Generated-";
private HttpUtilityExtra() {}
}

public static final class FileFormatType {
public static final String TXT = "txt";
Expand Down
28 changes: 23 additions & 5 deletions src/main/java/com/skyflow/utils/HttpUtility.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

public final class HttpUtility {

Expand All @@ -32,8 +34,11 @@ public static String sendRequest(String method, URL url, JsonObject params, Map<
try {
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod(method);
connection.setRequestProperty(Constants.HttpHeader.CONTENT_TYPE, Constants.HttpHeader.CONTENT_TYPE_JSON);
connection.setRequestProperty(Constants.HttpHeader.ACCEPT, Constants.HttpHeader.ACCEPT_ALL);
boolean hasContentType = headers != null && headers.containsKey(Constants.HttpHeader.CONTENT_TYPE);
if (!hasContentType && params != null && !params.isEmpty()) {
connection.setRequestProperty(Constants.HttpHeader.CONTENT_TYPE, Constants.HttpHeader.CONTENT_TYPE_JSON);
}

if (headers != null && !headers.isEmpty()) {
for (Map.Entry<String, String> entry : headers.entrySet())
Expand All @@ -52,9 +57,12 @@ public static String sendRequest(String method, URL url, JsonObject params, Map<
byte[] input = null;
String requestContentType = connection.getRequestProperty(Constants.HttpHeader.CONTENT_TYPE);

if (requestContentType.contains(Constants.HttpHeader.CONTENT_TYPE_FORM_URLENCODED)) {
// Check if this is a raw body (XML, plain text, etc.)
if (params.has(Constants.HttpUtilityExtra.RAW_BODY_KEY) && params.size() == 1) {
input = params.get(Constants.HttpUtilityExtra.RAW_BODY_KEY).getAsString().getBytes(StandardCharsets.UTF_8);
} else if (requestContentType != null && requestContentType.contains(Constants.HttpHeader.CONTENT_TYPE_FORM_URLENCODED)) {
input = formatJsonToFormEncodedString(params).getBytes(StandardCharsets.UTF_8);
} else if (requestContentType.contains(Constants.HttpHeader.CONTENT_TYPE_MULTIPART)) {
} else if (requestContentType != null && requestContentType.contains(Constants.HttpHeader.CONTENT_TYPE_MULTIPART)) {
input = formatJsonToMultiPartFormDataString(params, boundary).getBytes(StandardCharsets.UTF_8);
} else {
input = params.toString().getBytes(StandardCharsets.UTF_8);
Expand All @@ -67,7 +75,11 @@ public static String sendRequest(String method, URL url, JsonObject params, Map<

int httpCode = connection.getResponseCode();
String requestID = connection.getHeaderField(Constants.REQUEST_ID_HEADER_KEY);
HttpUtility.requestID = requestID.split(Constants.HttpUtility.REQUEST_ID_DELIMITER)[0];
if (requestID != null) {
HttpUtility.requestID = requestID.split(Constants.HttpUtility.REQUEST_ID_DELIMITER)[0];
} else {
HttpUtility.requestID = Constants.HttpUtilityExtra.SDK_GENERATED_PREFIX + UUID.randomUUID();
}
Map<String, List<String>> responseHeaders = connection.getHeaderFields();
Reader streamReader;
if (httpCode > 299) {
Expand Down Expand Up @@ -159,7 +171,13 @@ public static String appendRequestId(String message, String requestId) {
}

private static String makeFormEncodeKeyValuePair(String key, String value) {
return key + Constants.HttpUtility.FORM_ENCODE_SEPARATOR + value + Constants.HttpUtility.FORM_ENCODE_DELIMITER;
try {
String encodedKey = URLEncoder.encode(key, StandardCharsets.UTF_8.toString());
String encodedValue = URLEncoder.encode(value, StandardCharsets.UTF_8.toString());
return encodedKey + Constants.HttpUtility.FORM_ENCODE_SEPARATOR + encodedValue + Constants.HttpUtility.FORM_ENCODE_DELIMITER;
} catch (Exception e) {
return key + Constants.HttpUtility.FORM_ENCODE_SEPARATOR + value + Constants.HttpUtility.FORM_ENCODE_DELIMITER;
}
}

}
31 changes: 22 additions & 9 deletions src/main/java/com/skyflow/utils/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
Expand Down Expand Up @@ -77,10 +79,10 @@ public static PrivateKey getPrivateKeyFromPem(String pemKey) throws SkyflowExcep
PrivateKey privateKey = null;

if (pemKey.contains(PKCS8PrivateHeader)) {
privateKeyContent = privateKeyContent.replace(PKCS8PrivateHeader, "");
privateKeyContent = privateKeyContent.replace(PKCS8PrivateFooter, "");
privateKeyContent = privateKeyContent.replace("\n", "");
privateKeyContent = privateKeyContent.replace("\r\n", "");
privateKeyContent = privateKeyContent.replace(PKCS8PrivateHeader, Constants.EMPTY_STRING);
privateKeyContent = privateKeyContent.replace(PKCS8PrivateFooter, Constants.EMPTY_STRING);
privateKeyContent = privateKeyContent.replace("\n", Constants.EMPTY_STRING);
privateKeyContent = privateKeyContent.replace("\r\n", Constants.EMPTY_STRING);
privateKey = parsePkcs8PrivateKey(Base64.decodeBase64(privateKeyContent));
} else {
LogUtil.printErrorLog(ErrorLogs.JWT_INVALID_FORMAT.getLog());
Expand Down Expand Up @@ -110,7 +112,12 @@ public static String constructConnectionURL(ConnectionConfig config, InvokeConne
for (Map.Entry<String, String> entry : invokeConnectionRequest.getPathParams().entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
filledURL = new StringBuilder(filledURL.toString().replace(String.format("{%s}", key), value));
try {
String encodedValue = URLEncoder.encode(value, StandardCharsets.UTF_8.name());
filledURL = new StringBuilder(filledURL.toString().replace(String.format(Constants.CURLY_PLACEHOLDER, key), encodedValue));
} catch (Exception e) {
filledURL = new StringBuilder(filledURL.toString().replace(String.format(Constants.CURLY_PLACEHOLDER, key), value));
}
}
}

Expand All @@ -119,7 +126,13 @@ public static String constructConnectionURL(ConnectionConfig config, InvokeConne
for (Map.Entry<String, String> entry : invokeConnectionRequest.getQueryParams().entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
filledURL.append(key).append("=").append(value).append("&");
try {
String encodedKey = URLEncoder.encode(key, StandardCharsets.UTF_8.name());
String encodedValue = URLEncoder.encode(value, StandardCharsets.UTF_8.name());
filledURL.append(encodedKey).append(Constants.HttpUtility.FORM_ENCODE_SEPARATOR).append(encodedValue).append(Constants.HttpUtility.FORM_ENCODE_DELIMITER);
} catch (Exception e) {
filledURL.append(key).append(Constants.HttpUtility.FORM_ENCODE_SEPARATOR).append(value).append(Constants.HttpUtility.FORM_ENCODE_DELIMITER);
}
}
filledURL = new StringBuilder(filledURL.substring(0, filledURL.length() - 1));
}
Expand Down Expand Up @@ -152,7 +165,7 @@ public static JsonObject getMetrics() {
InfoLogs.UNABLE_TO_GENERATE_SDK_METRIC.getLog(),
Constants.SDK_METRIC_CLIENT_DEVICE_MODEL
));
deviceModel = "";
deviceModel = Constants.EMPTY_STRING;
}

// Retrieve OS details
Expand All @@ -164,7 +177,7 @@ public static JsonObject getMetrics() {
InfoLogs.UNABLE_TO_GENERATE_SDK_METRIC.getLog(),
Constants.SDK_METRIC_CLIENT_OS_DETAILS
));
osDetails = "";
osDetails = Constants.EMPTY_STRING;
}

// Retrieve Java version details
Expand All @@ -176,7 +189,7 @@ public static JsonObject getMetrics() {
InfoLogs.UNABLE_TO_GENERATE_SDK_METRIC.getLog(),
Constants.SDK_METRIC_RUNTIME_DETAILS
));
javaVersion = "";
javaVersion = Constants.EMPTY_STRING;
}
details.addProperty(Constants.SDK_METRIC_NAME_VERSION, Constants.SDK_METRIC_NAME_VERSION_PREFIX + sdkVersion);
details.addProperty(Constants.SDK_METRIC_CLIENT_DEVICE_MODEL, deviceModel);
Expand Down
28 changes: 22 additions & 6 deletions src/main/java/com/skyflow/utils/validations/Validations.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.skyflow.utils.validations;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.skyflow.config.ConnectionConfig;
import com.skyflow.config.Credentials;
Expand Down Expand Up @@ -137,12 +138,27 @@ public static void validateInvokeConnectionRequest(InvokeConnectionRequest invok
}

if (requestBody != null) {
Gson gson = new Gson();
JsonObject bodyObject = gson.toJsonTree(requestBody).getAsJsonObject();
if (bodyObject.isEmpty()) {
LogUtil.printErrorLog(Utils.parameterizedString(
ErrorLogs.EMPTY_REQUEST_BODY.getLog(), InterfaceName.INVOKE_CONNECTION.getName()));
throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyRequestBody.getMessage());
if (requestBody.getClass().equals(Object.class)) {
return;
}
if (requestBody instanceof String) {
String bodyStr = (String) requestBody;
if (bodyStr.trim().isEmpty()) {
LogUtil.printErrorLog(Utils.parameterizedString(
ErrorLogs.EMPTY_REQUEST_BODY.getLog(), InterfaceName.INVOKE_CONNECTION.getName()));
throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyRequestBody.getMessage());
}
} else {
Gson gson = new Gson();
JsonElement bodyElement = gson.toJsonTree(requestBody);
if (bodyElement.isJsonObject()) {
JsonObject bodyObject = bodyElement.getAsJsonObject();
if (bodyObject.isEmpty()) {
LogUtil.printErrorLog(Utils.parameterizedString(
ErrorLogs.EMPTY_REQUEST_BODY.getLog(), InterfaceName.INVOKE_CONNECTION.getName()));
throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyRequestBody.getMessage());
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,50 @@ public InvokeConnectionResponse invoke(InvokeConnectionRequest invokeConnectionR
headers.put(Constants.SDK_METRICS_HEADER_KEY, Utils.getMetrics().toString());

RequestMethod requestMethod = invokeConnectionRequest.getMethod();
JsonObject requestBody = null;
Object requestBodyObject = invokeConnectionRequest.getRequestBody();
String requestContentType = headers
.getOrDefault(Constants.HttpHeader.CONTENT_TYPE, Constants.HttpHeader.CONTENT_TYPE_JSON)
.toLowerCase();
boolean isJsonRequest = requestContentType.contains(Constants.HttpHeader.CONTENT_TYPE_JSON);

if (requestBodyObject != null) {
try {
requestBody = convertObjectToJson(requestBodyObject);
} catch (Exception e) {
LogUtil.printErrorLog(ErrorLogs.INVALID_REQUEST_HEADERS.getLog());
throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidRequestBody.getMessage());
Object processedRequestBody;

try {
if (requestBodyObject instanceof String) {
if (isJsonRequest) {
JsonObject jsonWrapper = new JsonObject();
jsonWrapper.addProperty(
Constants.HttpUtilityExtra.RAW_BODY_KEY,
(String) requestBodyObject
);
processedRequestBody = jsonWrapper;
} else {
processedRequestBody = requestBodyObject;
}
} else if (requestBodyObject instanceof JsonObject || requestBodyObject == null) {
processedRequestBody = requestBodyObject;
} else {
processedRequestBody = convertObjectToJson(requestBodyObject);
}
} catch (Exception e) {
LogUtil.printErrorLog(ErrorLogs.INVALID_REQUEST_HEADERS.getLog());
throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidRequestBody.getMessage());
}
JsonObject payloadToSend;
if (processedRequestBody instanceof JsonObject || processedRequestBody == null) {
payloadToSend = (JsonObject) processedRequestBody;
} else {
payloadToSend = new JsonObject();
payloadToSend.addProperty(Constants.HttpUtilityExtra.RAW_BODY_KEY, processedRequestBody.toString());
}

String response = HttpUtility.sendRequest(requestMethod.name(), new URL(filledURL), requestBody, headers);
JsonObject data = JsonParser.parseString(response).getAsJsonObject();
String response = HttpUtility.sendRequest(requestMethod.name(), new URL(filledURL), payloadToSend, headers);
Object data = response;
try {
data = JsonParser.parseString(response).getAsJsonObject();
} catch (Exception e) {
LogUtil.printErrorLog(ErrorLogs.INVOKE_CONNECTION_REQUEST_REJECTED.getLog());
}
HashMap<String, String> metadata = new HashMap<>();
metadata.put(Constants.JsonFieldNames.REQUEST_ID, HttpUtility.getRequestID());
connectionResponse = new InvokeConnectionResponse(data, metadata, null);
Expand Down
67 changes: 67 additions & 0 deletions src/test/java/com/skyflow/utils/HttpUtilityTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,71 @@ public void testSendRequestError() {
fail(INVALID_EXCEPTION_THROWN);
}
}

// New test cases for content-type handling changes

@Test
@PrepareForTest({URL.class, HttpURLConnection.class})
public void testSendRequestWithRawBody() {
try {
given(mockConnection.getRequestProperty("content-type")).willReturn("application/xml");
Map<String, String> headers = new HashMap<>();
headers.put("content-type", "application/xml");
JsonObject params = new JsonObject();
params.addProperty("__raw_body__", "<xml>test</xml>");
String response = httpUtility.sendRequest("POST", url, params, headers);
Assert.assertEquals(expected, response);
} catch (Exception e) {
fail(INVALID_EXCEPTION_THROWN);
}
}

@Test
@PrepareForTest({URL.class, HttpURLConnection.class})
public void testSendRequestWithoutContentTypeHeader() {
try {
given(mockConnection.getRequestProperty("content-type")).willReturn("application/json");
JsonObject params = new JsonObject();
params.addProperty("key", "value");
String response = httpUtility.sendRequest("POST", url, params, null);
Assert.assertEquals(expected, response);
} catch (Exception e) {
fail(INVALID_EXCEPTION_THROWN);
}
}

@Test
@PrepareForTest({URL.class, HttpURLConnection.class})
public void testSendRequestWithNullRequestId() {
try {
given(mockConnection.getHeaderField(anyString())).willReturn(null);
given(mockConnection.getRequestProperty("content-type")).willReturn("application/json");
Map<String, String> headers = new HashMap<>();
headers.put("content-type", "application/json");
JsonObject params = new JsonObject();
params.addProperty("key", "value");
String response = httpUtility.sendRequest("GET", url, params, headers);
Assert.assertEquals(expected, response);
Assert.assertNotNull(HttpUtility.getRequestID());
} catch (Exception e) {
fail(INVALID_EXCEPTION_THROWN);
}
}

@Test
@PrepareForTest({URL.class, HttpURLConnection.class})
public void testSendRequestFormURLEncodedWithSpecialCharacters() {
try {
given(mockConnection.getRequestProperty("content-type")).willReturn("application/x-www-form-urlencoded");
Map<String, String> headers = new HashMap<>();
headers.put("content-type", "application/x-www-form-urlencoded");
JsonObject params = new JsonObject();
params.addProperty("key", "value with spaces");
params.addProperty("special", "test@email.com");
String response = httpUtility.sendRequest("POST", url, params, headers);
Assert.assertEquals(expected, response);
} catch (Exception e) {
fail(INVALID_EXCEPTION_THROWN);
}
}
}
Loading
Loading