// Copyright 2003 - 2004 Nathan Hamblen
// Modified BSD license:
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
// 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import "MyDocument.h"
#import "EditController.h"
#import "FillController.h"
#import "WebLocationWrapper.h"
#import "IconFamily.h"

// delimeters for the dynamic location parameters
static NSString *START_DELIMITER = @"[", *END_DELIMITER = @"]",
// file type names
*DYNAMIC_LOCATION_TYPE = @"Dynamic Location File", *WEB_LOCATION_TYPE = @"Web Location File";

// Category that handles the parameters we are using for Dynamic Locations
@implementation NSString (ParameterAccess)

// Returns an array of the parameters contained in the string, or nil if the parameter syntax is invalid (delimiters out of order)
- (NSArray *)parameterNameArray
{
  NSMutableArray *paramAry = [NSMutableArray arrayWithCapacity:8];
  unsigned int strLen = [self length];
  NSRange remaining = NSMakeRange(0, strLen);
  NSCharacterSet *delimSet = [NSCharacterSet characterSetWithCharactersInString:
    [START_DELIMITER stringByAppendingString:END_DELIMITER]];
  bool searching = YES;
  while (searching)
  {
    NSRange delimRange = [self rangeOfCharacterFromSet:delimSet options:NSLiteralSearch
                                                   range:remaining];
    NSRange paramRange;

    if (delimRange.location == NSNotFound)
      searching = NO;
    else
    {
      if (![[self substringWithRange:delimRange] isEqualToString:START_DELIMITER])
        return nil;
      paramRange.location = NSMaxRange(delimRange);
      remaining.location = paramRange.location;
      remaining.length = strLen - remaining.location;
      delimRange = [self rangeOfCharacterFromSet:delimSet options:NSLiteralSearch
                                             range:remaining];
      if (delimRange.location == NSNotFound ||
          ![[self substringWithRange:delimRange] isEqualToString:END_DELIMITER])
        return nil;
      remaining.location = NSMaxRange(delimRange);
      remaining.length = strLen - remaining.location;
      paramRange.length = delimRange.location - paramRange.location;
      [paramAry addObject:[self substringWithRange:paramRange]];
    }
  }
  return paramAry;
}

// Returns a string with parameters replaced by the contents of valueArray.
- (NSString *)stringFilledWithParameters:(NSArray *)valueArray
{
  unsigned int strLen = [self length];
  NSRange remaining = NSMakeRange(0, strLen);
  NSCharacterSet *delimSet = [NSCharacterSet characterSetWithCharactersInString:
    [START_DELIMITER stringByAppendingString:END_DELIMITER]];
  NSMutableString *filledString = [NSMutableString stringWithCapacity:strLen * 2];
  NSEnumerator *params = [valueArray objectEnumerator];
  bool startDelim = YES;
  while (remaining.location < strLen)
  {
    NSRange delimRange = [self rangeOfCharacterFromSet:delimSet options:NSLiteralSearch
                                                   range:remaining];
    if (delimRange.location == NSNotFound)
      delimRange.location = strLen;
    if (startDelim)
    {
      NSRange chunk = NSMakeRange(remaining.location,
                                  delimRange.location - remaining.location);
      [filledString appendString:[self substringWithRange:chunk]];
      if (delimRange.location < strLen)
        [filledString appendString:[params nextObject]];
    }
    remaining.location = NSMaxRange(delimRange);
    remaining.length = strLen - remaining.location;
    startDelim = !startDelim;
  }
  return filledString;
}

// Used to validate that the string will be valid once parameters are supplied.
- (NSString *)stringFilledWithDummyParameters
{
  NSArray *paramAry = [self parameterNameArray];
  NSMutableArray *valAry;
  unsigned short i;

  if (paramAry == nil || [paramAry count] < 1)
    return nil;
  valAry = [NSMutableArray arrayWithCapacity:[paramAry count]];
  for (i=0; i< [paramAry count]; i++)
    [valAry addObject:@"test"];
  return [self stringFilledWithParameters:valAry];
}

@end

@interface MyDocument(Internals)
- (void) endAnyConnection;
- (void) setSiteIconImage:(NSImage *)newImage;
- siteIconSearchInString: (NSString *) urlContents;
- (void) loadSiteIcon:(NSString *) href;
@end

@implementation MyDocument

#pragma mark  Initilization

