/** * 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 #include #include #include #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; }