blob: 483854ffdf2d1f41f64476118aec56a9edfc2f98 [file] [log] [blame] [edit]
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#include "http_request_utils.h"
#include "crt.h"
#include "java_class_ids.h"
#include <aws/common/byte_order.h>
#include <aws/http/http.h>
#include <aws/http/request_response.h>
#include <aws/io/stream.h>
#if _MSC_VER
# pragma warning(disable : 4204) /* non-constant aggregate initializer */
#endif
struct aws_http_request_body_stream_impl {
struct aws_input_stream base;
struct aws_allocator *allocator;
JavaVM *jvm;
jobject http_request_body_stream;
bool body_done;
bool is_valid;
};
static int s_aws_input_stream_seek(struct aws_input_stream *stream, int64_t offset, enum aws_stream_seek_basis basis) {
struct aws_http_request_body_stream_impl *impl =
AWS_CONTAINER_OF(stream, struct aws_http_request_body_stream_impl, base);
if (!impl->is_valid) {
return aws_raise_error(AWS_ERROR_HTTP_INVALID_BODY_STREAM);
}
int result = AWS_OP_SUCCESS;
if (impl->http_request_body_stream != NULL) {
if (basis != AWS_SSB_BEGIN || offset != 0) {
return AWS_OP_ERR;
}
/********** JNI ENV ACQUIRE **********/
JNIEnv *env = aws_jni_acquire_thread_env(impl->jvm);
if (env == NULL) {
/* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */
return AWS_OP_ERR;
}
if (!(*env)->CallBooleanMethod(
env, impl->http_request_body_stream, http_request_body_stream_properties.reset_position)) {
result = aws_raise_error(AWS_ERROR_HTTP_CALLBACK_FAILURE);
}
if (aws_jni_check_and_clear_exception(env)) {
result = aws_raise_error(AWS_ERROR_HTTP_CALLBACK_FAILURE);
}
aws_jni_release_thread_env(impl->jvm, env);
/********** JNI ENV RELEASE **********/
}
if (result == AWS_OP_SUCCESS) {
impl->body_done = false;
}
return result;
}
static int s_aws_input_stream_read(struct aws_input_stream *stream, struct aws_byte_buf *dest) {
struct aws_http_request_body_stream_impl *impl =
AWS_CONTAINER_OF(stream, struct aws_http_request_body_stream_impl, base);
if (!impl->is_valid) {
return aws_raise_error(AWS_ERROR_HTTP_INVALID_BODY_STREAM);
}
if (impl->http_request_body_stream == NULL) {
impl->body_done = true;
return AWS_OP_SUCCESS;
}
if (impl->body_done) {
return AWS_OP_SUCCESS;
}
/********** JNI ENV ACQUIRE **********/
JNIEnv *env = aws_jni_acquire_thread_env(impl->jvm);
if (env == NULL) {
/* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */
return AWS_OP_ERR;
}
size_t out_remaining = dest->capacity - dest->len;
jobject direct_buffer = aws_jni_direct_byte_buffer_from_raw_ptr(env, dest->buffer + dest->len, out_remaining);
impl->body_done = (*env)->CallBooleanMethod(
env, impl->http_request_body_stream, http_request_body_stream_properties.send_outgoing_body, direct_buffer);
int result = AWS_OP_SUCCESS;
if (aws_jni_check_and_clear_exception(env)) {
result = aws_raise_error(AWS_ERROR_HTTP_CALLBACK_FAILURE);
} else {
size_t amt_written = aws_jni_byte_buffer_get_position(env, direct_buffer);
dest->len += amt_written;
}
(*env)->DeleteLocalRef(env, direct_buffer);
aws_jni_release_thread_env(impl->jvm, env);
/********** JNI ENV RELEASE **********/
return result;
}
static int s_aws_input_stream_get_status(struct aws_input_stream *stream, struct aws_stream_status *status) {
struct aws_http_request_body_stream_impl *impl =
AWS_CONTAINER_OF(stream, struct aws_http_request_body_stream_impl, base);
status->is_end_of_stream = impl->body_done;
status->is_valid = impl->is_valid;
return AWS_OP_SUCCESS;
}
static int s_aws_input_stream_get_length(struct aws_input_stream *stream, int64_t *length) {
AWS_FATAL_ASSERT(length && "NULL length out param passed to JNI aws_input_stream_get_length");
struct aws_http_request_body_stream_impl *impl =
AWS_CONTAINER_OF(stream, struct aws_http_request_body_stream_impl, base);
if (impl->http_request_body_stream != NULL) {
/********** JNI ENV ACQUIRE **********/
JNIEnv *env = aws_jni_acquire_thread_env(impl->jvm);
if (env == NULL) {
/* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */
return AWS_OP_ERR;
}
*length =
(*env)->CallLongMethod(env, impl->http_request_body_stream, http_request_body_stream_properties.get_length);
int result = AWS_OP_SUCCESS;
if (aws_jni_check_and_clear_exception(env)) {
result = aws_raise_error(AWS_ERROR_HTTP_CALLBACK_FAILURE);
}
aws_jni_release_thread_env(impl->jvm, env);
/********** JNI ENV RELEASE **********/
return result;
}
return AWS_OP_ERR;
}
static void s_aws_input_stream_destroy(struct aws_http_request_body_stream_impl *impl) {
/********** JNI ENV ACQUIRE **********/
JNIEnv *env = aws_jni_acquire_thread_env(impl->jvm);
if (env == NULL) {
/* If we can't get an environment, then the JVM is probably shutting down. Don't crash. */
return;
}
if (impl->http_request_body_stream != NULL) {
(*env)->DeleteGlobalRef(env, impl->http_request_body_stream);
}
aws_jni_release_thread_env(impl->jvm, env);
/********** JNI ENV RELEASE **********/
aws_mem_release(impl->allocator, impl);
}
static struct aws_input_stream_vtable s_aws_input_stream_vtable = {
.seek = s_aws_input_stream_seek,
.read = s_aws_input_stream_read,
.get_status = s_aws_input_stream_get_status,
.get_length = s_aws_input_stream_get_length,
};
struct aws_input_stream *aws_input_stream_new_from_java_http_request_body_stream(
struct aws_allocator *allocator,
JNIEnv *env,
jobject http_request_body_stream) {
struct aws_http_request_body_stream_impl *impl =
aws_mem_calloc(allocator, 1, sizeof(struct aws_http_request_body_stream_impl));
impl->allocator = allocator;
impl->base.vtable = &s_aws_input_stream_vtable;
aws_ref_count_init(&impl->base.ref_count, impl, (aws_simple_completion_callback *)s_aws_input_stream_destroy);
jint jvmresult = (*env)->GetJavaVM(env, &impl->jvm);
AWS_FATAL_ASSERT(jvmresult == 0);
impl->is_valid = true;
if (http_request_body_stream != NULL) {
impl->http_request_body_stream = (*env)->NewGlobalRef(env, http_request_body_stream);
if (impl->http_request_body_stream == NULL) {
goto on_error;
}
} else {
impl->body_done = true;
}
return &impl->base;
on_error:
aws_input_stream_release(&impl->base);
return NULL;
}
static inline int s_marshal_http_header_to_buffer(
struct aws_byte_buf *buf,
const struct aws_byte_cursor *name,
const struct aws_byte_cursor *value) {
if (aws_byte_buf_reserve_relative(buf, sizeof(int) + sizeof(int) + name->len + value->len)) {
return AWS_OP_ERR;
}
/* This will append to the buffer without overwriting anything */
aws_byte_buf_write_be32(buf, (uint32_t)name->len);
aws_byte_buf_write_from_whole_cursor(buf, *name);
aws_byte_buf_write_be32(buf, (uint32_t)value->len);
aws_byte_buf_write_from_whole_cursor(buf, *value);
return AWS_OP_SUCCESS;
}
int aws_marshal_http_headers_array_to_dynamic_buffer(
struct aws_byte_buf *buf,
const struct aws_http_header *header_array,
size_t num_headers) {
for (size_t i = 0; i < num_headers; ++i) {
if (s_marshal_http_header_to_buffer(buf, &header_array[i].name, &header_array[i].value)) {
return AWS_OP_ERR;
}
}
return AWS_OP_SUCCESS;
}
int aws_marshal_http_headers_to_dynamic_buffer(struct aws_byte_buf *buf, const struct aws_http_headers *headers) {
for (size_t i = 0; i < aws_http_headers_count(headers); ++i) {
struct aws_http_header header;
aws_http_headers_get_index(headers, i, &header);
if (s_marshal_http_header_to_buffer(buf, &header.name, &header.value)) {
return AWS_OP_ERR;
}
}
return AWS_OP_SUCCESS;
}
/**
* Unmarshal the request from java.
*
* Version is as int: [4-bytes BE]
*
* Each string field is: [4-bytes BE] [variable length bytes specified
* by the previous field]
*
* Each request is like: [version][method][path][header name-value
* pairs]
*
* s_unmarshal_http_request_to_get_version to get the version field, which is a 4 byte int.
* s_unmarshal_http_request_without_version to get the whole request without version field.
*/
static inline enum aws_http_version s_unmarshal_http_request_to_get_version(struct aws_byte_cursor *request_blob) {
uint32_t version = 0;
if (!aws_byte_cursor_read_be32(request_blob, &version)) {
aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
}
return version;
}
static inline int s_unmarshal_http_request_without_version(
struct aws_http_message *message,
struct aws_byte_cursor *request_blob) {
uint32_t field_len = 0;
if (aws_http_message_get_protocol_version(message) != AWS_HTTP_VERSION_2) {
/* HTTP/1 puts method and path first, but those are empty in HTTP/2 */
if (!aws_byte_cursor_read_be32(request_blob, &field_len)) {
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
}
struct aws_byte_cursor method = aws_byte_cursor_advance(request_blob, field_len);
int result = aws_http_message_set_request_method(message, method);
if (result != AWS_OP_SUCCESS) {
return AWS_OP_ERR;
}
if (!aws_byte_cursor_read_be32(request_blob, &field_len)) {
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
}
struct aws_byte_cursor path = aws_byte_cursor_advance(request_blob, field_len);
result = aws_http_message_set_request_path(message, path);
if (result != AWS_OP_SUCCESS) {
return AWS_OP_ERR;
}
} else {
/* Read empty method and path from the marshalled request */
if (!aws_byte_cursor_read_be32(request_blob, &field_len) || field_len) {
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
}
if (!aws_byte_cursor_read_be32(request_blob, &field_len) || field_len) {
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
}
}
while (request_blob->len) {
if (!aws_byte_cursor_read_be32(request_blob, &field_len)) {
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
}
struct aws_byte_cursor header_name = aws_byte_cursor_advance(request_blob, field_len);
if (!aws_byte_cursor_read_be32(request_blob, &field_len)) {
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
}
struct aws_byte_cursor header_value = aws_byte_cursor_advance(request_blob, field_len);
struct aws_http_header header = {
.name = header_name,
.value = header_value,
};
aws_http_message_add_header(message, header);
}
return AWS_OP_SUCCESS;
}
static inline int s_unmarshal_http_headers(struct aws_http_headers *headers, struct aws_byte_cursor *request_blob) {
uint32_t field_len = 0;
while (request_blob->len) {
if (!aws_byte_cursor_read_be32(request_blob, &field_len)) {
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
}
struct aws_byte_cursor header_name = aws_byte_cursor_advance(request_blob, field_len);
if (!aws_byte_cursor_read_be32(request_blob, &field_len)) {
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
}
struct aws_byte_cursor header_value = aws_byte_cursor_advance(request_blob, field_len);
struct aws_http_header header = {
.name = header_name,
.value = header_value,
};
aws_http_headers_add_header(headers, &header);
}
return AWS_OP_SUCCESS;
}
int aws_apply_java_http_request_changes_to_native_request(
JNIEnv *env,
jbyteArray marshalled_request,
jobject jni_body_stream,
struct aws_http_message *message) {
/* come back to this when we decide we need to. */
(void)jni_body_stream;
struct aws_http_headers *headers = aws_http_message_get_headers(message);
aws_http_headers_clear(headers);
int result = AWS_OP_SUCCESS;
const size_t marshalled_request_length = (*env)->GetArrayLength(env, marshalled_request);
uint8_t *marshalled_request_data = (*env)->GetPrimitiveArrayCritical(env, marshalled_request, NULL);
struct aws_byte_cursor marshalled_cur =
aws_byte_cursor_from_array((uint8_t *)marshalled_request_data, marshalled_request_length);
enum aws_http_version version = s_unmarshal_http_request_to_get_version(&marshalled_cur);
if (version != aws_http_message_get_protocol_version(message)) {
result = aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
} else {
result = s_unmarshal_http_request_without_version(message, &marshalled_cur);
}
(*env)->ReleasePrimitiveArrayCritical(env, marshalled_request, marshalled_request_data, 0);
if (result) {
aws_jni_throw_runtime_exception(
env, "HttpRequest.applyChangesToNativeRequest: %s\n", aws_error_debug_str(aws_last_error()));
return result;
}
if (jni_body_stream) {
struct aws_input_stream *body_stream =
aws_input_stream_new_from_java_http_request_body_stream(aws_jni_get_allocator(), env, jni_body_stream);
aws_http_message_set_body_stream(message, body_stream);
/* request controls the lifetime of body stream fully */
aws_input_stream_release(body_stream);
}
return result;
}
struct aws_http_message *aws_http_request_new_from_java_http_request(
JNIEnv *env,
jbyteArray marshalled_request,
jobject jni_body_stream) {
const char *exception_message = NULL;
const size_t marshalled_request_length = (*env)->GetArrayLength(env, marshalled_request);
jbyte *marshalled_request_data = (*env)->GetPrimitiveArrayCritical(env, marshalled_request, NULL);
struct aws_byte_cursor marshalled_cur =
aws_byte_cursor_from_array((uint8_t *)marshalled_request_data, marshalled_request_length);
enum aws_http_version version = s_unmarshal_http_request_to_get_version(&marshalled_cur);
struct aws_http_message *request = version == AWS_HTTP_VERSION_2
? aws_http2_message_new_request(aws_jni_get_allocator())
: aws_http_message_new_request(aws_jni_get_allocator());
int result = AWS_OP_SUCCESS;
if (version != aws_http_message_get_protocol_version(request)) {
result = aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
} else {
result = s_unmarshal_http_request_without_version(request, &marshalled_cur);
}
(*env)->ReleasePrimitiveArrayCritical(env, marshalled_request, marshalled_request_data, 0);
if (result) {
exception_message = "aws_http_request_new_from_java_http_request: Invalid marshalled request data.";
goto on_error;
}
if (jni_body_stream != NULL) {
struct aws_input_stream *body_stream =
aws_input_stream_new_from_java_http_request_body_stream(aws_jni_get_allocator(), env, jni_body_stream);
if (body_stream == NULL) {
exception_message = "aws_fill_out_request: Error building body stream";
goto on_error;
}
aws_http_message_set_body_stream(request, body_stream);
/* request controls the lifetime of body stream fully */
aws_input_stream_release(body_stream);
}
return request;
on_error:
if (exception_message) {
aws_jni_throw_runtime_exception(env, exception_message);
}
/* Don't need to destroy input stream since it's the last thing created */
aws_http_message_destroy(request);
return NULL;
}
struct aws_http_headers *aws_http_headers_new_from_java_http_headers(JNIEnv *env, jbyteArray marshalled_headers) {
struct aws_http_headers *headers = aws_http_headers_new(aws_jni_get_allocator());
if (headers == NULL) {
aws_jni_throw_runtime_exception(env, "aws_http_headers_new_from_java_http_headers: Unable to allocate headers");
return NULL;
}
const size_t marshalled_headers_length = (*env)->GetArrayLength(env, marshalled_headers);
jbyte *marshalled_headers_data = (*env)->GetPrimitiveArrayCritical(env, marshalled_headers, NULL);
struct aws_byte_cursor marshalled_cur =
aws_byte_cursor_from_array((uint8_t *)marshalled_headers_data, marshalled_headers_length);
int result = s_unmarshal_http_headers(headers, &marshalled_cur);
(*env)->ReleasePrimitiveArrayCritical(env, marshalled_headers, marshalled_headers_data, 0);
if (result) {
aws_jni_throw_runtime_exception(
env, "aws_http_headers_new_from_java_http_headers: Invalid marshalled headers data.");
goto on_error;
}
return headers;
on_error:
aws_http_headers_release(headers);
return NULL;
}
static inline int s_marshall_http_request(const struct aws_http_message *message, struct aws_byte_buf *request_buf) {
struct aws_byte_cursor method;
AWS_ZERO_STRUCT(method);
AWS_FATAL_ASSERT(!aws_http_message_get_request_method(message, &method));
struct aws_byte_cursor path;
AWS_ZERO_STRUCT(path);
AWS_FATAL_ASSERT(!aws_http_message_get_request_path(message, &path));
if (aws_byte_buf_reserve_relative(request_buf, sizeof(int) + sizeof(int) + sizeof(int) + method.len + path.len)) {
return AWS_OP_ERR;
}
aws_byte_buf_write_be32(request_buf, (uint32_t)aws_http_message_get_protocol_version(message));
aws_byte_buf_write_be32(request_buf, (uint32_t)method.len);
aws_byte_buf_write_from_whole_cursor(request_buf, method);
aws_byte_buf_write_be32(request_buf, (uint32_t)path.len);
aws_byte_buf_write_from_whole_cursor(request_buf, path);
const struct aws_http_headers *headers = aws_http_message_get_const_headers(message);
AWS_FATAL_ASSERT(headers);
size_t header_count = aws_http_message_get_header_count(message);
for (size_t i = 0; i < header_count; ++i) {
struct aws_http_header header;
AWS_ZERO_STRUCT(header);
AWS_FATAL_ASSERT(!aws_http_headers_get_index(headers, i, &header));
if (s_marshal_http_header_to_buffer(request_buf, &header.name, &header.value)) {
return AWS_OP_ERR;
}
}
return AWS_OP_SUCCESS;
}
jobject aws_java_http_request_from_native(JNIEnv *env, struct aws_http_message *message, jobject request_body_stream) {
jobject jni_request_blob = NULL;
jobject j_request = NULL;
struct aws_byte_buf marshaling_buf;
if (aws_byte_buf_init(&marshaling_buf, aws_jni_get_allocator(), 1024)) {
aws_jni_throw_runtime_exception(env, "aws_java_http_request_from_native: allocation failed");
return NULL;
}
if (s_marshall_http_request(message, &marshaling_buf)) {
aws_jni_throw_runtime_exception(
env, "aws_java_http_request_from_native: %s.", aws_error_debug_str(aws_last_error()));
goto done;
}
jni_request_blob = aws_jni_direct_byte_buffer_from_raw_ptr(env, marshaling_buf.buffer, marshaling_buf.len);
/* Currently our only use case for this does not involve a body stream. We should come back and handle this
when it's not time sensitive to do so. */
j_request = (*env)->NewObject(
env,
http_request_properties.http_request_class,
http_request_properties.constructor_method_id,
jni_request_blob,
request_body_stream);
if (aws_jni_check_and_clear_exception(env)) {
aws_raise_error(AWS_ERROR_HTTP_CALLBACK_FAILURE);
goto done;
}
done:
if (jni_request_blob) {
(*env)->DeleteLocalRef(env, jni_request_blob);
}
aws_byte_buf_clean_up(&marshaling_buf);
return j_request;
}