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