1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "components/cronet/android/chromium_url_request.h"
6
7#include "base/android/jni_android.h"
8#include "base/android/jni_string.h"
9#include "base/macros.h"
10#include "components/cronet/android/url_request_adapter.h"
11#include "components/cronet/android/url_request_context_adapter.h"
12#include "jni/ChromiumUrlRequest_jni.h"
13#include "net/base/net_errors.h"
14#include "net/base/request_priority.h"
15#include "net/http/http_response_headers.h"
16
17using base::android::ConvertUTF8ToJavaString;
18
19namespace cronet {
20namespace {
21
22net::RequestPriority ConvertRequestPriority(jint request_priority) {
23  switch (request_priority) {
24    case REQUEST_PRIORITY_IDLE:
25      return net::IDLE;
26    case REQUEST_PRIORITY_LOWEST:
27      return net::LOWEST;
28    case REQUEST_PRIORITY_LOW:
29      return net::LOW;
30    case REQUEST_PRIORITY_MEDIUM:
31      return net::MEDIUM;
32    case REQUEST_PRIORITY_HIGHEST:
33      return net::HIGHEST;
34    default:
35      return net::LOWEST;
36  }
37}
38
39void SetPostContentType(JNIEnv* env,
40                        URLRequestAdapter* request,
41                        jstring content_type) {
42  DCHECK(request != NULL);
43
44  std::string method_post("POST");
45  request->SetMethod(method_post);
46
47  std::string content_type_header("Content-Type");
48  std::string content_type_string(
49      base::android::ConvertJavaStringToUTF8(env, content_type));
50
51  request->AddHeader(content_type_header, content_type_string);
52}
53
54// A delegate of URLRequestAdapter that delivers callbacks to the Java layer.
55class JniURLRequestAdapterDelegate
56    : public URLRequestAdapter::URLRequestAdapterDelegate {
57 public:
58  JniURLRequestAdapterDelegate(JNIEnv* env, jobject owner) {
59    owner_ = env->NewGlobalRef(owner);
60  }
61
62  virtual void OnResponseStarted(URLRequestAdapter* request) OVERRIDE {
63    JNIEnv* env = base::android::AttachCurrentThread();
64    cronet::Java_ChromiumUrlRequest_onResponseStarted(env, owner_);
65  }
66
67  virtual void OnBytesRead(URLRequestAdapter* request) OVERRIDE {
68    int bytes_read = request->bytes_read();
69    if (bytes_read != 0) {
70      JNIEnv* env = base::android::AttachCurrentThread();
71      base::android::ScopedJavaLocalRef<jobject> java_buffer(
72          env, env->NewDirectByteBuffer(request->Data(), bytes_read));
73      cronet::Java_ChromiumUrlRequest_onBytesRead(
74          env, owner_, java_buffer.obj());
75    }
76  }
77
78  virtual void OnRequestFinished(URLRequestAdapter* request) OVERRIDE {
79    JNIEnv* env = base::android::AttachCurrentThread();
80    cronet::Java_ChromiumUrlRequest_finish(env, owner_);
81  }
82
83  virtual int ReadFromUploadChannel(net::IOBuffer* buf,
84                                    int buf_length) OVERRIDE {
85    JNIEnv* env = base::android::AttachCurrentThread();
86    base::android::ScopedJavaLocalRef<jobject> java_buffer(
87        env, env->NewDirectByteBuffer(buf->data(), buf_length));
88    jint bytes_read = cronet::Java_ChromiumUrlRequest_readFromUploadChannel(
89        env, owner_, java_buffer.obj());
90    return bytes_read;
91  }
92
93 protected:
94  virtual ~JniURLRequestAdapterDelegate() {
95    JNIEnv* env = base::android::AttachCurrentThread();
96    env->DeleteGlobalRef(owner_);
97  }
98
99 private:
100  jobject owner_;
101
102  DISALLOW_COPY_AND_ASSIGN(JniURLRequestAdapterDelegate);
103};
104
105}  // namespace
106
107// Explicitly register static JNI functions.
108bool ChromiumUrlRequestRegisterJni(JNIEnv* env) {
109  return RegisterNativesImpl(env);
110}
111
112static jlong CreateRequestAdapter(JNIEnv* env,
113                                  jobject object,
114                                  jlong urlRequestContextAdapter,
115                                  jstring url_string,
116                                  jint priority) {
117  URLRequestContextAdapter* context =
118      reinterpret_cast<URLRequestContextAdapter*>(urlRequestContextAdapter);
119  DCHECK(context != NULL);
120
121  GURL url(base::android::ConvertJavaStringToUTF8(env, url_string));
122
123  VLOG(1) << "New chromium network request: " << url.possibly_invalid_spec();
124
125  URLRequestAdapter* adapter =
126      new URLRequestAdapter(context,
127                            new JniURLRequestAdapterDelegate(env, object),
128                            url,
129                            ConvertRequestPriority(priority));
130
131  return reinterpret_cast<jlong>(adapter);
132}
133
134// synchronized
135static void AddHeader(JNIEnv* env,
136                      jobject object,
137                      jlong urlRequestAdapter,
138                      jstring name,
139                      jstring value) {
140  URLRequestAdapter* request =
141      reinterpret_cast<URLRequestAdapter*>(urlRequestAdapter);
142  DCHECK(request);
143
144  std::string name_string(base::android::ConvertJavaStringToUTF8(env, name));
145  std::string value_string(base::android::ConvertJavaStringToUTF8(env, value));
146
147  request->AddHeader(name_string, value_string);
148}
149
150static void SetMethod(JNIEnv* env,
151                      jobject object,
152                      jlong urlRequestAdapter,
153                      jstring method) {
154  URLRequestAdapter* request =
155      reinterpret_cast<URLRequestAdapter*>(urlRequestAdapter);
156  DCHECK(request);
157
158  std::string method_string(
159      base::android::ConvertJavaStringToUTF8(env, method));
160
161  request->SetMethod(method_string);
162}
163
164static void SetUploadData(JNIEnv* env,
165                          jobject object,
166                          jlong urlRequestAdapter,
167                          jstring content_type,
168                          jbyteArray content) {
169  URLRequestAdapter* request =
170      reinterpret_cast<URLRequestAdapter*>(urlRequestAdapter);
171  SetPostContentType(env, request, content_type);
172
173  if (content != NULL) {
174    jsize size = env->GetArrayLength(content);
175    if (size > 0) {
176      jbyte* content_bytes = env->GetByteArrayElements(content, NULL);
177      request->SetUploadContent(reinterpret_cast<const char*>(content_bytes),
178                                size);
179      env->ReleaseByteArrayElements(content, content_bytes, 0);
180    }
181  }
182}
183
184static void SetUploadChannel(JNIEnv* env,
185                             jobject object,
186                             jlong urlRequestAdapter,
187                             jstring content_type,
188                             jlong content_length) {
189  URLRequestAdapter* request =
190      reinterpret_cast<URLRequestAdapter*>(urlRequestAdapter);
191  SetPostContentType(env, request, content_type);
192
193  request->SetUploadChannel(env, content_length);
194}
195
196static void EnableChunkedUpload(JNIEnv* env,
197                               jobject object,
198                               jlong urlRequestAdapter,
199                               jstring content_type) {
200  URLRequestAdapter* request =
201      reinterpret_cast<URLRequestAdapter*>(urlRequestAdapter);
202  SetPostContentType(env, request, content_type);
203
204  request->EnableChunkedUpload();
205}
206
207static void AppendChunk(JNIEnv* env,
208                        jobject object,
209                        jlong urlRequestAdapter,
210                        jobject chunk_byte_buffer,
211                        jint chunk_size,
212                        jboolean is_last_chunk) {
213  URLRequestAdapter* request =
214      reinterpret_cast<URLRequestAdapter*>(urlRequestAdapter);
215  DCHECK(chunk_byte_buffer != NULL);
216
217  void* chunk = env->GetDirectBufferAddress(chunk_byte_buffer);
218  request->AppendChunk(
219      reinterpret_cast<const char*>(chunk), chunk_size, is_last_chunk);
220}
221
222/* synchronized */
223static void Start(JNIEnv* env, jobject object, jlong urlRequestAdapter) {
224  URLRequestAdapter* request =
225      reinterpret_cast<URLRequestAdapter*>(urlRequestAdapter);
226  if (request != NULL) {
227    request->Start();
228  }
229}
230
231/* synchronized */
232static void DestroyRequestAdapter(JNIEnv* env,
233                                  jobject object,
234                                  jlong urlRequestAdapter) {
235  URLRequestAdapter* request =
236      reinterpret_cast<URLRequestAdapter*>(urlRequestAdapter);
237  if (request != NULL) {
238    request->Destroy();
239  }
240}
241
242/* synchronized */
243static void Cancel(JNIEnv* env, jobject object, jlong urlRequestAdapter) {
244  URLRequestAdapter* request =
245      reinterpret_cast<URLRequestAdapter*>(urlRequestAdapter);
246  if (request != NULL) {
247    request->Cancel();
248  }
249}
250
251static jint GetErrorCode(JNIEnv* env, jobject object, jlong urlRequestAdapter) {
252  URLRequestAdapter* request =
253      reinterpret_cast<URLRequestAdapter*>(urlRequestAdapter);
254  int error_code = request->error_code();
255  switch (error_code) {
256    // TODO(mef): Investigate returning success on positive values, too, as
257    // they technically indicate success.
258    case net::OK:
259      return REQUEST_ERROR_SUCCESS;
260
261    // TODO(mef): Investigate this. The fact is that Chrome does not do this,
262    // and this library is not just being used for downloads.
263
264    // Comment from src/content/browser/download/download_resource_handler.cc:
265    // ERR_CONTENT_LENGTH_MISMATCH and ERR_INCOMPLETE_CHUNKED_ENCODING are
266    // allowed since a number of servers in the wild close the connection too
267    // early by mistake. Other browsers - IE9, Firefox 11.0, and Safari 5.1.4 -
268    // treat downloads as complete in both cases, so we follow their lead.
269    case net::ERR_CONTENT_LENGTH_MISMATCH:
270    case net::ERR_INCOMPLETE_CHUNKED_ENCODING:
271      return REQUEST_ERROR_SUCCESS;
272
273    case net::ERR_INVALID_URL:
274    case net::ERR_DISALLOWED_URL_SCHEME:
275    case net::ERR_UNKNOWN_URL_SCHEME:
276      return REQUEST_ERROR_MALFORMED_URL;
277
278    case net::ERR_CONNECTION_TIMED_OUT:
279      return REQUEST_ERROR_CONNECTION_TIMED_OUT;
280
281    case net::ERR_NAME_NOT_RESOLVED:
282      return REQUEST_ERROR_UNKNOWN_HOST;
283  }
284  return REQUEST_ERROR_UNKNOWN;
285}
286
287static jstring GetErrorString(JNIEnv* env,
288                              jobject object,
289                              jlong urlRequestAdapter) {
290  URLRequestAdapter* request =
291      reinterpret_cast<URLRequestAdapter*>(urlRequestAdapter);
292  int error_code = request->error_code();
293  char buffer[200];
294  std::string error_string = net::ErrorToString(error_code);
295  snprintf(buffer,
296           sizeof(buffer),
297           "System error: %s(%d)",
298           error_string.c_str(),
299           error_code);
300  return ConvertUTF8ToJavaString(env, buffer).Release();
301}
302
303static jint GetHttpStatusCode(JNIEnv* env,
304                              jobject object,
305                              jlong urlRequestAdapter) {
306  URLRequestAdapter* request =
307      reinterpret_cast<URLRequestAdapter*>(urlRequestAdapter);
308  return request->http_status_code();
309}
310
311static jstring GetContentType(JNIEnv* env,
312                              jobject object,
313                              jlong urlRequestAdapter) {
314  URLRequestAdapter* request =
315      reinterpret_cast<URLRequestAdapter*>(urlRequestAdapter);
316  if (request == NULL) {
317    return NULL;
318  }
319  std::string type = request->content_type();
320  if (!type.empty()) {
321    return ConvertUTF8ToJavaString(env, type.c_str()).Release();
322  } else {
323    return NULL;
324  }
325}
326
327static jlong GetContentLength(JNIEnv* env,
328                              jobject object,
329                              jlong urlRequestAdapter) {
330  URLRequestAdapter* request =
331      reinterpret_cast<URLRequestAdapter*>(urlRequestAdapter);
332  if (request == NULL) {
333    return 0;
334  }
335  return request->content_length();
336}
337
338static jstring GetHeader(JNIEnv* env,
339                         jobject object,
340                         jlong urlRequestAdapter,
341                         jstring name) {
342  URLRequestAdapter* request =
343      reinterpret_cast<URLRequestAdapter*>(urlRequestAdapter);
344  if (request == NULL) {
345    return NULL;
346  }
347
348  std::string name_string = base::android::ConvertJavaStringToUTF8(env, name);
349  std::string value = request->GetHeader(name_string);
350  if (!value.empty()) {
351    return ConvertUTF8ToJavaString(env, value.c_str()).Release();
352  } else {
353    return NULL;
354  }
355}
356
357static void GetAllHeaders(JNIEnv* env,
358                          jobject object,
359                          jlong urlRequestAdapter,
360                          jobject headersMap) {
361  URLRequestAdapter* request =
362      reinterpret_cast<URLRequestAdapter*>(urlRequestAdapter);
363  if (request == NULL)
364    return;
365
366  net::HttpResponseHeaders* headers = request->GetResponseHeaders();
367  if (headers == NULL)
368    return;
369
370  void* iter = NULL;
371  std::string header_name;
372  std::string header_value;
373  while (headers->EnumerateHeaderLines(&iter, &header_name, &header_value)) {
374    ScopedJavaLocalRef<jstring> name =
375        ConvertUTF8ToJavaString(env, header_name);
376    ScopedJavaLocalRef<jstring> value =
377        ConvertUTF8ToJavaString(env, header_value);
378    Java_ChromiumUrlRequest_onAppendResponseHeader(
379        env, object, headersMap, name.Release(), value.Release());
380  }
381
382  // Some implementations (notably HttpURLConnection) include a mapping for the
383  // null key; in HTTP's case, this maps to the HTTP status line.
384  ScopedJavaLocalRef<jstring> status_line =
385      ConvertUTF8ToJavaString(env, headers->GetStatusLine());
386  Java_ChromiumUrlRequest_onAppendResponseHeader(
387      env, object, headersMap, NULL, status_line.Release());
388}
389
390static jstring GetNegotiatedProtocol(JNIEnv* env,
391                                     jobject object,
392                                     jlong urlRequestAdapter) {
393  URLRequestAdapter* request =
394      reinterpret_cast<URLRequestAdapter*>(urlRequestAdapter);
395  if (request == NULL)
396    return ConvertUTF8ToJavaString(env, "").Release();
397
398  std::string negotiated_protocol = request->GetNegotiatedProtocol();
399  return ConvertUTF8ToJavaString(env, negotiated_protocol.c_str()).Release();
400}
401
402}  // namespace cronet
403