- (id)init
{
  self = [super init];
  if (self) {
    mImages = [[NSMutableSet alloc] initWithCapacity:5];
    mPageUrl = nil;
    mCompiledRegularExpression = nil;
    mData = [[NSMutableData alloc] init];
    mPageConnection = mImageConnection = nil;
    mPageResponse = nil;
  }
  return self;
}

// Make these appropriate controller for the current mode (edit or fill-in).
- (void)makeWindowControllers
{
  if (mDynamicLocation)
    [self addWindowController:[[[FillController alloc] init] autorelease]];
  else
    [self addWindowController:[[[EditController alloc] init] autorelease]];
}

#pragma mark  User interface, validation

// Change from edit to fill-in mode or vice-versa, called by window controllers.
- (void)switchToFillin:(BOOL)fillinFlag
{
  NSWindowController *newCont, *controller;
  NSEnumerator *controllers;

  if (fillinFlag)
    newCont = [[FillController alloc] init];
  else
    newCont =  [[EditController alloc] init];

  [self addWindowController:[newCont autorelease]];

  controllers = [[self windowControllers] objectEnumerator];
  while (controller = [controllers nextObject])
    if (controller != newCont)
      [controller close];
  [newCont showWindow:self];
}

// This prevents the user from changing the file type in the save panel.
- (BOOL)shouldRunSavePanelWithAccessoryView {
  return NO;
}

// Allows the window controller to validate (and disable) menu items if it implements the relevant protocol.
- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
{
  id cont = [[[self windowControllers] objectEnumerator] nextObject];
  if (cont && [cont conformsToProtocol:@protocol(NSUserInterfaceValidations)])
    return [cont validateUserInterfaceItem:anItem];
  return [super validateUserInterfaceItem:anItem];
}

// Displays an alert and returns NO if errors are found in the URL / dynamic location.
- (BOOL)validate
{
  NSWindow *topWind = nil;
  NSString *testUrlStr;
  NSURL *testUrl;
  if ([[self windowControllers] count] > 0)
    topWind = [[[self windowControllers]objectAtIndex:0] window];

  if (mDynamicLocation) {
    NSArray *paramAry = [mDynamicLocation parameterNameArray];
    if (paramAry == nil)
    {
      if (topWind)
        NSBeginAlertSheet(@"Parameter Error", nil, nil, nil, topWind, nil, nil, nil,
                          self, @"The parameter braces are not positioned correctly.");
      return NO;
    }
    if ([paramAry count] < 1)
    {
      if (topWind)
        NSBeginAlertSheet(@"No Parameters in Location", nil, nil, nil,
                          topWind, nil, nil, nil, self,
                          @"You must define parameters for a Dynamic Location.");
      return NO;
    }
    testUrlStr = [mDynamicLocation stringFilledWithDummyParameters];
  } else
    testUrlStr = mUrl;

  testUrl = [NSURL URLWithString:testUrlStr];
  if (!testUrl)
  {
    NSBeginAlertSheet(@"URL Error", nil, nil, nil, topWind, nil, nil, nil, self,
                      @"The entered Location is not a valid URL.");
    return NO;
  }
  return YES;
}

// Posts a notification on closing that enables the fill in window to save its position, search history, etc.
- (void) close
{
  [self endAnyConnection];
  [[NSNotificationCenter defaultCenter] postNotificationName:@"ClosingDocument"
                                                      object:self];
  [super close];
}

#pragma mark  Read and write

// Retrieve the URL from a the web location at fileName; if the file at path does not exist, a seed internet location file is copied from this application's bundle. Note that Project Builder does not correctly copy the seed's resource fork when building the project so it must be copied manually to the application package. If the destroyExisting flag is YES, we delete the existing file if there is one and copy from the seed.
+ (BOOL) ensureInternetLocationExists:(NSString*)path destroyExisting:(BOOL)destroyExisting
{
  NSFileManager* fileManager = [NSFileManager defaultManager];
  NSBundle* bundle = [NSBundle mainBundle];
  NSString* seedPath = [bundle pathForResource:@"seed" ofType:@"webloc"];

  if ([fileManager fileExistsAtPath:path])
  {
    if (destroyExisting)
      [fileManager removeFileAtPath:path handler:nil];
    else
      return YES;
  }
  return [fileManager copyPath:seedPath toPath:path handler:nil];
}

