1/* 2 * Copyright (C) 2005, 2006, 2007, 2008, 2012 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of Apple Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29#import "WebDataSource.h" 30 31#import "WebArchive.h" 32#import "WebArchiveInternal.h" 33#import "WebDataSourceInternal.h" 34#import "WebDocument.h" 35#import "WebDocumentLoaderMac.h" 36#import "WebFrameInternal.h" 37#import "WebFrameLoadDelegate.h" 38#import "WebFrameLoaderClient.h" 39#import "WebFrameViewInternal.h" 40#import "WebHTMLRepresentation.h" 41#import "WebKitErrorsPrivate.h" 42#import "WebKitLogging.h" 43#import "WebKitStatisticsPrivate.h" 44#import "WebKitNSStringExtras.h" 45#import "WebNSURLExtras.h" 46#import "WebNSURLRequestExtras.h" 47#import "WebPDFRepresentation.h" 48#import "WebResourceInternal.h" 49#import "WebResourceLoadDelegate.h" 50#import "WebViewInternal.h" 51#import <WebCore/ApplicationCacheStorage.h> 52#import <WebCore/FrameLoader.h> 53#import <WebCore/URL.h> 54#import <WebCore/LegacyWebArchive.h> 55#import <WebCore/MIMETypeRegistry.h> 56#import <WebCore/ResourceBuffer.h> 57#import <WebCore/ResourceRequest.h> 58#import <WebCore/SharedBuffer.h> 59#import <WebCore/WebCoreObjCExtras.h> 60#import <WebCore/WebCoreURLResponse.h> 61#import <WebKitLegacy/DOMHTML.h> 62#import <WebKitLegacy/DOMPrivate.h> 63#import <runtime/InitializeThreading.h> 64#import <wtf/Assertions.h> 65#import <wtf/MainThread.h> 66#import <wtf/RefPtr.h> 67#import <wtf/RetainPtr.h> 68#import <wtf/RunLoop.h> 69 70#if PLATFORM(IOS) 71#import "WebPDFViewIOS.h" 72#endif 73 74#if USE(QUICK_LOOK) 75#import <WebCore/QuickLook.h> 76#endif 77 78using namespace WebCore; 79 80class WebDataSourcePrivate 81{ 82public: 83 WebDataSourcePrivate(PassRefPtr<WebDocumentLoaderMac> loader) 84 : loader(loader) 85 , representationFinishedLoading(NO) 86 , includedInWebKitStatistics(NO) 87#if PLATFORM(IOS) 88 , _dataSourceDelegate(nil) 89#endif 90 { 91 ASSERT(this->loader); 92 } 93 ~WebDataSourcePrivate() 94 { 95 if (loader) { 96 ASSERT(!loader->isLoading()); 97 loader->detachDataSource(); 98 } 99 } 100 101 RefPtr<WebDocumentLoaderMac> loader; 102 RetainPtr<id<WebDocumentRepresentation> > representation; 103 BOOL representationFinishedLoading; 104 BOOL includedInWebKitStatistics; 105#if PLATFORM(IOS) 106 NSObject<WebDataSourcePrivateDelegate> *_dataSourceDelegate; 107#endif 108}; 109 110static inline WebDataSourcePrivate* toPrivate(void* privateAttribute) 111{ 112 return reinterpret_cast<WebDataSourcePrivate*>(privateAttribute); 113} 114 115#if ENABLE(DISK_IMAGE_CACHE) && PLATFORM(IOS) 116static void BufferMemoryMapped(PassRefPtr<SharedBuffer> buffer, SharedBuffer::CompletionStatus mapStatus, SharedBuffer::MemoryMappedNotifyCallbackData data) 117{ 118 NSObject<WebDataSourcePrivateDelegate> *delegate = [(WebDataSource *)data dataSourceDelegate]; 119 if (mapStatus == SharedBuffer::Succeeded) 120 [delegate dataSourceMemoryMapped]; 121 else 122 [delegate dataSourceMemoryMapFailed]; 123} 124#endif 125 126@interface WebDataSource (WebFileInternal) 127@end 128 129@implementation WebDataSource (WebFileInternal) 130 131- (void)_setRepresentation:(id<WebDocumentRepresentation>)representation 132{ 133 toPrivate(_private)->representation = representation; 134 toPrivate(_private)->representationFinishedLoading = NO; 135} 136 137static inline void addTypesFromClass(NSMutableDictionary *allTypes, Class objCClass, NSArray *supportTypes) 138{ 139 NSEnumerator *enumerator = [supportTypes objectEnumerator]; 140 ASSERT(enumerator != nil); 141 NSString *mime = nil; 142 while ((mime = [enumerator nextObject]) != nil) { 143 // Don't clobber previously-registered classes. 144 if ([allTypes objectForKey:mime] == nil) 145 [allTypes setObject:objCClass forKey:mime]; 146 } 147} 148 149+ (Class)_representationClassForMIMEType:(NSString *)MIMEType allowingPlugins:(BOOL)allowPlugins 150{ 151 Class repClass; 152 return [WebView _viewClass:nil andRepresentationClass:&repClass forMIMEType:MIMEType allowingPlugins:allowPlugins] ? repClass : nil; 153} 154@end 155 156@implementation WebDataSource (WebPrivate) 157 158+ (void)initialize 159{ 160 if (self == [WebDataSource class]) { 161#if !PLATFORM(IOS) 162 JSC::initializeThreading(); 163 WTF::initializeMainThreadToProcessMainThread(); 164 RunLoop::initializeMainRunLoop(); 165#endif 166 WebCoreObjCFinalizeOnMainThread(self); 167 } 168} 169 170- (NSError *)_mainDocumentError 171{ 172 return toPrivate(_private)->loader->mainDocumentError(); 173} 174 175- (void)_addSubframeArchives:(NSArray *)subframeArchives 176{ 177 // FIXME: This SPI is poor, poor design. Can we come up with another solution for those who need it? 178 NSEnumerator *enumerator = [subframeArchives objectEnumerator]; 179 WebArchive *archive; 180 while ((archive = [enumerator nextObject]) != nil) 181 toPrivate(_private)->loader->addAllArchiveResources([archive _coreLegacyWebArchive]); 182} 183 184#if !PLATFORM(IOS) 185- (NSFileWrapper *)_fileWrapperForURL:(NSURL *)URL 186{ 187 if ([URL isFileURL]) 188 return [[[NSFileWrapper alloc] initWithURL:[URL URLByResolvingSymlinksInPath] options:0 error:nullptr] autorelease]; 189 190 WebResource *resource = [self subresourceForURL:URL]; 191 if (resource) 192 return [resource _fileWrapperRepresentation]; 193 194 NSCachedURLResponse *cachedResponse = [[self _webView] _cachedResponseForURL:URL]; 195 if (cachedResponse) { 196 NSFileWrapper *wrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:[cachedResponse data]] autorelease]; 197 [wrapper setPreferredFilename:[[cachedResponse response] suggestedFilename]]; 198 return wrapper; 199 } 200 201 return nil; 202} 203#endif 204 205- (NSString *)_responseMIMEType 206{ 207 return [[self response] MIMEType]; 208} 209 210- (BOOL)_transferApplicationCache:(NSString*)destinationBundleIdentifier 211{ 212 if (!toPrivate(_private)->loader) 213 return NO; 214 215 NSString *cacheDir = [NSString _webkit_localCacheDirectoryWithBundleIdentifier:destinationBundleIdentifier]; 216 217 return ApplicationCacheStorage::storeCopyOfCache(cacheDir, toPrivate(_private)->loader->applicationCacheHost()); 218} 219 220- (void)_setDeferMainResourceDataLoad:(BOOL)flag 221{ 222 if (!toPrivate(_private)->loader) 223 return; 224 225 toPrivate(_private)->loader->setDeferMainResourceDataLoad(flag); 226} 227 228#if PLATFORM(IOS) 229- (void)_setOverrideTextEncodingName:(NSString *)encoding 230{ 231 toPrivate(_private)->loader->setOverrideEncoding([encoding UTF8String]); 232} 233#endif 234 235- (void)_setAllowToBeMemoryMapped 236{ 237#if ENABLE(DISK_IMAGE_CACHE) && PLATFORM(IOS) 238 RefPtr<ResourceBuffer> mainResourceBuffer = toPrivate(_private)->loader->mainResourceData(); 239 if (!mainResourceBuffer) 240 return; 241 242 RefPtr<SharedBuffer> mainResourceData = mainResourceBuffer->sharedBuffer(); 243 if (!mainResourceData) 244 return; 245 246 if (mainResourceData->memoryMappedNotificationCallback() != BufferMemoryMapped) { 247 ASSERT(!mainResourceData->memoryMappedNotificationCallback() && !mainResourceData->memoryMappedNotificationCallbackData()); 248 mainResourceData->setMemoryMappedNotificationCallback(BufferMemoryMapped, self); 249 } 250 251 switch (mainResourceData->allowToBeMemoryMapped()) { 252 case SharedBuffer::SuccessAlreadyMapped: 253 [[self dataSourceDelegate] dataSourceMemoryMapped]; 254 return; 255 case SharedBuffer::PreviouslyQueuedForMapping: 256 case SharedBuffer::QueuedForMapping: 257 return; 258 case SharedBuffer::FailureCacheFull: 259 [[self dataSourceDelegate] dataSourceMemoryMapFailed]; 260 return; 261 } 262 ASSERT_NOT_REACHED(); 263#endif 264} 265 266- (void)setDataSourceDelegate:(NSObject<WebDataSourcePrivateDelegate> *)delegate 267{ 268#if ENABLE(DISK_IMAGE_CACHE) && PLATFORM(IOS) 269 ASSERT(!toPrivate(_private)->_dataSourceDelegate); 270 toPrivate(_private)->_dataSourceDelegate = delegate; 271#endif 272} 273 274- (NSObject<WebDataSourcePrivateDelegate> *)dataSourceDelegate 275{ 276#if ENABLE(DISK_IMAGE_CACHE) && PLATFORM(IOS) 277 return toPrivate(_private)->_dataSourceDelegate; 278#else 279 return nullptr; 280#endif 281} 282 283@end 284 285@implementation WebDataSource (WebInternal) 286 287- (void)_finishedLoading 288{ 289 toPrivate(_private)->representationFinishedLoading = YES; 290 [[self representation] finishedLoadingWithDataSource:self]; 291} 292 293- (void)_receivedData:(NSData *)data 294{ 295 // protect self temporarily, as the bridge receivedData call could remove our last ref 296 RetainPtr<WebDataSource*> protect(self); 297 298 [[self representation] receivedData:data withDataSource:self]; 299 [[[[self webFrame] frameView] documentView] dataSourceUpdated:self]; 300} 301 302- (void)_setMainDocumentError:(NSError *)error 303{ 304 if (!toPrivate(_private)->representationFinishedLoading) { 305 toPrivate(_private)->representationFinishedLoading = YES; 306 [[self representation] receivedError:error withDataSource:self]; 307 } 308} 309 310- (void)_revertToProvisionalState 311{ 312 [self _setRepresentation:nil]; 313} 314 315+ (NSMutableDictionary *)_repTypesAllowImageTypeOmission:(BOOL)allowImageTypeOmission 316{ 317 static NSMutableDictionary *repTypes = nil; 318 static BOOL addedImageTypes = NO; 319 320 if (!repTypes) { 321 repTypes = [[NSMutableDictionary alloc] init]; 322 addTypesFromClass(repTypes, [WebHTMLRepresentation class], [WebHTMLRepresentation supportedNonImageMIMETypes]); 323 324 // Since this is a "secret default" we don't both registering it. 325 BOOL omitPDFSupport = [[NSUserDefaults standardUserDefaults] boolForKey:@"WebKitOmitPDFSupport"]; 326 if (!omitPDFSupport) 327#if PLATFORM(IOS) 328#define WebPDFRepresentation ([WebView _getPDFRepresentationClass]) 329#endif 330 addTypesFromClass(repTypes, [WebPDFRepresentation class], [WebPDFRepresentation supportedMIMETypes]); 331#if PLATFORM(IOS) 332#undef WebPDFRepresentation 333#endif 334 } 335 336 if (!addedImageTypes && !allowImageTypeOmission) { 337 addTypesFromClass(repTypes, [WebHTMLRepresentation class], [WebHTMLRepresentation supportedImageMIMETypes]); 338 addedImageTypes = YES; 339 } 340 341 return repTypes; 342} 343 344- (void)_replaceSelectionWithArchive:(WebArchive *)archive selectReplacement:(BOOL)selectReplacement 345{ 346 DOMDocumentFragment *fragment = [self _documentFragmentWithArchive:archive]; 347 if (fragment) 348 [[self webFrame] _replaceSelectionWithFragment:fragment selectReplacement:selectReplacement smartReplace:NO matchStyle:NO]; 349} 350 351// FIXME: There are few reasons why this method and many of its related methods can't be pushed entirely into WebCore in the future. 352- (DOMDocumentFragment *)_documentFragmentWithArchive:(WebArchive *)archive 353{ 354 ASSERT(archive); 355 WebResource *mainResource = [archive mainResource]; 356 if (mainResource) { 357 NSString *MIMEType = [mainResource MIMEType]; 358 if ([WebView canShowMIMETypeAsHTML:MIMEType]) { 359 NSString *markupString = [[NSString alloc] initWithData:[mainResource data] encoding:NSUTF8StringEncoding]; 360 // FIXME: seems poor form to do this as a side effect of getting a document fragment 361 if (toPrivate(_private)->loader) 362 toPrivate(_private)->loader->addAllArchiveResources([archive _coreLegacyWebArchive]); 363 364 DOMDocumentFragment *fragment = [[self webFrame] _documentFragmentWithMarkupString:markupString baseURLString:[[mainResource URL] _web_originalDataAsString]]; 365 [markupString release]; 366 return fragment; 367 } else if (MIMETypeRegistry::isSupportedImageMIMEType(MIMEType)) { 368 return [self _documentFragmentWithImageResource:mainResource]; 369 370 } 371 } 372 return nil; 373} 374 375- (DOMDocumentFragment *)_documentFragmentWithImageResource:(WebResource *)resource 376{ 377 DOMElement *imageElement = [self _imageElementWithImageResource:resource]; 378 if (!imageElement) 379 return 0; 380 DOMDocumentFragment *fragment = [[[self webFrame] DOMDocument] createDocumentFragment]; 381 [fragment appendChild:imageElement]; 382 return fragment; 383} 384 385- (DOMElement *)_imageElementWithImageResource:(WebResource *)resource 386{ 387 if (!resource) 388 return 0; 389 390 [self addSubresource:resource]; 391 392 DOMElement *imageElement = [[[self webFrame] DOMDocument] createElement:@"img"]; 393 394 // FIXME: calling _web_originalDataAsString on a file URL returns an absolute path. Workaround this. 395 NSURL *URL = [resource URL]; 396 [imageElement setAttribute:@"src" value:[URL isFileURL] ? [URL absoluteString] : [URL _web_originalDataAsString]]; 397 398 return imageElement; 399} 400 401// May return nil if not initialized with a URL. 402- (NSURL *)_URL 403{ 404 const URL& url = toPrivate(_private)->loader->url(); 405 if (url.isEmpty()) 406 return nil; 407 return url; 408} 409 410- (WebView *)_webView 411{ 412 return [[self webFrame] webView]; 413} 414 415- (BOOL)_isDocumentHTML 416{ 417 NSString *MIMEType = [self _responseMIMEType]; 418 return [WebView canShowMIMETypeAsHTML:MIMEType]; 419} 420 421- (void)_makeRepresentation 422{ 423 Class repClass = [[self class] _representationClassForMIMEType:[self _responseMIMEType] allowingPlugins:[[[self _webView] preferences] arePlugInsEnabled]]; 424 425#if PLATFORM(IOS) 426 if ([repClass respondsToSelector:@selector(_representationClassForWebFrame:)]) 427 repClass = [repClass performSelector:@selector(_representationClassForWebFrame:) withObject:[self webFrame]]; 428#endif 429 430 // Check if the data source was already bound? 431 if (![[self representation] isKindOfClass:repClass]) { 432 id newRep = repClass != nil ? [[repClass alloc] init] : nil; 433 [self _setRepresentation:(id <WebDocumentRepresentation>)newRep]; 434 [newRep release]; 435 } 436 437 id<WebDocumentRepresentation> representation = toPrivate(_private)->representation.get(); 438 [representation setDataSource:self]; 439#if PLATFORM(IOS) 440 toPrivate(_private)->loader->setResponseMIMEType([self _responseMIMEType]); 441#endif 442} 443 444- (DocumentLoader*)_documentLoader 445{ 446 return toPrivate(_private)->loader.get(); 447} 448 449- (id)_initWithDocumentLoader:(PassRefPtr<WebDocumentLoaderMac>)loader 450{ 451 self = [super init]; 452 if (!self) 453 return nil; 454 455 ASSERT(loader); 456 _private = static_cast<void*>(new WebDataSourcePrivate(loader)); 457 458 LOG(Loading, "creating datasource for %@", static_cast<NSURL *>(toPrivate(_private)->loader->request().url())); 459 460 if ((toPrivate(_private)->includedInWebKitStatistics = [[self webFrame] _isIncludedInWebKitStatistics])) 461 ++WebDataSourceCount; 462 463 return self; 464} 465 466@end 467 468@implementation WebDataSource 469 470- (instancetype)initWithRequest:(NSURLRequest *)request 471{ 472 return [self _initWithDocumentLoader:WebDocumentLoaderMac::create(request, SubstituteData())]; 473} 474 475- (void)dealloc 476{ 477 if (WebCoreObjCScheduleDeallocateOnMainThread([WebDataSource class], self)) 478 return; 479 480 if (toPrivate(_private) && toPrivate(_private)->includedInWebKitStatistics) 481 --WebDataSourceCount; 482 483#if ENABLE(DISK_IMAGE_CACHE) && PLATFORM(IOS) 484 // The code to remove memory mapped notification is only needed when we are viewing a PDF file. 485 // In such a case, WebPDFViewPlaceholder sets itself as the dataSourceDelegate. Guard the access 486 // to mainResourceData with this nil check so that we avoid assertions due to the resource being 487 // made purgeable. 488 if (_private && [self dataSourceDelegate]) { 489 RefPtr<ResourceBuffer> mainResourceBuffer = toPrivate(_private)->loader->mainResourceData(); 490 if (mainResourceBuffer) { 491 RefPtr<SharedBuffer> mainResourceData = mainResourceBuffer->sharedBuffer(); 492 if (mainResourceData && 493 mainResourceData->memoryMappedNotificationCallbackData() == self && 494 mainResourceData->memoryMappedNotificationCallback() == BufferMemoryMapped) { 495 mainResourceData->setMemoryMappedNotificationCallback(nullptr, nullptr); 496 } 497 } 498 } 499#endif 500 501#if USE(QUICK_LOOK) 502 // Added in -[WebCoreResourceHandleAsDelegate connection:didReceiveResponse:]. 503 if (NSURL *url = [[self response] URL]) 504 removeQLPreviewConverterForURL(url); 505#endif 506 507 delete toPrivate(_private); 508 509 [super dealloc]; 510} 511 512- (void)finalize 513{ 514 ASSERT_MAIN_THREAD(); 515 516 if (toPrivate(_private) && toPrivate(_private)->includedInWebKitStatistics) 517 --WebDataSourceCount; 518 519 delete toPrivate(_private); 520 521 [super finalize]; 522} 523 524- (NSData *)data 525{ 526 RefPtr<ResourceBuffer> mainResourceData = toPrivate(_private)->loader->mainResourceData(); 527 if (!mainResourceData) 528 return nil; 529 return mainResourceData->createNSData().autorelease(); 530} 531 532- (id <WebDocumentRepresentation>)representation 533{ 534 return toPrivate(_private)->representation.get(); 535} 536 537- (WebFrame *)webFrame 538{ 539 if (Frame* frame = toPrivate(_private)->loader->frame()) 540 return kit(frame); 541 542 return nil; 543} 544 545- (NSURLRequest *)initialRequest 546{ 547 return toPrivate(_private)->loader->originalRequest().nsURLRequest(UpdateHTTPBody); 548} 549 550- (NSMutableURLRequest *)request 551{ 552 FrameLoader* frameLoader = toPrivate(_private)->loader->frameLoader(); 553 if (!frameLoader || !frameLoader->frameHasLoaded()) 554 return nil; 555 556 // FIXME: this cast is dubious 557 return (NSMutableURLRequest *)toPrivate(_private)->loader->request().nsURLRequest(UpdateHTTPBody); 558} 559 560- (NSURLResponse *)response 561{ 562 return toPrivate(_private)->loader->response().nsURLResponse(); 563} 564 565- (NSString *)textEncodingName 566{ 567 NSString *textEncodingName = toPrivate(_private)->loader->overrideEncoding(); 568 if (!textEncodingName) 569 textEncodingName = [[self response] textEncodingName]; 570 return textEncodingName; 571} 572 573- (BOOL)isLoading 574{ 575 return toPrivate(_private)->loader->isLoadingInAPISense(); 576} 577 578// Returns nil or the page title. 579- (NSString *)pageTitle 580{ 581 return [[self representation] title]; 582} 583 584- (NSURL *)unreachableURL 585{ 586 const URL& unreachableURL = toPrivate(_private)->loader->unreachableURL(); 587 if (unreachableURL.isEmpty()) 588 return nil; 589 return unreachableURL; 590} 591 592- (WebArchive *)webArchive 593{ 594 // it makes no sense to grab a WebArchive from an uncommitted document. 595 if (!toPrivate(_private)->loader->isCommitted()) 596 return nil; 597 598 return [[[WebArchive alloc] _initWithCoreLegacyWebArchive:LegacyWebArchive::create(core([self webFrame]))] autorelease]; 599} 600 601- (WebResource *)mainResource 602{ 603 RefPtr<ArchiveResource> coreResource = toPrivate(_private)->loader->mainResource(); 604 return [[[WebResource alloc] _initWithCoreResource:coreResource.release()] autorelease]; 605} 606 607- (NSArray *)subresources 608{ 609 auto coreSubresources = toPrivate(_private)->loader->subresources(); 610 611 auto subresources = adoptNS([[NSMutableArray alloc] initWithCapacity:coreSubresources.size()]); 612 for (const auto& coreSubresource : coreSubresources) { 613 if (auto resource = adoptNS([[WebResource alloc] _initWithCoreResource:coreSubresource])) 614 [subresources addObject:resource.get()]; 615 } 616 617 return subresources.autorelease(); 618} 619 620- (WebResource *)subresourceForURL:(NSURL *)URL 621{ 622 RefPtr<ArchiveResource> subresource = toPrivate(_private)->loader->subresource(URL); 623 624 return subresource ? [[[WebResource alloc] _initWithCoreResource:subresource.get()] autorelease] : nil; 625} 626 627- (void)addSubresource:(WebResource *)subresource 628{ 629 toPrivate(_private)->loader->addArchiveResource([subresource _coreResource]); 630} 631 632@end 633