1 /* 2 * Copyright (C) 2011, 2012 Google Inc. All rights reserved. 3 * Copyright (C) 2013, Intel Corporation 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * * Redistributions in binary form must reproduce the above 12 * copyright notice, this list of conditions and the following disclaimer 13 * in the documentation and/or other materials provided with the 14 * distribution. 15 * * Neither the name of Google Inc. nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #include "config.h" 33 #include "core/loader/DocumentThreadableLoader.h" 34 35 #include "core/dom/Document.h" 36 #include "core/fetch/CrossOriginAccessControl.h" 37 #include "core/fetch/FetchRequest.h" 38 #include "core/fetch/Resource.h" 39 #include "core/fetch/ResourceFetcher.h" 40 #include "core/frame/LocalFrame.h" 41 #include "core/frame/csp/ContentSecurityPolicy.h" 42 #include "core/inspector/InspectorInstrumentation.h" 43 #include "core/inspector/InspectorTraceEvents.h" 44 #include "core/loader/CrossOriginPreflightResultCache.h" 45 #include "core/loader/DocumentThreadableLoaderClient.h" 46 #include "core/loader/FrameLoader.h" 47 #include "core/loader/ThreadableLoaderClient.h" 48 #include "platform/SharedBuffer.h" 49 #include "platform/network/ResourceRequest.h" 50 #include "platform/weborigin/SchemeRegistry.h" 51 #include "platform/weborigin/SecurityOrigin.h" 52 #include "wtf/Assertions.h" 53 54 namespace WebCore { 55 56 void DocumentThreadableLoader::loadResourceSynchronously(Document& document, const ResourceRequest& request, ThreadableLoaderClient& client, const ThreadableLoaderOptions& options, const ResourceLoaderOptions& resourceLoaderOptions) 57 { 58 // The loader will be deleted as soon as this function exits. 59 RefPtr<DocumentThreadableLoader> loader = adoptRef(new DocumentThreadableLoader(document, &client, LoadSynchronously, request, options, resourceLoaderOptions)); 60 ASSERT(loader->hasOneRef()); 61 } 62 63 PassRefPtr<DocumentThreadableLoader> DocumentThreadableLoader::create(Document& document, ThreadableLoaderClient* client, const ResourceRequest& request, const ThreadableLoaderOptions& options, const ResourceLoaderOptions& resourceLoaderOptions) 64 { 65 RefPtr<DocumentThreadableLoader> loader = adoptRef(new DocumentThreadableLoader(document, client, LoadAsynchronously, request, options, resourceLoaderOptions)); 66 if (!loader->resource()) 67 loader = nullptr; 68 return loader.release(); 69 } 70 71 DocumentThreadableLoader::DocumentThreadableLoader(Document& document, ThreadableLoaderClient* client, BlockingBehavior blockingBehavior, const ResourceRequest& request, const ThreadableLoaderOptions& options, const ResourceLoaderOptions& resourceLoaderOptions) 72 : m_client(client) 73 , m_document(document) 74 , m_options(options) 75 , m_resourceLoaderOptions(resourceLoaderOptions) 76 , m_forceDoNotAllowStoredCredentials(false) 77 , m_securityOrigin(m_resourceLoaderOptions.securityOrigin) 78 , m_sameOriginRequest(securityOrigin()->canRequest(request.url())) 79 , m_simpleRequest(true) 80 , m_async(blockingBehavior == LoadAsynchronously) 81 , m_timeoutTimer(this, &DocumentThreadableLoader::didTimeout) 82 { 83 ASSERT(client); 84 // Setting an outgoing referer is only supported in the async code path. 85 ASSERT(m_async || request.httpReferrer().isEmpty()); 86 87 // Save any CORS simple headers on the request here. If this request redirects cross-origin, we cancel the old request 88 // create a new one, and copy these headers. 89 const HTTPHeaderMap& headerMap = request.httpHeaderFields(); 90 HTTPHeaderMap::const_iterator end = headerMap.end(); 91 for (HTTPHeaderMap::const_iterator it = headerMap.begin(); it != end; ++it) { 92 if (isOnAccessControlSimpleRequestHeaderWhitelist(it->key, it->value)) 93 m_simpleRequestHeaders.add(it->key, it->value); 94 } 95 96 if (m_sameOriginRequest || m_options.crossOriginRequestPolicy == AllowCrossOriginRequests) { 97 loadRequest(request, m_resourceLoaderOptions); 98 return; 99 } 100 101 if (m_options.crossOriginRequestPolicy == DenyCrossOriginRequests) { 102 m_client->didFail(ResourceError(errorDomainBlinkInternal, 0, request.url().string(), "Cross origin requests are not supported.")); 103 return; 104 } 105 106 makeCrossOriginAccessRequest(request); 107 } 108 109 void DocumentThreadableLoader::makeCrossOriginAccessRequest(const ResourceRequest& request) 110 { 111 ASSERT(m_options.crossOriginRequestPolicy == UseAccessControl); 112 113 if ((m_options.preflightPolicy == ConsiderPreflight && isSimpleCrossOriginAccessRequest(request.httpMethod(), request.httpHeaderFields())) || m_options.preflightPolicy == PreventPreflight) { 114 // Cross-origin requests are only allowed for HTTP and registered schemes. We would catch this when checking response headers later, but there is no reason to send a request that's guaranteed to be denied. 115 if (!SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(request.url().protocol())) { 116 m_client->didFailAccessControlCheck(ResourceError(errorDomainBlinkInternal, 0, request.url().string(), "Cross origin requests are only supported for HTTP.")); 117 return; 118 } 119 120 ResourceRequest crossOriginRequest(request); 121 ResourceLoaderOptions crossOriginOptions(m_resourceLoaderOptions); 122 updateRequestForAccessControl(crossOriginRequest, securityOrigin(), effectiveAllowCredentials()); 123 loadRequest(crossOriginRequest, crossOriginOptions); 124 } else { 125 m_simpleRequest = false; 126 127 OwnPtr<ResourceRequest> crossOriginRequest = adoptPtr(new ResourceRequest(request)); 128 OwnPtr<ResourceLoaderOptions> crossOriginOptions = adoptPtr(new ResourceLoaderOptions(m_resourceLoaderOptions)); 129 // Do not set the Origin header for preflight requests. 130 updateRequestForAccessControl(*crossOriginRequest, 0, effectiveAllowCredentials()); 131 m_actualRequest = crossOriginRequest.release(); 132 m_actualOptions = crossOriginOptions.release(); 133 134 if (CrossOriginPreflightResultCache::shared().canSkipPreflight(securityOrigin()->toString(), m_actualRequest->url(), effectiveAllowCredentials(), m_actualRequest->httpMethod(), m_actualRequest->httpHeaderFields())) { 135 loadActualRequest(); 136 } else { 137 ResourceRequest preflightRequest = createAccessControlPreflightRequest(*m_actualRequest, securityOrigin()); 138 // Create a ResourceLoaderOptions for preflight. 139 ResourceLoaderOptions preflightOptions = *m_actualOptions; 140 preflightOptions.allowCredentials = DoNotAllowStoredCredentials; 141 loadRequest(preflightRequest, preflightOptions); 142 } 143 } 144 } 145 146 DocumentThreadableLoader::~DocumentThreadableLoader() 147 { 148 } 149 150 void DocumentThreadableLoader::cancel() 151 { 152 cancelWithError(ResourceError()); 153 } 154 155 void DocumentThreadableLoader::cancelWithError(const ResourceError& error) 156 { 157 RefPtr<DocumentThreadableLoader> protect(this); 158 159 // Cancel can re-enter and m_resource might be null here as a result. 160 if (m_client && resource()) { 161 ResourceError errorForCallback = error; 162 if (errorForCallback.isNull()) { 163 // FIXME: This error is sent to the client in didFail(), so it should not be an internal one. Use FrameLoaderClient::cancelledError() instead. 164 errorForCallback = ResourceError(errorDomainBlinkInternal, 0, resource()->url().string(), "Load cancelled"); 165 errorForCallback.setIsCancellation(true); 166 } 167 m_client->didFail(errorForCallback); 168 } 169 clearResource(); 170 m_client = 0; 171 } 172 173 void DocumentThreadableLoader::setDefersLoading(bool value) 174 { 175 if (resource()) 176 resource()->setDefersLoading(value); 177 } 178 179 void DocumentThreadableLoader::redirectReceived(Resource* resource, ResourceRequest& request, const ResourceResponse& redirectResponse) 180 { 181 ASSERT(m_client); 182 ASSERT_UNUSED(resource, resource == this->resource()); 183 184 RefPtr<DocumentThreadableLoader> protect(this); 185 if (!isAllowedByPolicy(request.url())) { 186 m_client->didFailRedirectCheck(); 187 request = ResourceRequest(); 188 return; 189 } 190 191 // Allow same origin requests to continue after allowing clients to audit the redirect. 192 if (isAllowedRedirect(request.url())) { 193 if (m_client->isDocumentThreadableLoaderClient()) 194 static_cast<DocumentThreadableLoaderClient*>(m_client)->willSendRequest(request, redirectResponse); 195 return; 196 } 197 198 // When using access control, only simple cross origin requests are allowed to redirect. The new request URL must have a supported 199 // scheme and not contain the userinfo production. In addition, the redirect response must pass the access control check if the 200 // original request was not same-origin. 201 if (m_options.crossOriginRequestPolicy == UseAccessControl) { 202 203 InspectorInstrumentation::didReceiveCORSRedirectResponse(m_document.frame(), resource->identifier(), m_document.frame()->loader().documentLoader(), redirectResponse, 0); 204 205 bool allowRedirect = false; 206 String accessControlErrorDescription; 207 208 if (m_simpleRequest) { 209 allowRedirect = CrossOriginAccessControl::isLegalRedirectLocation(request.url(), accessControlErrorDescription) 210 && (m_sameOriginRequest || passesAccessControlCheck(redirectResponse, effectiveAllowCredentials(), securityOrigin(), accessControlErrorDescription)); 211 } else { 212 accessControlErrorDescription = "The request was redirected to '"+ request.url().string() + "', which is disallowed for cross-origin requests that require preflight."; 213 } 214 215 if (allowRedirect) { 216 // FIXME: consider combining this with CORS redirect handling performed by 217 // CrossOriginAccessControl::handleRedirect(). 218 clearResource(); 219 220 RefPtr<SecurityOrigin> originalOrigin = SecurityOrigin::create(redirectResponse.url()); 221 RefPtr<SecurityOrigin> requestOrigin = SecurityOrigin::create(request.url()); 222 // If the original request wasn't same-origin, then if the request URL origin is not same origin with the original URL origin, 223 // set the source origin to a globally unique identifier. (If the original request was same-origin, the origin of the new request 224 // should be the original URL origin.) 225 if (!m_sameOriginRequest && !originalOrigin->isSameSchemeHostPort(requestOrigin.get())) 226 m_securityOrigin = SecurityOrigin::createUnique(); 227 // Force any subsequent requests to use these checks. 228 m_sameOriginRequest = false; 229 230 // Since the request is no longer same-origin, if the user didn't request credentials in 231 // the first place, update our state so we neither request them nor expect they must be allowed. 232 if (m_resourceLoaderOptions.credentialsRequested == ClientDidNotRequestCredentials) 233 m_forceDoNotAllowStoredCredentials = true; 234 235 // Remove any headers that may have been added by the network layer that cause access control to fail. 236 request.clearHTTPReferrer(); 237 request.clearHTTPOrigin(); 238 request.clearHTTPUserAgent(); 239 // Add any CORS simple request headers which we previously saved from the original request. 240 HTTPHeaderMap::const_iterator end = m_simpleRequestHeaders.end(); 241 for (HTTPHeaderMap::const_iterator it = m_simpleRequestHeaders.begin(); it != end; ++it) { 242 request.setHTTPHeaderField(it->key, it->value); 243 } 244 makeCrossOriginAccessRequest(request); 245 return; 246 } 247 248 ResourceError error(errorDomainBlinkInternal, 0, redirectResponse.url().string(), accessControlErrorDescription); 249 m_client->didFailAccessControlCheck(error); 250 } else { 251 m_client->didFailRedirectCheck(); 252 } 253 request = ResourceRequest(); 254 } 255 256 void DocumentThreadableLoader::dataSent(Resource* resource, unsigned long long bytesSent, unsigned long long totalBytesToBeSent) 257 { 258 ASSERT(m_client); 259 ASSERT_UNUSED(resource, resource == this->resource()); 260 m_client->didSendData(bytesSent, totalBytesToBeSent); 261 } 262 263 void DocumentThreadableLoader::dataDownloaded(Resource* resource, int dataLength) 264 { 265 ASSERT(m_client); 266 ASSERT_UNUSED(resource, resource == this->resource()); 267 ASSERT(!m_actualRequest); 268 269 m_client->didDownloadData(dataLength); 270 } 271 272 void DocumentThreadableLoader::responseReceived(Resource* resource, const ResourceResponse& response) 273 { 274 ASSERT_UNUSED(resource, resource == this->resource()); 275 handleResponse(resource->identifier(), response); 276 } 277 278 void DocumentThreadableLoader::handlePreflightResponse(unsigned long identifier, const ResourceResponse& response) 279 { 280 // Notifying the inspector here is necessary because a call to handlePreflightFailure() might synchronously 281 // cause the underlying ResourceLoader to be cancelled before it tells the inspector about the response. 282 // In that case, if we don't tell the inspector about the response now, the resource type in the inspector 283 // will default to "other" instead of something more descriptive. 284 DocumentLoader* loader = m_document.frame()->loader().documentLoader(); 285 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "ResourceReceiveResponse", "data", InspectorReceiveResponseEvent::data(identifier, m_document.frame(), response)); 286 // FIXME(361045): remove InspectorInstrumentation calls once DevTools Timeline migrates to tracing. 287 InspectorInstrumentation::didReceiveResourceResponse(m_document.frame(), identifier, loader, response, resource() ? resource()->loader() : 0); 288 289 String accessControlErrorDescription; 290 291 if (!passesAccessControlCheck(response, effectiveAllowCredentials(), securityOrigin(), accessControlErrorDescription)) { 292 handlePreflightFailure(response.url().string(), accessControlErrorDescription); 293 return; 294 } 295 296 if (!passesPreflightStatusCheck(response, accessControlErrorDescription)) { 297 handlePreflightFailure(response.url().string(), accessControlErrorDescription); 298 return; 299 } 300 301 OwnPtr<CrossOriginPreflightResultCacheItem> preflightResult = adoptPtr(new CrossOriginPreflightResultCacheItem(effectiveAllowCredentials())); 302 if (!preflightResult->parse(response, accessControlErrorDescription) 303 || !preflightResult->allowsCrossOriginMethod(m_actualRequest->httpMethod(), accessControlErrorDescription) 304 || !preflightResult->allowsCrossOriginHeaders(m_actualRequest->httpHeaderFields(), accessControlErrorDescription)) { 305 handlePreflightFailure(response.url().string(), accessControlErrorDescription); 306 return; 307 } 308 309 CrossOriginPreflightResultCache::shared().appendEntry(securityOrigin()->toString(), m_actualRequest->url(), preflightResult.release()); 310 } 311 312 void DocumentThreadableLoader::handleResponse(unsigned long identifier, const ResourceResponse& response) 313 { 314 ASSERT(m_client); 315 316 if (m_actualRequest) { 317 handlePreflightResponse(identifier, response); 318 return; 319 } 320 321 if (!m_sameOriginRequest && m_options.crossOriginRequestPolicy == UseAccessControl) { 322 String accessControlErrorDescription; 323 if (!passesAccessControlCheck(response, effectiveAllowCredentials(), securityOrigin(), accessControlErrorDescription)) { 324 m_client->didFailAccessControlCheck(ResourceError(errorDomainBlinkInternal, 0, response.url().string(), accessControlErrorDescription)); 325 return; 326 } 327 } 328 329 m_client->didReceiveResponse(identifier, response); 330 } 331 332 void DocumentThreadableLoader::dataReceived(Resource* resource, const char* data, int dataLength) 333 { 334 ASSERT_UNUSED(resource, resource == this->resource()); 335 handleReceivedData(data, dataLength); 336 } 337 338 void DocumentThreadableLoader::handleReceivedData(const char* data, int dataLength) 339 { 340 ASSERT(m_client); 341 // Preflight data should be invisible to clients. 342 if (!m_actualRequest) 343 m_client->didReceiveData(data, dataLength); 344 } 345 346 void DocumentThreadableLoader::notifyFinished(Resource* resource) 347 { 348 ASSERT(m_client); 349 ASSERT(resource == this->resource()); 350 351 m_timeoutTimer.stop(); 352 353 if (resource->errorOccurred()) 354 m_client->didFail(resource->resourceError()); 355 else 356 handleSuccessfulFinish(resource->identifier(), resource->loadFinishTime()); 357 } 358 359 void DocumentThreadableLoader::handleSuccessfulFinish(unsigned long identifier, double finishTime) 360 { 361 if (m_actualRequest) { 362 ASSERT(!m_sameOriginRequest); 363 ASSERT(m_options.crossOriginRequestPolicy == UseAccessControl); 364 loadActualRequest(); 365 } else 366 m_client->didFinishLoading(identifier, finishTime); 367 } 368 369 void DocumentThreadableLoader::didTimeout(Timer<DocumentThreadableLoader>* timer) 370 { 371 ASSERT_UNUSED(timer, timer == &m_timeoutTimer); 372 373 // Using values from net/base/net_error_list.h ERR_TIMED_OUT, 374 // Same as existing FIXME above - this error should be coming from FrameLoaderClient to be identifiable. 375 static const int timeoutError = -7; 376 ResourceError error("net", timeoutError, resource()->url(), String()); 377 error.setIsTimeout(true); 378 cancelWithError(error); 379 } 380 381 void DocumentThreadableLoader::loadActualRequest() 382 { 383 OwnPtr<ResourceRequest> actualRequest; 384 actualRequest.swap(m_actualRequest); 385 OwnPtr<ResourceLoaderOptions> actualOptions; 386 actualOptions.swap(m_actualOptions); 387 388 actualRequest->setHTTPOrigin(securityOrigin()->toAtomicString()); 389 390 clearResource(); 391 392 loadRequest(*actualRequest, *actualOptions); 393 } 394 395 void DocumentThreadableLoader::handlePreflightFailure(const String& url, const String& errorDescription) 396 { 397 ResourceError error(errorDomainBlinkInternal, 0, url, errorDescription); 398 399 // Prevent handleSuccessfulFinish() from bypassing access check. 400 m_actualRequest = nullptr; 401 402 m_client->didFailAccessControlCheck(error); 403 } 404 405 void DocumentThreadableLoader::loadRequest(const ResourceRequest& request, ResourceLoaderOptions resourceLoaderOptions) 406 { 407 // Any credential should have been removed from the cross-site requests. 408 const KURL& requestURL = request.url(); 409 ASSERT(m_sameOriginRequest || requestURL.user().isEmpty()); 410 ASSERT(m_sameOriginRequest || requestURL.pass().isEmpty()); 411 412 // Update resourceLoaderOptions with enforced values. 413 if (m_forceDoNotAllowStoredCredentials) 414 resourceLoaderOptions.allowCredentials = DoNotAllowStoredCredentials; 415 resourceLoaderOptions.securityOrigin = m_securityOrigin; 416 if (m_async) { 417 if (m_actualRequest) { 418 resourceLoaderOptions.sniffContent = DoNotSniffContent; 419 resourceLoaderOptions.dataBufferingPolicy = BufferData; 420 } 421 422 if (m_options.timeoutMilliseconds > 0) 423 m_timeoutTimer.startOneShot(m_options.timeoutMilliseconds / 1000.0, FROM_HERE); 424 425 FetchRequest newRequest(request, m_options.initiator, resourceLoaderOptions); 426 ASSERT(!resource()); 427 if (request.targetType() == ResourceRequest::TargetIsMedia) 428 setResource(m_document.fetcher()->fetchMedia(newRequest)); 429 else 430 setResource(m_document.fetcher()->fetchRawResource(newRequest)); 431 if (resource() && resource()->loader()) { 432 unsigned long identifier = resource()->identifier(); 433 InspectorInstrumentation::documentThreadableLoaderStartedLoadingForClient(&m_document, identifier, m_client); 434 } 435 return; 436 } 437 438 FetchRequest fetchRequest(request, m_options.initiator, resourceLoaderOptions); 439 ResourcePtr<Resource> resource = m_document.fetcher()->fetchSynchronously(fetchRequest); 440 ResourceResponse response = resource ? resource->response() : ResourceResponse(); 441 unsigned long identifier = resource ? resource->identifier() : std::numeric_limits<unsigned long>::max(); 442 ResourceError error = resource ? resource->resourceError() : ResourceError(); 443 444 InspectorInstrumentation::documentThreadableLoaderStartedLoadingForClient(&m_document, identifier, m_client); 445 446 if (!resource) { 447 m_client->didFail(error); 448 return; 449 } 450 451 // No exception for file:/// resources, see <rdar://problem/4962298>. 452 // Also, if we have an HTTP response, then it wasn't a network error in fact. 453 if (!error.isNull() && !requestURL.isLocalFile() && response.httpStatusCode() <= 0) { 454 m_client->didFail(error); 455 return; 456 } 457 458 // FIXME: A synchronous request does not tell us whether a redirect happened or not, so we guess by comparing the 459 // request and response URLs. This isn't a perfect test though, since a server can serve a redirect to the same URL that was 460 // requested. Also comparing the request and response URLs as strings will fail if the requestURL still has its credentials. 461 if (requestURL != response.url() && (!isAllowedByPolicy(response.url()) || !isAllowedRedirect(response.url()))) { 462 m_client->didFailRedirectCheck(); 463 return; 464 } 465 466 handleResponse(identifier, response); 467 468 SharedBuffer* data = resource->resourceBuffer(); 469 if (data) 470 handleReceivedData(data->data(), data->size()); 471 472 handleSuccessfulFinish(identifier, 0.0); 473 } 474 475 bool DocumentThreadableLoader::isAllowedRedirect(const KURL& url) const 476 { 477 if (m_options.crossOriginRequestPolicy == AllowCrossOriginRequests) 478 return true; 479 480 return m_sameOriginRequest && securityOrigin()->canRequest(url); 481 } 482 483 bool DocumentThreadableLoader::isAllowedByPolicy(const KURL& url) const 484 { 485 if (m_options.contentSecurityPolicyEnforcement != EnforceConnectSrcDirective) 486 return true; 487 return m_document.contentSecurityPolicy()->allowConnectToSource(url); 488 } 489 490 StoredCredentials DocumentThreadableLoader::effectiveAllowCredentials() const 491 { 492 if (m_forceDoNotAllowStoredCredentials) 493 return DoNotAllowStoredCredentials; 494 return m_resourceLoaderOptions.allowCredentials; 495 } 496 497 SecurityOrigin* DocumentThreadableLoader::securityOrigin() const 498 { 499 return m_securityOrigin ? m_securityOrigin.get() : m_document.securityOrigin(); 500 } 501 502 } // namespace WebCore 503