// Checks the HFS type and file name extension and updates either as needed.
+ (NSString *) checkTypeOfFile:(NSString *)inPath forType:(NSString *)documentType
{
  NSDictionary *infoPlist = [[NSBundle mainBundle] infoDictionary];
  NSEnumerator *types =
    [[infoPlist objectForKey:@"CFBundleDocumentTypes"] objectEnumerator];
  NSDictionary *typeInfo = nil;
  NSString *typeExtension = nil, *typeCode = nil;
  NSFileManager *fileManager = [NSFileManager defaultManager];
  NSString *outPath = inPath;

  while (!typeExtension && (typeInfo = [types nextObject]))
    if ([[typeInfo objectForKey:@"CFBundleTypeName"] isEqualToString:documentType])
    {
      typeCode = [[typeInfo objectForKey:@"CFBundleTypeOSTypes"] objectAtIndex:0];
      typeExtension = [[typeInfo objectForKey:@"CFBundleTypeExtensions"] objectAtIndex:0];
    }

  if (typeExtension && ![[inPath pathExtension] isEqualToString:typeExtension])
  {
    outPath = [[inPath stringByDeletingPathExtension]
     stringByAppendingPathExtension:typeExtension];
    if (![fileManager movePath:inPath toPath:outPath handler:nil])
      outPath = inPath;
  }
  if (typeCode)
  {
    NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithDictionary:
      [fileManager fileAttributesAtPath:outPath traverseLink:NO]];

    NSNumber *typeCodeNum = [NSNumber numberWithUnsignedLong:
      NSHFSTypeCodeFromFileType([NSString stringWithFormat:@"'%@'",typeCode])];

    NSString *creatorCodeString = ([documentType isEqualToString:WEB_LOCATION_TYPE]) ?
      @"MACS" : [infoPlist objectForKey:@"CFBundleSignature"];

    NSNumber *creatorCodeNum = [NSNumber numberWithUnsignedLong:
      NSHFSTypeCodeFromFileType([NSString stringWithFormat:@"'%@'", creatorCodeString])];

    [attributes setObject:typeCodeNum forKey:NSFileHFSTypeCode];

    [attributes setObject:creatorCodeNum forKey:NSFileHFSCreatorCode];

    [fileManager changeFileAttributes:attributes atPath:outPath];
  }
  return (outPath == nil) ? inPath : outPath;
}

- (BOOL)readFromFile:(NSString *)fileName ofType:(NSString *)type
{
  WebLocationWrapper  *wlWrapper = [[WebLocationWrapper alloc] initWithPath:fileName];
  NSString *errorString = nil;
  NSPropertyListFormat format;

  if ([type isEqualToString:DYNAMIC_LOCATION_TYPE])
  {
    [self setDynamicLocation:[wlWrapper url]];
    mSpecificPreferences =
      [NSPropertyListSerialization propertyListFromData:[wlWrapper regularFileContents]
                                       mutabilityOption:kCFPropertyListMutableContainersAndLeaves
                                                 format:&format
                                       errorDescription:&errorString];
    if (mSpecificPreferences)
      mSpecificPreferences = [mSpecificPreferences mutableCopyWithZone:nil];

    
// if the dynamic location doesn't have prefs yet we'll get a "stream had too few bytes" error which is not an error for the app
    if (errorString)
      [errorString release];
  }
  else if ([type isEqualToString:WEB_LOCATION_TYPE])
    [self setUrl:[wlWrapper url]];

  [wlWrapper release];
  return YES;
}

