1 /*
   2  * Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved.
   3  */
   4 #include "config.h"
   5 
   6 #if COMPILER(GCC)
   7 #pragma GCC diagnostic ignored "-Wunused-parameter"
   8 #endif
   9 
  10 #include "URLLoader.h"
  11 #include "FrameNetworkingContextJava.h"
  12 #include "HTTPParsers.h"
  13 #include <wtf/CompletionHandler.h>
  14 #include <wtf/java/JavaEnv.h>
  15 #include "MIMETypeRegistry.h"
  16 #include "ResourceError.h"
  17 #include "ResourceHandle.h"
  18 #include "ResourceRequest.h"
  19 #include "ResourceResponse.h"
  20 #include "ResourceHandleClient.h"
  21 #include "WebPage.h"
  22 #include "com_sun_webkit_LoadListenerClient.h"
  23 #include "com_sun_webkit_network_URLLoader.h"
  24 
  25 namespace WebCore {
  26 class Page;
  27 }
  28 
  29 namespace WebCore {
  30 
  31 namespace URLLoaderJavaInternal {
  32 
  33 static JGClass networkContextClass;
  34 static jmethodID loadMethod;
  35 
  36 static JGClass urlLoaderClass;
  37 static jmethodID cancelMethod;
  38 
  39 static JGClass formDataElementClass;
  40 static jmethodID createFromFileMethod;
  41 static jmethodID createFromByteArrayMethod;
  42 
  43 static void initRefs(JNIEnv* env)
  44 {
  45     if (!networkContextClass) {
  46         networkContextClass = JLClass(env->FindClass(
  47                 "com/sun/webkit/network/NetworkContext"));
  48         ASSERT(networkContextClass);
  49 
  50         loadMethod = env->GetStaticMethodID(
  51                 networkContextClass,
  52                 "fwkLoad",
  53                 "(Lcom/sun/webkit/WebPage;Z"
  54                 "Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;"
  55                 "[Lcom/sun/webkit/network/FormDataElement;J)"
  56                 "Lcom/sun/webkit/network/URLLoader;");
  57         ASSERT(loadMethod);
  58     }
  59     if (!urlLoaderClass) {
  60         urlLoaderClass = JLClass(env->FindClass(
  61                 "com/sun/webkit/network/URLLoader"));
  62         ASSERT(urlLoaderClass);
  63 
  64         cancelMethod = env->GetMethodID(urlLoaderClass, "fwkCancel", "()V");
  65         ASSERT(cancelMethod);
  66     }
  67     if (!formDataElementClass) {
  68         formDataElementClass = JLClass(env->FindClass(
  69                 "com/sun/webkit/network/FormDataElement"));
  70         ASSERT(formDataElementClass);
  71 
  72         createFromByteArrayMethod = env->GetStaticMethodID(
  73                 formDataElementClass,
  74                 "fwkCreateFromByteArray",
  75                 "([B)Lcom/sun/webkit/network/FormDataElement;");
  76         ASSERT(createFromByteArrayMethod);
  77 
  78         createFromFileMethod = env->GetStaticMethodID(
  79                 formDataElementClass,
  80                 "fwkCreateFromFile",
  81                 "(Ljava/lang/String;)"
  82                 "Lcom/sun/webkit/network/FormDataElement;");
  83         ASSERT(createFromFileMethod);
  84     }
  85 }
  86 
  87 static bool shouldRedirectAsGET(const ResourceRequest& request, const ResourceResponse& response, bool crossOrigin)
  88 {
  89     if (request.httpMethod() == "GET" || request.httpMethod() == "HEAD")
  90         return false;
  91 
  92     if (!request.url().protocolIsInHTTPFamily())
  93         return true;
  94 
  95     if (response.isSeeOther())
  96         return true;
  97 
  98     if ((response.isMovedPermanently() || response.isFound()) && (request.httpMethod() == "POST"))
  99         return true;
 100 
 101     if (crossOrigin && (request.httpMethod() == "DELETE"))
 102         return true;
 103 
 104     return false;
 105 }
 106 }
 107 
 108 URLLoader::URLLoader()
 109 {
 110 }
 111 
 112 URLLoader::~URLLoader()
 113 {
 114     cancel();
 115 }
 116 
 117 std::unique_ptr<URLLoader> URLLoader::loadAsynchronously(NetworkingContext* context,
 118                                                     ResourceHandle* handle)
 119 {
 120     std::unique_ptr<URLLoader> result = std::unique_ptr<URLLoader>(new URLLoader());
 121     result->m_target = std::unique_ptr<AsynchronousTarget>(new AsynchronousTarget(handle));
 122     result->m_ref = load(
 123             true,
 124             context,
 125             handle->firstRequest(),
 126             result->m_target.get());
 127     return result;
 128 }
 129 
 130 void URLLoader::cancel()
 131 {
 132     using namespace URLLoaderJavaInternal;
 133     if (m_ref) {
 134         JNIEnv* env = WebCore_GetJavaEnv();
 135         initRefs(env);
 136 
 137         env->CallVoidMethod(m_ref, cancelMethod);
 138         CheckAndClearException(env);
 139 
 140         m_ref.clear();
 141     }
 142 }
 143 
 144 void URLLoader::loadSynchronously(NetworkingContext* context,
 145                                   const ResourceRequest& request,
 146                                   ResourceError& error,
 147                                   ResourceResponse& response,
 148                                   Vector<char>& data)
 149 {
 150     SynchronousTarget target(request, error, response, data);
 151     load(false, context, request, &target);
 152 }
 153 
 154 JLObject URLLoader::load(bool asynchronous,
 155                          NetworkingContext* context,
 156                          const ResourceRequest& request,
 157                          Target* target)
 158 {
 159     using namespace URLLoaderJavaInternal;
 160     if (!context) {
 161         return nullptr;
 162     }
 163 
 164     if (!context->isValid()) {
 165         // If NetworkingContext is invalid then we are no longer attached
 166         // to a Page. This must be an attempt to load from an unload handler,
 167         // so let's just block it.
 168         return nullptr;
 169     }
 170 
 171     Page* page = static_cast<FrameNetworkingContextJava*>(context)->page();
 172     ASSERT(page);
 173 
 174     JLObject webPage = WebPage::jobjectFromPage(page);
 175     ASSERT(webPage);
 176 
 177     String headerString;
 178     const HTTPHeaderMap& headerMap = request.httpHeaderFields();
 179     for (
 180         HTTPHeaderMap::const_iterator it = headerMap.begin();
 181         headerMap.end() != it;
 182         ++it)
 183     {
 184         headerString.append(it->key);
 185         headerString.append(": ");
 186         headerString.append(it->value);
 187         headerString.append("\n");
 188     }
 189 
 190     JNIEnv* env = WebCore_GetJavaEnv();
 191     initRefs(env);
 192 
 193     JLObject loader = env->CallStaticObjectMethod(
 194             networkContextClass,
 195             loadMethod,
 196             (jobject) webPage,
 197             bool_to_jbool(asynchronous),
 198             (jstring) request.url().string().toJavaString(env),
 199             (jstring) request.httpMethod().toJavaString(env),
 200             (jstring) headerString.toJavaString(env),
 201             (jobjectArray) toJava(request.httpBody()),
 202             ptr_to_jlong(target));
 203     CheckAndClearException(env);
 204 
 205     return loader;
 206 }
 207 
 208 JLObjectArray URLLoader::toJava(const FormData* formData)
 209 {
 210     using namespace URLLoaderJavaInternal;
 211     if (!formData) {
 212         return nullptr;
 213     }
 214 
 215     const Vector<FormDataElement>& elements = formData->elements();
 216     size_t size = elements.size();
 217     if (size == 0) {
 218         return nullptr;
 219     }
 220 
 221     JNIEnv* env = WebCore_GetJavaEnv();
 222     initRefs(env);
 223 
 224     JLObjectArray result = env->NewObjectArray(
 225             size,
 226             formDataElementClass,
 227             nullptr);
 228     for (size_t i = 0; i < size; i++) {
 229         JLObject resultElement;
 230         if (elements[i].m_type == FormDataElement::Type::EncodedFile) {
 231             resultElement = env->CallStaticObjectMethod(
 232                     formDataElementClass,
 233                     createFromFileMethod,
 234                     (jstring) elements[i].m_filename.toJavaString(env));
 235         } else {
 236             const Vector<char>& data = elements[i].m_data;
 237             JLByteArray byteArray = env->NewByteArray(data.size());
 238             env->SetByteArrayRegion(
 239                     (jbyteArray) byteArray,
 240                     (jsize) 0,
 241                     (jsize) data.size(),
 242                     (const jbyte*) data.data());
 243             resultElement = env->CallStaticObjectMethod(
 244                     formDataElementClass,
 245                     createFromByteArrayMethod,
 246                     (jbyteArray) byteArray);
 247         }
 248         env->SetObjectArrayElement(
 249                 (jobjectArray) result,
 250                 i,
 251                 (jobject) resultElement);
 252     }
 253 
 254     return result;
 255 }
 256 
 257 URLLoader::Target::~Target()
 258 {
 259 }
 260 
 261 URLLoader::AsynchronousTarget::AsynchronousTarget(ResourceHandle* handle)
 262     : m_handle(handle)
 263 {
 264 }
 265 
 266 void URLLoader::AsynchronousTarget::didSendData(long totalBytesSent,
 267                                                 long totalBytesToBeSent)
 268 {
 269     ResourceHandleClient* client = m_handle->client();
 270     if (client) {
 271         client->didSendData(m_handle, totalBytesSent, totalBytesToBeSent);
 272     }
 273 }
 274 
 275 
 276 bool URLLoader::AsynchronousTarget::willSendRequest(
 277         const String& newUrl,
 278         const String& newMethod,
 279         const ResourceResponse& response)
 280 {
 281     using namespace URLLoaderJavaInternal;
 282     ASSERT(isMainThread());
 283     ResourceHandleClient* client = m_handle->client();
 284     if (client) {
 285         ResourceRequest request = m_handle->firstRequest();
 286         String location = response.httpHeaderField(HTTPHeaderName::Location);
 287         URL newURL = URL(URL(), newUrl);
 288         bool crossOrigin = !protocolHostAndPortAreEqual(request.url(), newURL);
 289 
 290         ResourceRequest newRequest = request;
 291         newRequest.setURL(newURL);
 292 
 293         if (shouldRedirectAsGET(newRequest, response, crossOrigin)) {
 294             newRequest.setHTTPMethod("GET");
 295             newRequest.setHTTPBody(nullptr);
 296             newRequest.clearHTTPContentType();
 297         } else {
 298             newRequest.setHTTPMethod(newMethod);
 299         }
 300 
 301         // Should not set Referer after a redirect from a secure resource to non-secure one.
 302         if (!newURL.protocolIs("https") && protocolIs(newRequest.httpReferrer(), "https") && m_handle->context()->shouldClearReferrerOnHTTPSToHTTPRedirect())
 303             newRequest.clearHTTPReferrer();
 304 
 305         client->willSendRequestAsync(m_handle, WTFMove(newRequest), ResourceResponse(response), [] (ResourceRequest&&) {
 306         });
 307     }
 308     return true;
 309 }
 310 
 311 void URLLoader::AsynchronousTarget::didReceiveResponse(
 312         const ResourceResponse& response)
 313 {
 314     ResourceHandleClient* client = m_handle->client();
 315     if (client) {
 316         client->didReceiveResponseAsync(m_handle, ResourceResponse(response), [] () {
 317         });
 318     }
 319 }
 320 
 321 void URLLoader::AsynchronousTarget::didReceiveData(const char* data, int length)
 322 {
 323     ResourceHandleClient* client = m_handle->client();
 324     if (client) {
 325         client->didReceiveData(m_handle, data, length, 0);
 326     }
 327 }
 328 
 329 void URLLoader::AsynchronousTarget::didFinishLoading()
 330 {
 331     ResourceHandleClient* client = m_handle->client();
 332     if (client) {
 333         client->didFinishLoading(m_handle);
 334     }
 335 }
 336 
 337 void URLLoader::AsynchronousTarget::didFail(const ResourceError& error)
 338 {
 339     ResourceHandleClient* client = m_handle->client();
 340     if (client) {
 341         client->didFail(m_handle, error);
 342     }
 343 }
 344 
 345 URLLoader::SynchronousTarget::SynchronousTarget(const ResourceRequest& request,
 346                                                 ResourceError& error,
 347                                                 ResourceResponse& response,
 348                                                 Vector<char>& data)
 349     : m_request(request)
 350     , m_error(error)
 351     , m_response(response)
 352     , m_data(data)
 353 {
 354     m_error = ResourceError();
 355 }
 356 
 357 void URLLoader::SynchronousTarget::didSendData(long, long)
 358 {
 359 }
 360 
 361 bool URLLoader::SynchronousTarget::willSendRequest(
 362         const String& newUrl,
 363         const String&,
 364         const ResourceResponse&)
 365 {
 366     // The following code was adapted from the Windows port
 367     // FIXME: This needs to be fixed to follow redirects correctly even
 368     // for cross-domain requests
 369     if (!protocolHostAndPortAreEqual(m_request.url(), URL(URL(), newUrl))) {
 370         didFail(ResourceError(
 371                 String(),
 372                 com_sun_webkit_LoadListenerClient_INVALID_RESPONSE,
 373                 m_request.url(),
 374                 "Illegal redirect"));
 375         return false;
 376     }
 377     return true;
 378 }
 379 
 380 void URLLoader::SynchronousTarget::didReceiveResponse(
 381         const ResourceResponse& response)
 382 {
 383     m_response = response;
 384 }
 385 
 386 void URLLoader::SynchronousTarget::didReceiveData(const char* data, int length)
 387 {
 388     m_data.append(data, length);
 389 }
 390 
 391 void URLLoader::SynchronousTarget::didFinishLoading()
 392 {
 393 }
 394 
 395 void URLLoader::SynchronousTarget::didFail(const ResourceError& error)
 396 {
 397     m_error = error;
 398     m_response.setHTTPStatusCode(404);
 399 }
 400 
 401 } // namespace WebCore
 402 
 403 using namespace WebCore;
 404 
 405 #ifdef __cplusplus
 406 extern "C" {
 407 #endif
 408 
 409 static void setupResponse(ResourceResponse& response,
 410                           JNIEnv* env,
 411                           jint status,
 412                           jstring contentType,
 413                           jstring contentEncoding,
 414                           jlong contentLength,
 415                           jstring headers,
 416                           jstring url)
 417 {
 418     if (status > 0) {
 419         response.setHTTPStatusCode(status);
 420     }
 421 
 422     // Fix for RT-13802: If the mime type is not specified
 423     // and the expected content length is 0 or not specified,
 424     // set the mime type to "text/html" as e.g. the CF port
 425     // does
 426     String contentTypeString(env, contentType);
 427     if (contentTypeString.isEmpty() && contentLength <= 0) {
 428         contentTypeString = "text/html";
 429     }
 430     if (!contentTypeString.isEmpty()) {
 431         response.setMimeType(
 432                 extractMIMETypeFromMediaType(contentTypeString).convertToLowercaseWithoutLocale());
 433     }
 434 
 435     String contentEncodingString(env, contentEncoding);
 436     if (contentEncodingString.isEmpty() && !contentTypeString.isEmpty()) {
 437         contentEncodingString = extractCharsetFromMediaType(contentTypeString);
 438     }
 439     if (!contentEncodingString.isEmpty()) {
 440         response.setTextEncodingName(contentEncodingString);
 441     }
 442 
 443     if (contentLength > 0) {
 444         response.setExpectedContentLength(
 445                 static_cast<long long>(contentLength));
 446     }
 447 
 448     String headersString(env, headers);
 449     int splitPos = headersString.find("\n");
 450     while (splitPos != -1) {
 451         String s = headersString.left(splitPos);
 452         int j = s.find(":");
 453         if (j != -1) {
 454             String key = s.left(j);
 455             String val = s.substring(j + 1);
 456             response.setHTTPHeaderField(key, val);
 457         }
 458         headersString = headersString.substring(splitPos + 1);
 459         splitPos = headersString.find("\n");
 460     }
 461 
 462     URL kurl = URL(URL(), String(env, url));
 463     response.setURL(kurl);
 464 
 465     // Setup mime type for local resources
 466     if (/*kurl.hasPath()*/kurl.pathEnd() != kurl.pathStart() && kurl.protocol() == String("file")) {
 467         response.setMimeType(MIMETypeRegistry::getMIMETypeForPath(kurl.path()));
 468     }
 469 }
 470 
 471 JNIEXPORT void JNICALL Java_com_sun_webkit_network_URLLoader_twkDidSendData
 472   (JNIEnv*, jclass, jlong totalBytesSent, jlong totalBytesToBeSent, jlong data)
 473 {
 474     URLLoader::Target* target =
 475             static_cast<URLLoader::Target*>(jlong_to_ptr(data));
 476     ASSERT(target);
 477     target->didSendData(totalBytesSent, totalBytesToBeSent);
 478 }
 479 
 480 JNIEXPORT jboolean JNICALL Java_com_sun_webkit_network_URLLoader_twkWillSendRequest
 481   (JNIEnv* env, jclass, jstring newUrl, jstring newMethod, jint status,
 482    jstring contentType, jstring contentEncoding, jlong contentLength,
 483    jstring headers, jstring url, jlong data)
 484 {
 485     URLLoader::Target* target =
 486             static_cast<URLLoader::Target*>(jlong_to_ptr(data));
 487     ASSERT(target);
 488 
 489     ResourceResponse response;
 490     setupResponse(
 491             response,
 492             env,
 493             status,
 494             contentType,
 495             contentEncoding,
 496             contentLength,
 497             headers,
 498             url);
 499 
 500     return bool_to_jbool(target->willSendRequest(
 501             String(env, newUrl),
 502             String(env, newMethod),
 503             response));
 504 }
 505 
 506 JNIEXPORT void JNICALL Java_com_sun_webkit_network_URLLoader_twkDidReceiveResponse
 507   (JNIEnv* env, jclass, jint status, jstring contentType,
 508    jstring contentEncoding, jlong contentLength, jstring headers,
 509    jstring url, jlong data)
 510 {
 511     URLLoader::Target* target =
 512             static_cast<URLLoader::Target*>(jlong_to_ptr(data));
 513     ASSERT(target);
 514 
 515     ResourceResponse response;
 516     setupResponse(
 517             response,
 518             env,
 519             status,
 520             contentType,
 521             contentEncoding,
 522             contentLength,
 523             headers,
 524             url);
 525 
 526     target->didReceiveResponse(response);
 527 }
 528 
 529 JNIEXPORT void JNICALL Java_com_sun_webkit_network_URLLoader_twkDidReceiveData
 530   (JNIEnv* env, jclass, jobject byteBuffer, jint position, jint remaining,
 531    jlong data)
 532 {
 533     URLLoader::Target* target =
 534             static_cast<URLLoader::Target*>(jlong_to_ptr(data));
 535     ASSERT(target);
 536     const char* address =
 537             static_cast<const char*>(env->GetDirectBufferAddress(byteBuffer));
 538     target->didReceiveData(address + position, remaining);
 539 }
 540 
 541 JNIEXPORT void JNICALL Java_com_sun_webkit_network_URLLoader_twkDidFinishLoading
 542   (JNIEnv*, jclass, jlong data)
 543 {
 544     URLLoader::Target* target =
 545             static_cast<URLLoader::Target*>(jlong_to_ptr(data));
 546     ASSERT(target);
 547     target->didFinishLoading();
 548 }
 549 
 550 JNIEXPORT void JNICALL Java_com_sun_webkit_network_URLLoader_twkDidFail
 551   (JNIEnv* env, jclass, jint errorCode, jstring url, jstring message,
 552    jlong data)
 553 {
 554     URLLoader::Target* target =
 555             static_cast<URLLoader::Target*>(jlong_to_ptr(data));
 556     ASSERT(target);
 557     target->didFail(ResourceError(
 558             String(),
 559             errorCode,
 560             URL(env, url),
 561             String(env, message)));
 562 }
 563 
 564 #ifdef __cplusplus
 565 }
 566 #endif