1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
|
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "components/embedder_support/android/util/input_stream.h"
#include "base/android/jni_android.h"
#include "base/feature_list.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
// Disable "Warnings treated as errors" for input_stream_jni as it's a Java
// system class and we have to generate C++ hooks for all methods in the class
// even if they're unused.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#pragma GCC diagnostic pop
#include "net/base/io_buffer.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "components/embedder_support/android/util_jni_headers/InputStreamUtil_jni.h"
using base::android::AttachCurrentThread;
using base::android::ClearException;
using base::android::JavaRef;
namespace embedder_support {
namespace {
// This should be the same as InputStramUtil.EXCEPTION_THROWN_STATUS.
const int kExceptionThrownStatusCode = -2;
} // namespace
// Experiment to control the size of the intermediate buffer used to copy from
// Java's InputStream into C++'s net::IOBuffer.
BASE_FEATURE(kEnableCustomInputStreamBufferSize,
"EnableCustomInputStreamBufferSize",
base::FEATURE_DISABLED_BY_DEFAULT);
// Effectively the maximum number of bytes that will be copied during a JNI call
// to Java_InputStreamUtil_read.
const base::FeatureParam<int> kBufferSize{&kEnableCustomInputStreamBufferSize,
"BufferSize", 4096};
// static
int InputStream::GetIntermediateBufferSize() {
return kBufferSize.Get();
}
// TODO: Use unsafe version for all Java_InputStream methods in this file
// once BUG 157880 is fixed and implement graceful exception handling.
InputStream::InputStream() = default;
InputStream::InputStream(const JavaRef<jobject>& stream) : jobject_(stream) {
DCHECK(stream);
}
InputStream::~InputStream() {
base::UmaHistogramCounts10000("Android.InputStream.TotalRead.SizeKB",
total_bytes_read_ / 1024);
JNIEnv* env = AttachCurrentThread();
if (jobject_.obj())
Java_InputStreamUtil_close(env, jobject_);
}
bool InputStream::BytesAvailable(int* bytes_available) const {
JNIEnv* env = AttachCurrentThread();
int bytes = Java_InputStreamUtil_available(env, jobject_);
if (bytes == kExceptionThrownStatusCode)
return false;
*bytes_available = bytes;
return true;
}
bool InputStream::Skip(int64_t n, int64_t* bytes_skipped) {
JNIEnv* env = AttachCurrentThread();
int bytes = Java_InputStreamUtil_skip(env, jobject_, n);
if (bytes < 0)
return false;
if (bytes > n)
return false;
*bytes_skipped = bytes;
return true;
}
bool InputStream::Read(net::IOBuffer* dest, int length, int* bytes_read) {
JNIEnv* env = AttachCurrentThread();
if (!buffer_.obj()) {
// Allocate transfer buffer.
base::android::ScopedJavaLocalRef<jbyteArray> temp(
env, env->NewByteArray(GetIntermediateBufferSize()));
buffer_.Reset(temp);
if (ClearException(env))
return false;
}
int remaining_length = length;
char* dest_write_ptr = dest->data();
*bytes_read = 0;
while (remaining_length > 0) {
const int max_transfer_length =
std::min(remaining_length, GetIntermediateBufferSize());
const int transfer_length = Java_InputStreamUtil_read(
env, jobject_, buffer_, 0, max_transfer_length);
if (transfer_length == kExceptionThrownStatusCode)
return false;
if (transfer_length < 0) // EOF
break;
// Note: it is possible, yet unlikely, that the Java InputStream returns
// a transfer_length == 0 from time to time. In such cases we just continue
// the read until we get either valid data or reach EOF.
if (transfer_length == 0)
continue;
DCHECK_GE(max_transfer_length, transfer_length);
DCHECK_GE(env->GetArrayLength(buffer_.obj()), transfer_length);
// This check is to prevent a malicious InputStream implementation from
// overrunning the |dest| buffer.
if (transfer_length > max_transfer_length)
return false;
// Copy the data over to the provided C++ IOBuffer.
DCHECK_GE(remaining_length, transfer_length);
env->GetByteArrayRegion(buffer_.obj(), 0, transfer_length,
reinterpret_cast<jbyte*>(dest_write_ptr));
if (ClearException(env))
return false;
remaining_length -= transfer_length;
dest_write_ptr += transfer_length;
}
// bytes_read can be strictly less than the req. length if EOF is encountered.
DCHECK_GE(remaining_length, 0);
DCHECK_LE(remaining_length, length);
*bytes_read = length - remaining_length;
total_bytes_read_ += *bytes_read;
return true;
}
} // namespace embedder_support
|