// We're going to disobey orders here and NOT use a backup file. NSDocument's built-in backup system doesn't handle custom icons (or any resources) very well, and I'm not writing my own backup routine.
- (BOOL)writeWithBackupToFile:(NSString *)fullDocumentPath
                       ofType:(NSString *)documentTypeName
                saveOperation:(NSSaveOperationType)saveOperationType
{
  WebLocationWrapper *wlWrapper;
  BOOL status;
  NSData *prefData = nil;
  NSString *errorString = nil;

  [[NSNotificationCenter defaultCenter] postNotificationName:@"NeedsUpdate"
                                                      object:self];
  if (![self validate])
    return NO;

  [[self undoManager] removeAllActions];

  if (![MyDocument ensureInternetLocationExists:fullDocumentPath
                                destroyExisting:(saveOperationType == NSSaveAsOperation)])
  {
    NSLog(@"Unable to create new internet location from seed location.");
    return NO;
  }

  fullDocumentPath = [MyDocument checkTypeOfFile:fullDocumentPath
                                         forType:documentTypeName];
  [self setFileName:fullDocumentPath];

  if (mDynamicLocation && mSpecificPreferences && [mSpecificPreferences count] > 0)
    prefData =
      [NSPropertyListSerialization dataFromPropertyList:mSpecificPreferences
                                                 format:NSPropertyListXMLFormat_v1_0
                                       errorDescription:&errorString];
  if (errorString) {
    NSLog(errorString);
    [errorString release];
  }

  wlWrapper = [[WebLocationWrapper alloc] initRegularFileWithContents:prefData];

  if (!wlWrapper)
  {
    NSLog(@"Unable to open location wrapper.");
    return NO;
  }

  [wlWrapper setUrl:(mDynamicLocation ? mDynamicLocation : mUrl)];

  status = [wlWrapper writeToFile:fullDocumentPath
                            atomically:NO
                       updateFilenames:YES];
  [wlWrapper release];

  if (!status)
    return NO;

  if (mSiteImage)
  {
    NSEnumerator *reps;
    NSImageRep *rep;
    NSMutableArray *bitmaps = [NSMutableArray arrayWithCapacity:3];
    NSImage* workImage = [mSiteImage copyWithZone:[mSiteImage zone]];
    NSSize siteImageSize = [workImage size];
    NSImage *fileIcon;
    NSGraphicsContext *graphicsContext = [NSGraphicsContext currentContext];
    IconFamily *iconFamily;

    [workImage setScalesWhenResized:YES];
    if ([documentTypeName isEqualToString:DYNAMIC_LOCATION_TYPE])
      fileIcon = [[IconFamily iconFamilyWithContentsOfFile:
        [[NSBundle mainBundle] pathForResource:@"dynamiclocation" ofType:@"icns"]]
        imageWithAllReps];
    else
      fileIcon = [[IconFamily iconFamilyWithSystemIcon:
        kInternetLocationHTTP] imageWithAllReps];
    reps = [[fileIcon representations] objectEnumerator];

    [NSGraphicsContext saveGraphicsState];
    [graphicsContext setShouldAntialias:YES];
    [graphicsContext setImageInterpolation:NSImageInterpolationHigh];
    while (rep = [reps nextObject])
    {
      NSBitmapImageRep *bitmap;
      NSSize fullSize = [rep size], siteSize;
      NSRect fullRect = NSMakeRect(0.F, 0.F, fullSize.width, fullSize.height);
      [fileIcon lockFocusOnRepresentation:rep];
      if (siteImageSize.width >= fullSize.width &&
          siteImageSize.height >= fullSize.height)
      {
        siteSize = fullSize;
        [[NSColor clearColor] set];
        NSRectFill(fullRect);
      }
      else
        siteSize = NSMakeSize(fullSize.width * .6F, fullSize.height * .6F);

      [workImage setSize:siteSize];
      [workImage drawAtPoint:NSMakePoint(0.F, 0.F)
                    fromRect:NSMakeRect(0.F, 0.F, siteSize.width, siteSize.height)
                   operation:NSCompositeSourceOver
                    fraction:1.0F];
      bitmap = [[[NSBitmapImageRep alloc] initWithFocusedViewRect:fullRect] autorelease];
      [fileIcon unlockFocus];

      [bitmaps addObject:bitmap];
    }
    [NSGraphicsContext restoreGraphicsState];
    iconFamily = [[IconFamily alloc] initWithBitmaps:bitmaps];
    status = [iconFamily setAsCustomIconForFile:fullDocumentPath];
    [iconFamily release];
    [workImage release];
  }
  return status;
}

// Appkit tries to set an incorrect filename after calling our writeToFile method (our method changes the file name) so we have to intercept that call.
- (void)setFileName:(NSString *)fileName
{
  if ([[NSFileManager defaultManager] fileExistsAtPath:fileName])
    [super setFileName:fileName];
}

#pragma mark  Site icons

// Called by the edit window controller and by the import procedure to retrieve site icons. This method kicks off the page download and parse controller, NSXMLParser. The parse procedure will eventually complete and callloadSiteIcon (in parserDidEndDocument) with a site icon href if it finds one. If the parser fails to start, we call setSiteIconImage directly with nil, which will try to guess the site icon name.
- (void)refreshSiteImage
{
  NSString *urlStr;
  
// If there is an existing connection, end it.
  [self endAnyConnection];

  [[NSNotificationCenter defaultCenter] postNotificationName:@"NeedsUpdate"
                                                      object:self];

  if (![self validate])
    return;

  if (mDynamicLocation)
    urlStr = [mDynamicLocation stringFilledWithDummyParameters];
  else
    urlStr = mUrl;

  if (mPageUrl) [mPageUrl release];
  mPageUrl = [[NSURL alloc] initWithString:urlStr];
  mRedirectCount = 0;
  mPageConnection =
    [[NSURLConnection alloc] initWithRequest:[NSURLRequest requestWithURL:mPageUrl]
                                    delegate:self];
}

// ends a page or icon downloading connetion if one is active
- (void) endAnyConnection
{
  NSURLConnection *conn = mPageConnection ? mPageConnection : mImageConnection;
  if (conn)
  {
    [conn cancel];
    [conn release];
    mPageConnection = mImageConnection = nil;
    
//[self setSiteIconImage:nil];
  }
}

// hold on to the response object for page connections because it may contain character set information
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse*)response
{
  if (connection == mPageConnection)
  {
    if (mPageResponse)
      [mPageResponse release];
    mPageResponse = [response retain];
  }
  [mData setLength:0];
}

// append page or icon data
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
  [mData appendData:data];
}

// page or icon finished loading
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
  if (connection == mPageConnection) {
    NSString *pageText = nil, *siteIconUrl;
    NSStringEncoding encoding = 0;

    [[NSNotificationCenter defaultCenter] postNotificationName:@"PartialLoad"
                                                        object:self];
    
// use encoding if specified in header
    if ([mPageResponse textEncodingName])
      encoding = CFStringConvertEncodingToNSStringEncoding(
                   CFStringConvertIANACharSetNameToEncoding(
                      (CFStringRef)[mPageResponse textEncodingName]));

    if (encoding)
      pageText = [[NSString alloc] initWithData:mData encoding:encoding];
    if (!pageText)
      pageText = [[NSString alloc] initWithData:mData
                                       encoding:NSISOLatin1StringEncoding];
    siteIconUrl = [self siteIconSearchInString:pageText];

    if (pageText)
      [pageText release];

    
// if load was aborted by a second entry into - refreshSiteImage while we were searching, then mPageConnection will have been nil'd or set to a new connection
    if (mPageConnection == connection)
    {
      mPageConnection = nil;
      [self loadSiteIcon:siteIconUrl]; 
// ok to be nil
    }
  } else if (connection == mImageConnection) {
    NSImage *iconImage;
    const unsigned fillerSize = 2048;
    char buffer[fillerSize];
    unsigned spaceCount = 0;
    NSData *imageData = mData;

    
// webcore somestimes has a section of innapropriate data prepended to the icon data. It corresponds to 2047 spaces and one newline character. If this data is present, it must be excluded from what is passed to NSImage
    [mData getBytes:buffer length:fillerSize];

    while ((buffer[spaceCount] == ' ') && (spaceCount < fillerSize))
      spaceCount++;

    if ((spaceCount == (fillerSize - 1)) &&
        (buffer[spaceCount] == '\n'))
      imageData = [mData subdataWithRange:
        NSMakeRange(fillerSize, [mData length] - fillerSize)];

    iconImage = [[NSImage alloc] initWithData:imageData];
    mImageConnection = nil;
    [self setSiteIconImage:iconImage];
    
// don't release iconImage as setIconImage expects it to be retained
  }
  [connection release];
}

// webcore seems to screw up if a page redirects more than once (like at http://www.voyages-sncf.com/) so upon the second redirect we just give up on retrieving the page.
- (NSURLRequest *)connection:(NSURLConnection *)connection
             willSendRequest:(NSURLRequest *)request
            redirectResponse:(NSURLResponse *)response
{
  if (++mRedirectCount < 2)
    return request;
  else
    return nil;
}

// log the error and stop there. When importing bookmarks while not connected to the internet, you see this error quite a bit.
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
  NSLog([NSString stringWithFormat:@"Connection failed with error:%@",
    [error description]]);
  [connection release];
  if (connection == mImageConnection)
    mImageConnection = nil;
  else if (connection == mPageConnection) {
    mPageConnection = nil;
    [[NSNotificationCenter defaultCenter] postNotificationName:@"PartialLoad"
                                                        object:self];
  }
  [self setSiteIconImage:nil];
}

// search for a site icon tag in html or xml using the pcre library
- siteIconSearchInString: (NSString *) urlContents
{
  NSString * siteIconUrl = nil;

  
// /< *link[^>]+rel *= *"(shortcut )*icon"[^>]*href *= *"([^"]+)"/i'
  if (urlContents && ([urlContents length] > 0))
  {
    const char *error;
    const char *result;
    int erroffset;
    const char *subject = [urlContents UTF8String];
    int rc;
    int ovector[30];

    if (!mCompiledRegularExpression)
      mCompiledRegularExpression =
        pcre_compile(
                     "< *link[^>]+rel *= *\"(shortcut )*icon\"[^>]*href *= *\"([^\"]+)\"",
                     PCRE_UTF8 | PCRE_CASELESS,
                     &error,           
// for error message
                     &erroffset,       
// for error offset
                     NULL);            
// use default character tables
    rc = pcre_exec(mCompiledRegularExpression,  
// result of pcre_compile()
                   NULL,            
// we didn't study the pattern
                   subject,          
// the subject string
                   strlen(subject), 
// the length of the subject string
                   0,                
// start at offset 0 in the subject
                   0,                
// default options
                   ovector,          
// vector for substring information
                   30);              
// number of elements in the vector
    if (rc > 0) {
      pcre_get_substring(subject, ovector, rc, 2, &result);
      siteIconUrl = [[NSString alloc] initWithUTF8String:result];
      pcre_free_substring(result);
    }
  }

  return siteIconUrl;
}

// Retrieve a site icon from the server. At this point, we either have an image URL (href) from the page or we won't have one. If we don't have one, assume /favicon.ico.
- (void) loadSiteIcon:(NSString *) href
{
  NSURL *iconUrl;

  if (!href)
    href = @"/favicon.ico";
  iconUrl = [NSURL URLWithString:href relativeToURL:mPageUrl];

  mRedirectCount = 0;
  mImageConnection =
    [[NSURLConnection alloc] initWithRequest:[NSURLRequest requestWithURL:iconUrl]
                                    delegate:self];
  if (!mImageConnection)
    [self setSiteIconImage:nil];
}

// Sets the image and registers an undo event. newImage was just retrieved from the server.
- (void)setSiteIconImage:(NSImage *)newImage
{
  NSImage *currentImage = mSiteImage;
  if (currentImage != newImage)
    [[self undoManager] registerUndoWithTarget:self
                                      selector:@selector(setSiteIconImage:)
                                        object:currentImage];
  if (newImage)
    [mImages addObject:newImage];

  mSiteImage = newImage;
  [[NSNotificationCenter defaultCenter] postNotificationName:@"SiteImageChanged"
                                                      object:self];
}

#pragma mark  Fields

// the url for a standard internet location
- (NSString *)url
{
  return mUrl;
}

// Set the url, and unset the dynamic location if it is set (in which case we are switching from a dynamic location to a standard location.
- (void)setUrl:(NSString *)aUrl
{
  if (mDynamicLocation) {
    [mDynamicLocation release];
    mDynamicLocation = nil;
  }
  if (mUrl)
    [mUrl release];
  mUrl = [aUrl retain];
  [self setFileType:WEB_LOCATION_TYPE];
}

// the location (with unfilled parameters) for a dynamic location
- (NSString *)dynamicLocation
{
  return mDynamicLocation;
}

// Set the dynamic location, and unset the url if it is set (in which case we are switching from a standard location to a dynamic location.
- (void)setDynamicLocation:(NSString *)aDynamicLocation
{
  if (mUrl) {
    [mUrl release];
    mUrl = nil;
  }
  if (mDynamicLocation) [mDynamicLocation release];
  mDynamicLocation = [aDynamicLocation retain];
  [self setFileType:DYNAMIC_LOCATION_TYPE];
}

// Pointer to the site icon if there is one
- (NSImage *)siteImage
{
  return mSiteImage;
}

// These prefs are used with dynamic locations to save the fill in window location, search history, etc.
- (NSMutableDictionary *)specificPreferences
{
  if (!mSpecificPreferences)
    mSpecificPreferences = [[NSMutableDictionary alloc] initWithCapacity:8];
  return mSpecificPreferences;
}

#pragma mark  Deallocation

- (void)dealloc
{
  NSEnumerator *imageEnum = [mImages objectEnumerator];
  id object;

  [[self undoManager] removeAllActions];
  while (object = [imageEnum nextObject])
    [object release];
  [mImages release];

  
// If there is an existing connection, end it.
  [self endAnyConnection];

  if (mUrl) [mUrl release];
  if (mDynamicLocation) [mDynamicLocation release];
  if (mSpecificPreferences) [mSpecificPreferences release];
  if (mPageResponse) [mPageResponse release];
  if (mData) [mData release];
  if (mPageUrl) [mPageUrl release];
  if (mCompiledRegularExpression) pcre_free(mCompiledRegularExpression);

  [super dealloc];
}

@end