home *** CD-ROM | disk | FTP | other *** search
/ Tools / WinSN5.0Ver.iso / NETSCAP.50 / WIN1998.ZIP / ns / lib / libnet / crawler.c < prev    next >
Encoding:
C/C++ Source or Header  |  1998-04-08  |  52.3 KB  |  1,460 lines

  1. /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  2.  *
  3.  * The contents of this file are subject to the Netscape Public License
  4.  * Version 1.0 (the "NPL"); you may not use this file except in
  5.  * compliance with the NPL.  You may obtain a copy of the NPL at
  6.  * http://www.mozilla.org/NPL/
  7.  *
  8.  * Software distributed under the NPL is distributed on an "AS IS" basis,
  9.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
  10.  * for the specific language governing rights and limitations under the
  11.  * NPL.
  12.  *
  13.  * The Initial Developer of this code under the NPL is Netscape
  14.  * Communications Corporation.  Portions created by Netscape are
  15.  * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
  16.  * Reserved.
  17.  */
  18. /*** crawler.c ****************************************************/
  19. /*   description:        implementation for the html crawler.      */
  20.   
  21.  
  22.  /********************************************************************
  23.  
  24.   15.Jan.98 mchung
  25.     The crawler sometimes has the annoying behavior of thrashing when the cache is getting
  26.     full. When the available space is less than a threshold (sizeSlop) crawling will stop,
  27.     but the situation arises where we are outside the threshold and each file that comes
  28.     in is larger. Netlib will pull down the file and immediately remove it because it would
  29.     exceed the cache size. There are a couple of strategies for fixing this: increase the
  30.     sizeSlop when a file is not cached, so we cross the threshold sooner, or stop caching
  31.     after a certain number of files have not been cached, or some combination of these. 
  32.     In any case, crawling should _not_ stop if no cache has been specified.
  33.  
  34.     The approach I am taking for now is to increase the sizeSlop (which lowers the threshold
  35.     for the cache to be considered full) by SIZE_SLOP every time a file fails to be cached.
  36.     This would alleviate the thrashing problem but would result in the threshold being
  37.     lowered prematurely if the crawler encounters several large files which would exceed the cache,
  38.     before the cache is nearly full, but the remainder of the files are "normal" size. The
  39.     appropriate strategy really depends on how accurately the max cache size reflects the total
  40.     content size (if not accurate we need to be more aggressive about lowering the threshold), 
  41.     and how close to the cache limit we need to go.
  42.  
  43.     To do
  44.     (maybe) One service the crawler might provide is informing its client that a url has
  45.     changed. For a page this could also mean that one or more of its images or resources
  46.     has changed. In order to do this it would be necessary to track which images and resources
  47.     belong to which page(s) (or crawl them right after the page), find the url in the cache 
  48.     and store their last modified date and content length before calling netlib to get the url.
  49.     On return if the last modified date is later or the content length is different, the
  50.     url has changed.
  51.  
  52.     (definitely) i18n of the parser
  53.  
  54.   $Revision: 3.1 $
  55.   $Date: 1998/03/28 03:31:25 $
  56.  
  57.  *********************************************************************/
  58.  
  59. #include "xp.h"
  60. #include "xp_str.h"
  61. #include "xpassert.h"
  62. #include "prio.h"
  63. #include "prmem.h"
  64. #include "plhash.h"
  65. #include "prprf.h"
  66. #include "robotxt.h"
  67. #include "pagescan.h"
  68. #include "crawler.h"
  69.  
  70. /* #define CRAWLERTEST */
  71.  
  72. #ifdef CRAWLERTEST
  73. #include "fe_proto.h" /* FE_GetNetHelpContext */
  74. #include "prinrval.h"
  75. #include "mkextcac.h"
  76. #endif
  77.  
  78. #define SIZE_SLOP ((uint32)2000)
  79.  
  80. typedef uint8 CRAWL_Status;
  81. #define CRAWL_STOPPED            ((CRAWL_Status)0x00)
  82. #define CRAWL_STOP_REQUESTED    ((CRAWL_Status)0x01)
  83. #define CRAWL_RUNNING            ((CRAWL_Status)0x02)
  84.  
  85. typedef uint8 CRAWL_CrawlerItemType;
  86. #define CRAWLER_ITEM_TYPE_PAGE        ((CRAWL_CrawlerItemType)0x00)
  87. #define CRAWLER_ITEM_TYPE_IMAGE        ((CRAWL_CrawlerItemType)0x01)
  88. #define CRAWLER_ITEM_TYPE_RESOURCE    ((CRAWL_CrawlerItemType)0x02)
  89.  
  90. typedef uint8 CRAWL_LinkStatus;
  91.  
  92. #define NEW_LINK        ((CRAWL_LinkStatus)0x01)
  93. #define REPLACED_LINK    ((CRAWL_LinkStatus)0x02)
  94. #define OLD_LINK        ((CRAWL_LinkStatus)0x03)
  95.  
  96. typedef struct _CRAWL_LinkInfoStruc {
  97.     time_t lastModifiedDate;
  98.     CRAWL_LinkStatus status;
  99. } CRAWL_LinkInfoStruc;
  100.  
  101. /*
  102.  * Typedef for function callback (used internally)
  103.  */
  104.  typedef void
  105. (*CRAWL_ProcessItemFunc)(CRAWL_Crawler crawler, char *url, CRAWL_RobotControl control, CRAWL_CrawlerItemType type);
  106.  
  107. typedef struct _CRAWL_ItemTable {
  108.     CRAWL_ItemList items;
  109.     uint16 count;
  110. } CRAWL_ItemTable;
  111.  
  112. typedef struct _CRAWL_CrawlerStruct {
  113.     char *siteName;
  114.     char *siteHost;
  115.     uint8 depth; /* how many levels to crawl */
  116.     uint32 sizeSlop; /* size in bytes of the amount of space left in the cache for the cache
  117.                         to be considered full */
  118.  
  119.     CRAWL_ItemTable *linkedPagesTable; /* has entry for each depth containing all the page URLs */
  120.     CRAWL_ItemTable *linkedImagesTable; /* has entry for each depth containing all the image URLs*/
  121.     CRAWL_ItemTable *linkedResourcesTable; /* has entry for each depth containing all the resource URLs*/
  122.  
  123.     PLHashTable *pagesParsed; /* key is a url, value is last modified time */
  124.     PLHashTable *imagesCached; /* key is a url, value is last modified time */
  125.     PLHashTable *resourcesCached; /* key is a url, value is last modified time */
  126.  
  127.     CRAWL_Status status; /* is the crawler running? */
  128.     CRAWL_Error error;
  129.  
  130.     uint8 currentDepth; /* starts at 1 */
  131.     CRAWL_CrawlerItemType currentType; /* which type of item we're working on */
  132.     uint16 itemIndex; /* which item in the table we're working on */
  133.     CRAWL_ItemList requiredResources;
  134.     uint16 numRequiredResources;
  135.  
  136.     /* determines what items crawler is allowed or disallowed to crawl at a given site.
  137.        key is a site name, value is RobotControl */
  138.     PLHashTable *robotControlTable; 
  139.  
  140.     CRAWL_ItemList keys; /* keeps track of the hashtable keys (so they can be freed) */
  141.     uint16 numKeys;
  142.     uint16 sizeKeys;
  143.  
  144.     PRBool stayInSite;
  145.     PRBool manageCache; /* maintain a file which lists the cached items so links unreferenced in the next update may be removed */
  146.     MWContext *context; /* dummy context */
  147.     ExtCacheDBInfo *cache; /* external cache */
  148.     CRAWL_PostProcessItemFn postProcessItemFn;
  149.     void *postProcessItemData;
  150.     CRAWL_ExitFn exitFn;
  151.     void *exitData;
  152. } CRAWL_CrawlerStruct;
  153.  
  154. typedef struct _Crawl_DoProcessItemRecordStruct {
  155.     CRAWL_Crawler crawler;
  156.     CRAWL_CrawlerItemType type; /* which type of item we're working on */
  157.     char *url;
  158.     CRAWL_RobotControl control;
  159.     CRAWL_ProcessItemFunc func;
  160. } Crawl_DoProcessItemRecordStruct;
  161.  
  162. typedef Crawl_DoProcessItemRecordStruct *Crawl_DoProcessItemRecord;
  163.  
  164. extern void crawl_stringToLower(char *str);
  165. extern int crawl_appendStringList(char ***list, uint16 *len, uint16 *size, char *str);
  166.  
  167. /* prototypes */
  168. static PRBool crawl_hostEquals(char *pagehost, char *sitehost);
  169. static void crawl_destroyItemTable(CRAWL_ItemTable *table);
  170. static int crawl_appendToItemList(CRAWL_ItemList *list1, 
  171.                                    uint16 *numList1,
  172.                                    const CRAWL_ItemList list2, 
  173.                                    uint16 numList2);
  174. static int crawl_insertInItemList(CRAWL_ItemList *list1, 
  175.                                    uint16 *numList1,
  176.                                    const CRAWL_ItemList list2, 
  177.                                    uint16 numList2,
  178.                                    uint16 pos);
  179. static int crawl_destroyRobotControl(PLHashEntry *he, int i, void *arg);
  180. static int crawl_destroyLinkInfo(PLHashEntry *he, int i, void *arg);
  181. static PRBool crawl_cacheNearlyFull(CRAWL_Crawler crawler);
  182. static void crawl_processLinkWithRobotControl(CRAWL_Crawler crawler, 
  183.                                                char *url, 
  184.                                                CRAWL_RobotControl control, 
  185.                                                CRAWL_CrawlerItemType type);
  186. static int crawl_addCacheTableEntry(PLHashTable *ht, const char *key, time_t lastModifiedDate);
  187. static void crawl_executePostProcessItemFn(CRAWL_Crawler crawler, URL_Struct *URL_s, PRBool isCached);
  188. static void crawl_nonpage_exit(URL_Struct *URL_s, 
  189.                                        int status, 
  190.                                        MWContext *window_id, 
  191.                                        CRAWL_CrawlerItemType type);
  192. static void crawl_cache_image_exit(URL_Struct *URL_s, int status, MWContext *window_id);
  193. static void crawl_cache_resource_exit(URL_Struct *URL_s, int status, MWContext *window_id);
  194. static void crawl_processItemWithRobotControl(CRAWL_Crawler crawler, 
  195.                                                char *url, 
  196.                                                CRAWL_RobotControl control, 
  197.                                                CRAWL_CrawlerItemType type);
  198. static void crawl_processNextLink(CRAWL_Crawler crawler);
  199. static void crawl_scanPageComplete(void *data, CRAWL_PageInfo pageInfo);
  200. static PRBool crawl_isCrawlableURL(char *url);
  201. static Crawl_DoProcessItemRecord crawl_makeDoProcessItemRecord(CRAWL_Crawler crawler, 
  202.                                                                 char *url, 
  203.                                                                 CRAWL_RobotControl control, 
  204.                                                                 CRAWL_CrawlerItemType type, 
  205.                                                                 CRAWL_ProcessItemFunc func);
  206. static void crawl_doProcessItem(void *data);
  207. static void crawl_processLink(CRAWL_Crawler crawler, 
  208.                                PLHashTable *ht, 
  209.                                char *url, 
  210.                                CRAWL_ProcessItemFunc func, 
  211.                                CRAWL_CrawlerItemType type);
  212. static int crawl_writeCachedLinks(PLHashEntry *he, int i, void *arg);
  213. static int crawl_writeCachedImages(PLHashEntry *he, int i, void *arg);
  214. static int crawl_writeCachedResources(PLHashEntry *he, int i, void *arg);
  215. static char* crawl_makeCacheInfoFilename(CRAWL_Crawler crawler);
  216. static void crawl_writeCacheList(CRAWL_Crawler crawler);
  217. static int crawl_processCacheInfoEntry(CRAWL_Crawler crawler, char *line, PLHashTable *ht);
  218. static int crawl_processCacheInfoLine(CRAWL_Crawler crawler, char *line);
  219. static void crawl_removeDanglingLinksFromCache(CRAWL_Crawler crawler);
  220. static int crawl_updateCrawlerErrors(PLHashEntry *he, int i, void *arg);
  221. static void crawl_crawlerFinish(CRAWL_Crawler crawler);
  222. static void crawl_outOfMemory(CRAWL_Crawler crawler);
  223. #ifdef CRAWLERTEST
  224. void testCrawler(char *name, char *inURL, uint8 depth, uint32 maxSize, PRBool stayInSite);
  225. static void myPostProcessFn(CRAWL_Crawler crawler, URL_Struct *url_s, PRBool isCached, void *data);
  226. static void myExitFn(CRAWL_Crawler crawler, void *data);
  227. #endif
  228.  
  229. PR_IMPLEMENT(PRBool) CRAWL_IsStopped(CRAWL_Crawler crawler) {
  230.     return((crawler->status == CRAWL_STOPPED) ? PR_TRUE : PR_FALSE);
  231. }
  232.  
  233. PR_IMPLEMENT(CRAWL_Error) CRAWL_GetError(CRAWL_Crawler crawler) {
  234.     return(crawler->error);
  235. }
  236.  
  237. /* returns true if the host names are the same.
  238.  
  239.    The arguments are assumed to be already converted to lower case.
  240.  
  241.    If the hostname of the page is a substring of the hostname of the site, or
  242.    vice versa, return true. For example, w3 and w3.mcom.com
  243.  
  244.    This would fail if for example an intranet had a server called netscape. Then any
  245.    pages www.netscape.com would be considered to be on that server.
  246.  
  247.    If the domain names are the same, return true. The domain name is extracted by taking the
  248.    substring after the first dot. The domain name must contain another dot (www.yahoo.com and
  249.    search.yahoo.com are considered equivalent, but foo.org and bar.org are not).
  250.  
  251.    This fails to compare id addresses to host names
  252. */
  253. static PRBool 
  254. crawl_hostEquals(char *pagehost, char *sitehost) {
  255.     if ((XP_STRSTR(sitehost, pagehost) != NULL) || (XP_STRSTR(pagehost, sitehost) != NULL))
  256.         return PR_TRUE;
  257.     else {
  258.         char *pageDomain = XP_STRCHR(pagehost, '.');
  259.         char *siteDomain = XP_STRCHR(sitehost, '.');
  260.         if ((pageDomain != NULL) && (siteDomain != NULL)) {
  261.             char *pageDomainType = XP_STRCHR(pageDomain+1, '.');
  262.             char *siteDomainType = XP_STRCHR(siteDomain+1, '.');
  263.             if ((pageDomainType != NULL) &&
  264.                 (siteDomainType != NULL) &&
  265.                 (XP_STRCMP(pageDomain+1, siteDomain+1) == 0)) {
  266.                 return PR_TRUE;
  267.             }
  268.         }
  269.     }
  270.     return PR_FALSE;
  271. }
  272.  
  273. static void 
  274. crawl_destroyItemTable(CRAWL_ItemTable *table) {
  275.     uint16 count;
  276.     for (count = 0; count < table->count; count++) {
  277.         char *item = *(table->items + count);
  278.         if (item != NULL) PR_Free(item);
  279.     }
  280.     if (table->items != NULL) PR_Free(table->items);
  281. }
  282.  
  283. /* appends list2 to the end of list1. Returns -1 if no memory */
  284. static int 
  285. crawl_appendToItemList(CRAWL_ItemList *list1, 
  286.                                  uint16 *numList1,
  287.                                  const CRAWL_ItemList list2, 
  288.                                  uint16 numList2) {
  289.     /* this memory freed in CRAWL_DestroyCrawler */
  290.     CRAWL_ItemList newList = (CRAWL_ItemList)PR_Malloc(sizeof(CRAWL_Item) * (*numList1 + numList2));
  291.     CRAWL_ItemList old = *list1;
  292.     if (newList == NULL) return -1;
  293.     memcpy((char*)newList, (char*)*list1, sizeof(CRAWL_Item) * (*numList1)); /* copy first list */
  294.     memcpy((char*)(newList + *numList1), (char*)list2, sizeof(CRAWL_Item) * numList2);
  295.     *list1 = newList;
  296.     *numList1 += numList2;
  297.     if (old != NULL) PR_Free(old);
  298.     return 0;
  299. }
  300.  
  301. /* inserts list2 at (zero-indexed) position in list1. Returns -1 if no memory. */
  302. static int 
  303. crawl_insertInItemList(CRAWL_ItemList *list1, 
  304.                                  uint16 *numList1,
  305.                                  const CRAWL_ItemList list2, 
  306.                                  uint16 numList2,
  307.                                  uint16 pos) {
  308.     /* this memory freed in CRAWL_DestroyCrawler */
  309.     CRAWL_ItemList newList = (CRAWL_ItemList)PR_Malloc(sizeof(CRAWL_Item) * (*numList1 + numList2));
  310.     CRAWL_ItemList old = *list1;
  311.     if (newList == NULL) return -1;
  312.     memcpy((char*)newList, (char*)*list1, sizeof(CRAWL_Item) * pos); /* copy first list up to pos */
  313.     memcpy((char*)(newList + pos), (char*)list2, sizeof(CRAWL_Item) * numList2); /* copy second list */
  314.     memcpy((char*)(newList + pos + numList2), (char*)(*list1 + pos), sizeof(CRAWL_Item) * (*numList1 - pos));
  315.     *list1 = newList;
  316.     *numList1 += numList2;
  317.     if (old != NULL) PR_Free(old);
  318.     return 0;
  319. }
  320.  
  321. PR_IMPLEMENT(CRAWL_Crawler) 
  322. CRAWL_MakeCrawler(MWContext *context, 
  323.                           char *siteName, 
  324.                           uint8 depth, 
  325.                           PRBool stayInSite,
  326.                           PRBool manageCache,
  327.                           ExtCacheDBInfo *cache,
  328.                           CRAWL_PostProcessItemFn postProcessItemFn,
  329.                           void *postProcessItemData,
  330.                           CRAWL_ExitFn exitFn,
  331.                           void *exitData) {
  332.     CRAWL_Crawler crawler;
  333.     if (depth < 1) return NULL;
  334.     crawler = PR_NEWZAP(CRAWL_CrawlerStruct);
  335.     if (crawler == NULL) return NULL;
  336.     crawler->siteName = XP_STRDUP(siteName);
  337.     crawl_stringToLower(crawler->siteName);
  338.     crawler->siteHost = NET_ParseURL(crawler->siteName, GET_PROTOCOL_PART | GET_HOST_PART);
  339.     crawler->depth = depth;
  340.     crawler->sizeSlop = SIZE_SLOP;
  341.     crawler->stayInSite = stayInSite;
  342.     crawler->manageCache = manageCache;
  343.     /* this memory freed in CRAWL_DestroyCrawler */
  344.     crawler->linkedPagesTable = (CRAWL_ItemTable*)PR_Calloc(depth+1, sizeof(CRAWL_ItemTable));
  345.     crawler->linkedImagesTable = (CRAWL_ItemTable*)PR_Calloc(depth+1, sizeof(CRAWL_ItemTable));
  346.     crawler->linkedResourcesTable = (CRAWL_ItemTable*)PR_Calloc(depth+1, sizeof(CRAWL_ItemTable));
  347.     if (crawler->linkedPagesTable == NULL ||
  348.         crawler->linkedImagesTable == NULL ||
  349.         crawler->linkedResourcesTable == NULL)
  350.         return NULL;
  351.     crawler->pagesParsed = PL_NewHashTable(100, PL_HashString, PL_CompareStrings, PL_CompareValues, NULL, NULL); 
  352.     crawler->imagesCached = PL_NewHashTable(100, PL_HashString, PL_CompareStrings, PL_CompareValues, NULL, NULL); 
  353.     crawler->resourcesCached = PL_NewHashTable(100, PL_HashString, PL_CompareStrings, PL_CompareValues, NULL, NULL); 
  354.     crawler->robotControlTable = PL_NewHashTable(50, PL_HashString, PL_CompareStrings, PL_CompareValues, NULL, NULL); 
  355.     crawler->context = context;
  356.     crawler->cache = cache;
  357.     crawler->status = CRAWL_STOPPED;
  358.     crawler->postProcessItemFn = postProcessItemFn;
  359.     crawler->postProcessItemData = postProcessItemData;
  360.     crawler->exitFn = exitFn;
  361.     crawler->exitData = exitData;
  362.     return crawler;
  363. }
  364.  
  365. /* an enumerator function for the robotControlTable hashtable - maybe this should be moved to an allocator op */
  366. static int 
  367. crawl_destroyRobotControl(PLHashEntry *he, int i, void *arg) {
  368. #if defined(XP_MAC)
  369. #pragma unused(i, arg)
  370. #endif
  371.     CRAWL_DestroyRobotControl((CRAWL_RobotControl)he->value);
  372.     return HT_ENUMERATE_NEXT;
  373. }
  374.  
  375. /* an enumerator function for the cache tables */
  376. static int
  377. crawl_destroyLinkInfo(PLHashEntry *he, int i, void *arg) {
  378. #if defined(XP_MAC)
  379. #pragma unused(i, arg)
  380. #endif
  381.     if (he->value != NULL) PR_Free(he->value);
  382.     return HT_ENUMERATE_NEXT;
  383. }
  384.  
  385. PR_IMPLEMENT(void) 
  386. CRAWL_DestroyCrawler(CRAWL_Crawler crawler) {
  387.     int i;
  388.     if (crawler->siteName != NULL) PR_DELETE(crawler->siteName);
  389.     if (crawler->siteHost != NULL) PR_DELETE(crawler->siteHost);
  390.     PL_HashTableEnumerateEntries(crawler->pagesParsed, crawl_destroyLinkInfo, NULL);
  391.     PL_HashTableEnumerateEntries(crawler->imagesCached, crawl_destroyLinkInfo, NULL);
  392.     PL_HashTableEnumerateEntries(crawler->resourcesCached, crawl_destroyLinkInfo, NULL);
  393.     PL_HashTableEnumerateEntries(crawler->robotControlTable, crawl_destroyRobotControl, NULL);
  394.     for (i = 0; i < crawler->depth; i++) {
  395.         crawl_destroyItemTable(crawler->linkedPagesTable + i);
  396.         crawl_destroyItemTable(crawler->linkedImagesTable + i);
  397.         crawl_destroyItemTable(crawler->linkedResourcesTable + i);
  398.     }
  399.     PR_DELETE(crawler->linkedPagesTable);
  400.     PR_DELETE(crawler->linkedImagesTable);
  401.     PR_DELETE(crawler->linkedResourcesTable);
  402.     PL_HashTableDestroy(crawler->pagesParsed);
  403.     PL_HashTableDestroy(crawler->imagesCached);
  404.     PL_HashTableDestroy(crawler->resourcesCached);
  405.     PL_HashTableDestroy(crawler->robotControlTable);
  406.     for (i = 0; i < crawler->numKeys; i++) {
  407.         XP_FREE(crawler->keys[i]); /* these were created with XP_STRDUP so use XP_FREE */
  408.     }
  409.     if (crawler->keys != NULL) PR_DELETE(crawler->keys);
  410.     PR_DELETE(crawler);
  411. }
  412.  
  413. /* returns true if the cache is almost full (libnet insures that DiskCacheSize won't exceed MaxSize)
  414. */
  415. static PRBool 
  416. crawl_cacheNearlyFull(CRAWL_Crawler crawler) {
  417.     if ((crawler->cache != NULL) &&
  418.         (crawler->cache->MaxSize - crawler->cache->DiskCacheSize <= crawler->sizeSlop))
  419.         return PR_TRUE;
  420.     else return PR_FALSE;
  421. }
  422.  
  423. /* error handling for no memory, for situations where we want to exit right away. */
  424. static void 
  425. crawl_outOfMemory(CRAWL_Crawler crawler) {
  426.     crawler->error |= CRAWL_NO_MEMORY;
  427.     crawl_crawlerFinish(crawler);
  428. }
  429.  
  430. /* scans a page if robots.txt allows it */
  431. static void 
  432. crawl_processLinkWithRobotControl(CRAWL_Crawler crawler, 
  433.                                     char *url, 
  434.                                     CRAWL_RobotControl control, 
  435.                                     CRAWL_CrawlerItemType type) {
  436.     XP_ASSERT(type == CRAWLER_ITEM_TYPE_PAGE);
  437.     if (CRAWL_GetRobotControl(control, url) == CRAWL_ROBOT_DISALLOWED) {
  438.         crawl_processNextLink(crawler);
  439.     } else {
  440.         CRAWL_PageInfo pageInfo = crawl_makePage(crawler->siteName, url, crawler->cache);
  441.         if (pageInfo != NULL) {
  442.             crawl_scanPage(pageInfo, crawler->context, crawl_scanPageComplete, crawler);
  443.         } else crawl_outOfMemory(crawler);
  444.     }
  445. }
  446.  
  447. /* add an entry to the table of items cached with the last modified time */
  448. static int
  449. crawl_addCacheTableEntry(PLHashTable *ht, const char *key, time_t lastModifiedDate) {
  450.     CRAWL_LinkInfoStruc *info = PR_NEWZAP(CRAWL_LinkInfoStruc);
  451.     if (info == NULL) return -1;
  452.     info->lastModifiedDate = lastModifiedDate;
  453.     info->status = NEW_LINK;
  454.     PL_HashTableAdd(ht, key, (void*)info);
  455.     return 0;
  456. }
  457.  
  458. static void crawl_executePostProcessItemFn(CRAWL_Crawler crawler, URL_Struct *URL_s, PRBool isCached) {
  459.     if (!isCached) crawler->sizeSlop += SIZE_SLOP; /* lower the threshold for cache fullness */
  460.     if (crawler->postProcessItemFn != NULL) 
  461.         (crawler->postProcessItemFn)(crawler, URL_s, isCached, crawler->postProcessItemData);
  462. }
  463.  
  464. /* common completion code for images and resources */
  465. static void
  466. crawl_nonpage_exit(URL_Struct *URL_s, int status, MWContext *window_id, CRAWL_CrawlerItemType type) {
  467. #if defined(XP_MAC)
  468. #pragma unused(window_id)
  469. #endif    
  470.     int err = 0;
  471.     CRAWL_Crawler crawler = (CRAWL_Crawler)URL_s->owner_data;
  472.     PLHashTable *table = NULL;
  473.     switch(type) {
  474.     case CRAWLER_ITEM_TYPE_IMAGE:
  475.         table = crawler->imagesCached;
  476.         break;
  477.     case CRAWLER_ITEM_TYPE_RESOURCE:
  478.         table = crawler->resourcesCached;
  479.         break;
  480.     default:
  481.         break;
  482.     }
  483.     XP_ASSERT(table != NULL);
  484.  
  485.     if (URL_s->server_status >= 400) crawler->error |= CRAWL_SERVER_ERR;
  486.     /* add to the images cached if we are in fact caching and the cache_file is set */
  487.     if ((status >= 0) && ((crawler->cache == NULL) || (URL_s->cache_file != NULL))) {
  488.         char *url = XP_STRDUP(URL_s->address);
  489.         if (url == NULL) {
  490.             crawl_outOfMemory(crawler);
  491.             return;
  492.         }
  493.         err = crawl_addCacheTableEntry(table, url, URL_s->last_modified);
  494.         crawl_executePostProcessItemFn(crawler, URL_s, PR_TRUE);
  495.         if (err == 0)
  496.             err = crawl_appendStringList(&crawler->keys, &crawler->numKeys, &crawler->sizeKeys, url);
  497.     } else {
  498.         crawl_executePostProcessItemFn(crawler, URL_s, PR_FALSE);
  499.     }
  500.  
  501.     if (status != MK_CHANGING_CONTEXT)
  502.         NET_FreeURLStruct(URL_s);
  503.  
  504.     if (err == 0) crawl_processNextLink(crawler);
  505.     else crawl_outOfMemory(crawler); /* alert! assumes any error code returned means out of memory */
  506. }
  507.  
  508. /* exit routine for NET_GetURL for images */
  509. static void
  510. crawl_cache_image_exit(URL_Struct *URL_s, int status, MWContext *window_id)
  511. {
  512.     crawl_nonpage_exit(URL_s, status, window_id, CRAWLER_ITEM_TYPE_IMAGE);
  513. }
  514.  
  515. /* exit routine for NET_GetURL for resources */
  516. static void
  517. crawl_cache_resource_exit(URL_Struct *URL_s, int status, MWContext *window_id)
  518. {
  519.     crawl_nonpage_exit(URL_s, status, window_id, CRAWLER_ITEM_TYPE_RESOURCE);
  520. }
  521.  
  522. /* caches an image or resource if robots.txt allows it */
  523. static 
  524. void crawl_processItemWithRobotControl(CRAWL_Crawler crawler, 
  525.                                     char *url, 
  526.                                     CRAWL_RobotControl control, 
  527.                                     CRAWL_CrawlerItemType type) {
  528.     if (CRAWL_GetRobotControl(control, url) == CRAWL_ROBOT_DISALLOWED) {
  529.         crawl_processNextLink(crawler);
  530.     } else if (crawler->cache != NULL) {
  531.         URL_Struct *url_s;
  532.         url_s = NET_CreateURLStruct(url, NET_NORMAL_RELOAD);
  533.         if (url_s == NULL) crawl_outOfMemory(crawler);
  534.         url_s->load_background = PR_TRUE;
  535.         url_s->SARCache = crawler->cache;
  536.         url_s->owner_data = crawler;
  537.         switch (type) {
  538.         case CRAWLER_ITEM_TYPE_IMAGE:
  539.             NET_GetURL(url_s, FO_CACHE_AND_CRAWL_RESOURCE, crawler->context, crawl_cache_image_exit);
  540.             break;
  541.         case CRAWLER_ITEM_TYPE_RESOURCE:
  542.             NET_GetURL(url_s, FO_CACHE_AND_CRAWL_RESOURCE, crawler->context, crawl_cache_resource_exit);
  543.             break;
  544.         }
  545.     }
  546. }
  547.  
  548. /* Process the next link in the table at the current depth.
  549.    Pages at the previous depth are scanned, and then images and resources at the current depth
  550.    are cached.
  551. */
  552. #ifndef DEFER_RESOURCE_SCAN
  553.  
  554. static
  555. void crawl_processNextLink(CRAWL_Crawler crawler) {
  556.     PRBool allDone = PR_FALSE;
  557.     PLHashTable *completedTable;
  558.     CRAWL_ProcessItemFunc func = NULL;
  559.     static uint16 requiredIndex = 0;
  560.  
  561.     /* parse all the pages at the previous depth, (this includes also frames and layers for
  562.        the most recently scanned page) and then process the images and resources
  563.        at the current depth.
  564.     */
  565.     if (crawler->currentDepth <= crawler->depth) {
  566.         CRAWL_ItemTable *table;
  567.         switch (crawler->currentType) {
  568.         case CRAWLER_ITEM_TYPE_PAGE:
  569.             /* if the previous page had any required resources, cache them now. */
  570.             if (crawler->requiredResources != NULL) {
  571.                 XP_TRACE(("required resources"));
  572.                 if (requiredIndex < crawler->numRequiredResources) {
  573.                     crawl_processLink(crawler, 
  574.                                     crawler->resourcesCached, 
  575.                                     *(crawler->requiredResources + requiredIndex++), 
  576.                                     crawl_processItemWithRobotControl,
  577.                                     CRAWLER_ITEM_TYPE_RESOURCE);
  578.                     return; /* wait for callback */
  579.                 } else {
  580.                     uint16 i;
  581.                     for (i = 0; i < crawler->numRequiredResources; i++) {
  582.                         PR_Free(*(crawler->requiredResources + i));
  583.                     }
  584.                     requiredIndex = crawler->numRequiredResources = 0;
  585.                     PR_DELETE(crawler->requiredResources);
  586.                 }
  587.             }
  588.             /* process the pages at the previous level */
  589.             table = crawler->linkedPagesTable + crawler->currentDepth - 1;
  590.             func = crawl_processLinkWithRobotControl;
  591.             completedTable = crawler->pagesParsed;
  592.             if (crawler->itemIndex == table->count) { /* no more items */
  593.                 /* done with the pages, now do the images */
  594.                 XP_TRACE(("finished pages"));
  595.                 func = crawl_processItemWithRobotControl;
  596.                 crawler->currentType = CRAWLER_ITEM_TYPE_IMAGE;
  597.                 completedTable = crawler->imagesCached;
  598.                 table = crawler->linkedImagesTable + crawler->currentDepth;
  599.                 crawler->itemIndex = 0;
  600.             }
  601.             break;
  602.         case CRAWLER_ITEM_TYPE_IMAGE: 
  603.             table = crawler->linkedImagesTable + crawler->currentDepth;
  604.             func = crawl_processItemWithRobotControl;
  605.             completedTable = crawler->imagesCached;
  606.             if (crawler->itemIndex == table->count) { /* no more items */
  607.                 XP_TRACE(("finished images"));
  608.                 /* done with the images, now do the resources */
  609.                 func = crawl_processItemWithRobotControl;
  610.                 crawler->currentType = CRAWLER_ITEM_TYPE_RESOURCE;
  611.                 completedTable = crawler->resourcesCached;
  612.                 table = crawler->linkedResourcesTable + crawler->currentDepth;
  613.                 crawler->itemIndex = 0;
  614.             }
  615.             break;
  616.         case CRAWLER_ITEM_TYPE_RESOURCE: 
  617.             table = crawler->linkedResourcesTable + crawler->currentDepth;
  618.             func = crawl_processItemWithRobotControl;
  619.             completedTable = crawler->resourcesCached;
  620.             if (crawler->itemIndex == table->count) { /* no more items */
  621.                 XP_TRACE(("finished resources"));
  622.                 if (crawler->currentDepth == crawler->depth) {
  623.                     allDone = PR_TRUE;
  624.                     break;
  625.                 }
  626.                 /* done with the resources, now go to next level */
  627.                 func = crawl_processLinkWithRobotControl;
  628.                 crawler->currentType = CRAWLER_ITEM_TYPE_PAGE;
  629.                 completedTable = crawler->pagesParsed;
  630.                 crawler->currentDepth++;
  631.                 XP_TRACE(("depth = %d", crawler->currentDepth));
  632.                 crawler->itemIndex = 0;
  633.                 table = crawler->linkedPagesTable + crawler->currentDepth - 1;
  634.             }
  635.             break;
  636.         }
  637.         if (!allDone) {
  638.             if (table->count == crawler->itemIndex) crawl_processNextLink(crawler); /* new table is empty */
  639.             else {
  640.                 crawl_processLink(crawler, 
  641.                                 completedTable, 
  642.                                 *(table->items + (crawler->itemIndex++)), 
  643.                                 func, 
  644.                                 crawler->currentType);
  645.             }
  646.         } else {
  647.             crawl_crawlerFinish(crawler);
  648.         }
  649.     }
  650. }
  651.  
  652. #else
  653.  
  654. /* this version traverses the tree like Netcaster 1.0: all the pages at all the depths, then all the
  655.    images at all the depths, and then all the resources at all the depths.
  656. */
  657. static
  658. void crawl_processNextLink(CRAWL_Crawler crawler) {
  659.     PRBool allDone = PR_FALSE;
  660.     PLHashTable *completedTable;
  661.     CRAWL_ProcessItemFunc func = NULL;
  662.     static uint16 requiredIndex = 0;
  663.  
  664.     /* parse all the pages at the previous depth, (this includes also frames and layers for
  665.        the most recently scanned page) and then process the images and resources
  666.        at the current depth.
  667.     */
  668.     if (crawler->currentDepth <= crawler->depth) {
  669.         CRAWL_ItemTable *table;
  670.         switch (crawler->currentType) {
  671.         case CRAWLER_ITEM_TYPE_PAGE:
  672.             /* if the previous page had any required resources, cache them now. */
  673.             if (crawler->requiredResources != NULL) {
  674.                 XP_TRACE(("required resources"));
  675.                 if (requiredIndex < crawler->numRequiredResources) {
  676.                     crawl_processLink(crawler, 
  677.                                     crawler->resourcesCached, 
  678.                                     *(crawler->requiredResources + requiredIndex++), 
  679.                                     crawl_processItemWithRobotControl,
  680.                                     CRAWLER_ITEM_TYPE_RESOURCE);
  681.                     return; /* wait for callback */
  682.                 } else {
  683.                     uint16 i;
  684.                     for (i = 0; i < crawler->numRequiredResources; i++) {
  685.                         PR_Free(*(crawler->requiredResources + i));
  686.                     }
  687.                     requiredIndex = crawler->numRequiredResources = 0;
  688.                     PR_DELETE(crawler->requiredResources);
  689.                 }
  690.             }
  691.             /* process the pages at the previous level */
  692.             table = crawler->linkedPagesTable + crawler->currentDepth - 1;
  693.             func = crawl_processLinkWithRobotControl;
  694.             completedTable = crawler->pagesParsed;
  695.             if (crawler->itemIndex == table->count) { /* no more items */
  696.                 /* done with the pages at this level, now go to next level */
  697.                 if (crawler->currentDepth < crawler->depth) {
  698.                     crawler->currentDepth++;
  699.                     XP_TRACE(("depth = %d", crawler->currentDepth));
  700.                     crawler->itemIndex = 0;
  701.                 } else {
  702.                     /* done with pages, now do images */
  703.                     crawler->currentDepth = 1;
  704.                     crawler->itemIndex = 0;
  705.                     func = crawl_processItemWithRobotControl;
  706.                     crawler->currentType = CRAWLER_ITEM_TYPE_IMAGE;
  707.                     completedTable = crawler->imagesCached;
  708.                     table = crawler->linkedImagesTable + crawler->currentDepth;
  709.                 }
  710.             }
  711.             break;
  712.         case CRAWLER_ITEM_TYPE_IMAGE: 
  713.             table = crawler->linkedImagesTable + crawler->currentDepth;
  714.             func = crawl_processItemWithRobotControl;
  715.             completedTable = crawler->imagesCached;
  716.             if (crawler->itemIndex == table->count) { /* no more items */
  717.                 if (crawler->currentDepth < crawler->depth) {
  718.                     crawler->currentDepth++;
  719.                     XP_TRACE(("depth = %d", crawler->currentDepth));
  720.                     crawler->itemIndex = 0;
  721.                 } else {
  722.                     /* done with the images, now do the resources */
  723.                     crawler->currentDepth = 1;
  724.                     crawler->itemIndex = 0;
  725.                     func = crawl_processItemWithRobotControl;
  726.                     crawler->currentType = CRAWLER_ITEM_TYPE_RESOURCE;
  727.                     completedTable = crawler->resourcesCached;
  728.                     table = crawler->linkedResourcesTable + crawler->currentDepth;
  729.                     crawler->itemIndex = 0;
  730.                 }
  731.             }
  732.             break;
  733.         case CRAWLER_ITEM_TYPE_RESOURCE: 
  734.             table = crawler->linkedResourcesTable + crawler->currentDepth;
  735.             func = crawl_processItemWithRobotControl;
  736.             completedTable = crawler->resourcesCached;
  737.             if (crawler->itemIndex == table->count) { /* no more items */
  738.                 if (crawler->currentDepth < crawler->depth) {
  739.                     crawler->currentDepth++;
  740.                     XP_TRACE(("depth = %d", crawler->currentDepth));
  741.                     crawler->itemIndex = 0;
  742.                 } else {
  743.                     allDone = PR_TRUE;
  744.                     break;
  745.                 }
  746.             }
  747.             break;
  748.         }
  749.         if (!allDone) {
  750.             if (table->count == crawler->itemIndex) crawl_processNextLink(crawler); /* new table is empty */
  751.             else {
  752.                 crawl_processLink(crawler, 
  753.                                 completedTable, 
  754.                                 *(table->items + (crawler->itemIndex++)), 
  755.                                 func, 
  756.                                 crawler->currentType);
  757.             }
  758.         } else {
  759.             crawl_crawlerFinish(crawler);
  760.         }
  761.     }
  762. }
  763. #endif
  764.  
  765. /* adds links from the page just parsed to the appropriate table, and continue.
  766.    This is a completion routine for the page scan.
  767. */
  768. static
  769. void crawl_scanPageComplete(void *data, CRAWL_PageInfo pageInfo) {
  770.     int err = 0;
  771.     CRAWL_Crawler crawler = (CRAWL_Crawler)data;
  772.     URL_Struct *url_s = crawl_getPageURL_Struct(pageInfo);
  773.     char *url = XP_STRDUP(crawl_getPageURL(pageInfo));
  774.  
  775.     if (url == NULL) crawl_outOfMemory(crawler);
  776.  
  777.     if (url_s->server_status >= 400) crawler->error |= CRAWL_SERVER_ERR;
  778.  
  779.     /* add url to pages parsed only if it was actually cached - this might mean that
  780.        we would parse the url again if encountered, but this should only be encountered 
  781.        when the cache is full, and we're about to quit.
  782.     */
  783.     if (crawl_pageCanBeIndexed(pageInfo)) { /* no meta robots tag directing us not to index, i.e. cache */
  784.         if ((crawler->cache == NULL) || (url_s->cache_file != NULL)) { /* was cached, or not cache specified */
  785.             err = crawl_addCacheTableEntry(crawler->pagesParsed, url, crawl_getPageLastModified(pageInfo));
  786.             crawl_executePostProcessItemFn(crawler, url_s, PR_TRUE);
  787.             if (err == 0)
  788.                 err = crawl_appendStringList(&crawler->keys, &crawler->numKeys, &crawler->sizeKeys, url);
  789.         } else { /* wasn't cached */
  790.             crawl_executePostProcessItemFn(crawler, url_s, PR_FALSE);
  791.         }
  792.     } else { /* obey meta robots tag and remove from cache. */
  793.         NET_RemoveURLFromCache(url_s);
  794.         if (crawler->postProcessItemFn != NULL) 
  795.             (crawler->postProcessItemFn)(crawler, url_s, PR_FALSE, crawler->postProcessItemData);
  796.     }
  797.  
  798.     if ((crawl_getPageLinks(pageInfo) != NULL) && (err == 0)) {
  799.         /* add links to pages at depth */
  800.         err = crawl_appendToItemList(&(crawler->linkedPagesTable + crawler->currentDepth)->items,
  801.                                     &(crawler->linkedPagesTable + crawler->currentDepth)->count,
  802.                                     crawl_getPageLinks(pageInfo),
  803.                                     crawl_getPageLinkCount(pageInfo));
  804.     }
  805.     if ((crawl_getPageImages(pageInfo) != NULL) && (err == 0)) {
  806.         /* add images to images at depth */
  807.         err = crawl_appendToItemList(&(crawler->linkedImagesTable + crawler->currentDepth)->items,
  808.                                     &(crawler->linkedImagesTable + crawler->currentDepth)->count,
  809.                                     crawl_getPageImages(pageInfo),
  810.                                     crawl_getPageImageCount(pageInfo));
  811.     }
  812.     if ((crawl_getPageResources(pageInfo) != NULL) && (err == 0)) {
  813.         /* add resources to resources at depth */
  814.         err = crawl_appendToItemList(&(crawler->linkedResourcesTable + crawler->currentDepth)->items,
  815.                                     &(crawler->linkedResourcesTable + crawler->currentDepth)->count,
  816.                                     crawl_getPageResources(pageInfo),
  817.                                     crawl_getPageResourceCount(pageInfo));
  818.     }
  819.     if ((crawl_getPageFrames(pageInfo) != NULL) && (err == 0)) {
  820.         /* add frames to pages currently being processed (next link will be a frame) */
  821.         err = crawl_insertInItemList(&(crawler->linkedPagesTable + crawler->currentDepth - 1)->items,
  822.                                     &(crawler->linkedPagesTable + crawler->currentDepth - 1)->count,
  823.                                     crawl_getPageFrames(pageInfo),
  824.                                     crawl_getPageFrameCount(pageInfo),
  825.                                     crawler->itemIndex);
  826.     }
  827.     if ((crawl_getPageLayers(pageInfo) != NULL) && (err == 0)){
  828.         /* add layers to pages currently being processed */
  829.         err = crawl_insertInItemList(&(crawler->linkedPagesTable + crawler->currentDepth - 1)->items,
  830.                                     &(crawler->linkedPagesTable + crawler->currentDepth - 1)->count,
  831.                                     crawl_getPageLayers(pageInfo),
  832.                                     crawl_getPageLayerCount(pageInfo),
  833.                                     crawler->itemIndex);
  834.     }
  835.     if ((crawl_getPageRequiredResources(pageInfo) != NULL) && (err == 0)) {
  836.         err = crawl_appendToItemList(&crawler->requiredResources,
  837.                                     &crawler->numRequiredResources,
  838.                                     crawl_getPageRequiredResources(pageInfo),
  839.                                     crawl_getPageRequiredResourceCount(pageInfo));
  840.     }
  841.     /* crawler->currentPage = pageInfo; */
  842.     if (err == 0) crawl_processNextLink(crawler);
  843.     else crawl_outOfMemory(crawler);
  844. }
  845.  
  846. /* returns false if the url is empty or contains any entities */
  847. static
  848. PRBool crawl_isCrawlableURL(char *url) {
  849.     char *amp, *semicolon;
  850.     if (*url == '\0') return PR_FALSE;
  851.     amp = XP_STRCHR(url, '&');
  852.     if (amp != NULL) {
  853.         semicolon = XP_STRCHR(amp, ';');
  854.         if (semicolon != NULL) return PR_FALSE; /* don't crawl any url with entities */
  855.     }
  856.     return PR_TRUE;
  857. }
  858.  
  859. /* callback structure for the robots.txt parser, freed in robotxt.c */
  860. static Crawl_DoProcessItemRecord 
  861. crawl_makeDoProcessItemRecord(CRAWL_Crawler crawler, char *url, CRAWL_RobotControl control, CRAWL_CrawlerItemType type, CRAWL_ProcessItemFunc func) {
  862.     Crawl_DoProcessItemRecord rec;
  863.     rec = (Crawl_DoProcessItemRecord)PR_Malloc(sizeof(Crawl_DoProcessItemRecordStruct));
  864.     rec->crawler = crawler;
  865.     rec->control = control;
  866.     rec->url = url;
  867.     rec->type = type;
  868.     rec->func = func;
  869.     return rec;
  870. }
  871.  
  872. /* callback for the robots.txt parser */
  873. static void 
  874. crawl_doProcessItem(void *data) {
  875.     Crawl_DoProcessItemRecord rec = (Crawl_DoProcessItemRecord)data;
  876.     rec->func(rec->crawler, rec->url, rec->control, rec->type);
  877. }
  878.  
  879. /* processes a link (page, image, or resource).
  880. */
  881. static void 
  882. crawl_processLink(CRAWL_Crawler crawler, 
  883.                     PLHashTable *ht, 
  884.                     char *url, 
  885.                     CRAWL_ProcessItemFunc func, 
  886.                     CRAWL_CrawlerItemType type) {
  887.     CRAWL_RobotControl control;
  888.     char *siteURL;
  889.     PLHashNumber keyHash;
  890.     PLHashEntry *he, **hep;
  891.  
  892.     if (crawler->status == CRAWL_STOP_REQUESTED) {
  893.         crawler->error |= CRAWL_INTERRUPTED;
  894.         crawl_crawlerFinish(crawler); /* stop update */
  895.         return;
  896.     }
  897.  
  898.     if (crawl_cacheNearlyFull(crawler)) {
  899.         XP_TRACE(("crawl_processLink: cache is full, stopping"));
  900.         crawler->error |= CRAWL_CACHE_FULL;
  901.         crawl_crawlerFinish(crawler); /* stop update */
  902.         return;
  903.     }
  904.     
  905.     if (!crawl_isCrawlableURL(url)) {
  906.         crawl_processNextLink(crawler);
  907.         return;
  908.     }
  909.  
  910.     /* check if already processed - use raw lookup because value can be 0 */
  911.     keyHash = (*ht->keyHash)(url);
  912.     hep = PL_HashTableRawLookup(ht, keyHash, url);
  913.     if ((he = *hep) != 0) {
  914.         crawl_processNextLink(crawler);
  915.         return;
  916.     }
  917.  
  918.     siteURL = NET_ParseURL(url, GET_PROTOCOL_PART | GET_HOST_PART); /* XP_ALLOC'd */
  919.     crawl_stringToLower(siteURL);
  920.  
  921.     if (crawler->stayInSite && !crawl_hostEquals(siteURL, crawler->siteHost)) {
  922.         XP_FREE(siteURL);
  923.         crawl_processNextLink(crawler); /* skip this item */
  924.         return;
  925.     }
  926.  
  927.     /* get robot directives for this site, creating if it doesn't exist */
  928.     control = PL_HashTableLookup(crawler->robotControlTable, siteURL);
  929.     if (control == NULL) {
  930.         control = CRAWL_MakeRobotControl(crawler->context, siteURL);
  931.         if (control != NULL) {
  932.             Crawl_DoProcessItemRecord rec;
  933.             PL_HashTableAdd(crawler->robotControlTable, siteURL, control);
  934.             /* keep a separate list of the hosts around so we can free them later on */
  935.             if (crawl_appendStringList(&crawler->keys, &crawler->numKeys, &crawler->sizeKeys, siteURL) < 0) {
  936.                 crawl_outOfMemory(crawler);
  937.                 return;
  938.             }
  939.             /* if we successfully issue request for robots.txt, return (link processed in callback),
  940.              , otherwise process it now. */
  941.             rec = crawl_makeDoProcessItemRecord(crawler, url, control, type, func);
  942.             if (rec == NULL) {
  943.                 crawl_outOfMemory(crawler);
  944.                 return;
  945.             }
  946.             if (CRAWL_ReadRobotControlFile(control, crawl_doProcessItem, rec, PR_TRUE)) return; /* wait for the callback */
  947.         } else { 
  948.             XP_FREE(siteURL);
  949.             crawl_outOfMemory(crawler);
  950.             return;
  951.         }
  952.     } else XP_FREE(siteURL); /* we found a robot control */
  953.  
  954.     if (control != NULL) {
  955.         func(crawler, url, control, type);
  956.     }
  957. }
  958.  
  959. /* starts crawling from the url specified */
  960. PR_IMPLEMENT(void) 
  961. CRAWL_StartCrawler(CRAWL_Crawler crawler, char *url) {
  962.     crawler->currentDepth = 1;
  963.     crawler->status = CRAWL_RUNNING;
  964.     /* just assume it's a page for now. The crawler converter won't attempt to
  965.        parse it if the mime type is not text/html. */
  966.     crawl_processLink(crawler, 
  967.                                             crawler->pagesParsed, 
  968.                                             url, 
  969.                                             crawl_processLinkWithRobotControl, 
  970.                                             CRAWLER_ITEM_TYPE_PAGE);
  971. }
  972.  
  973. /* stops crawling safely */
  974. PR_IMPLEMENT(void) 
  975. CRAWL_StopCrawler(CRAWL_Crawler crawler) {
  976.     crawler->status = CRAWL_STOP_REQUESTED;
  977. }
  978.  
  979. /****************************************************************************************/
  980. /* cache management using .dat file                                                        */
  981. /****************************************************************************************/
  982.  
  983. static int 
  984. crawl_writeCachedLinks(PLHashEntry *he, int i, void *arg) {
  985. #if defined(XP_MAC)
  986. #pragma unused(i)
  987. #endif    
  988.     PRFileDesc *fd = (PRFileDesc *)arg;
  989.     PR_fprintf(fd, "L>%s/%lu\n", he->key, he->value);
  990.     return HT_ENUMERATE_NEXT;
  991. }
  992.  
  993. static int 
  994. crawl_writeCachedImages(PLHashEntry *he, int i, void *arg) {
  995. #if defined(XP_MAC)
  996. #pragma unused(i)
  997. #endif    
  998.     PRFileDesc *fd = (PRFileDesc *)arg;
  999.     PR_fprintf(fd, "I>%s/%lu\n", he->key, he->value);
  1000.     return HT_ENUMERATE_NEXT;
  1001. }
  1002.  
  1003. static int 
  1004. crawl_writeCachedResources(PLHashEntry *he, int i, void *arg) {
  1005. #if defined(XP_MAC)
  1006. #pragma unused(i)
  1007. #endif    
  1008.     PRFileDesc *fd = (PRFileDesc *)arg;
  1009.     PR_fprintf(fd, "R>%s/%lu\n", he->key, he->value);
  1010.     return HT_ENUMERATE_NEXT;
  1011. }
  1012.  
  1013. /* result is malloc'd on Windows, not on Mac or UNIX.
  1014.    crawler->cache is assumed to be non-null. */
  1015. static char*
  1016. crawl_makeCacheInfoFilename(CRAWL_Crawler crawler) {
  1017. #if defined(XP_MAC)
  1018.     OSErr ConvertMacPathToUnixPath(const char *macPath, char **unixPath);
  1019. #endif    
  1020.     char *tmp = NULL, *tmpName, *dot, *filename;
  1021.  
  1022.     tmp = (char *)PR_MALLOC(XP_STRLEN(crawler->cache->filename) + 5); /* +5 for .dat and null termination */
  1023.     XP_STRCPY(tmp, crawler->cache->filename);
  1024.     if (tmp == NULL) return NULL;
  1025.     dot = XP_STRCHR(tmp, '.');
  1026.     if (dot != NULL) *dot = '\0';
  1027.     XP_STRCAT(tmp, ".dat");
  1028.     tmpName = WH_FileName(tmp, xpSARCache);
  1029. #ifndef XP_MAC
  1030.     filename = WH_FilePlatformName(tmpName);
  1031. #else
  1032.     /* unfortunately PR_Open doesn't like the output of WH_FileName, we have to
  1033.        convert it to a Unix path, or use the XP_File routines.
  1034.     */
  1035.     /* filename = tmpName; */
  1036.     ConvertMacPathToUnixPath(tmpName, &filename);
  1037. #endif
  1038.     XP_FREE(tmp);
  1039.     return filename;
  1040. }
  1041.  
  1042. /* Writes list of all links, images, and resources that were cached by the crawler.
  1043.    crawler->cache is assumed to be non-null.
  1044. */
  1045. static void 
  1046. crawl_writeCacheList(CRAWL_Crawler crawler) {
  1047.     PRFileDesc *fd;
  1048.     char *filename = crawl_makeCacheInfoFilename(crawler);
  1049.     if (filename != NULL) {
  1050.         PR_Delete(filename);
  1051.         fd = PR_Open(filename,  PR_CREATE_FILE | PR_RDWR, 0644);
  1052.         if(fd == NULL) return;
  1053.         /* write here */
  1054.         PL_HashTableEnumerateEntries(crawler->pagesParsed, crawl_writeCachedLinks, fd);
  1055.         PL_HashTableEnumerateEntries(crawler->imagesCached, crawl_writeCachedImages, fd);
  1056.         PL_HashTableEnumerateEntries(crawler->resourcesCached, crawl_writeCachedResources, fd);
  1057.         PR_Close(fd);
  1058. #ifdef XP_WIN
  1059.         PR_Free(filename); /* WH_FilePlatformName malloc's on Win but not Mac and X */
  1060. #endif
  1061.     } else crawler->error |= CRAWL_NO_MEMORY;
  1062. }
  1063.  
  1064. /* if the cache item specified in the line does not exist in the table, remove it from the cache 
  1065.    returns -1 if no memory, 0 for no error. */
  1066. static int 
  1067. crawl_processCacheInfoEntry(CRAWL_Crawler crawler, char *line, PLHashTable *ht) {
  1068.     PLHashNumber keyHash;
  1069.     PLHashEntry *he, **hep;
  1070.     char old;
  1071.     char *slash = XP_STRRCHR(line, '/');
  1072.     char *gt = XP_STRCHR(line, '>');
  1073.     if ((slash != NULL) && (gt != NULL)) {
  1074.         char *url = gt + 1;
  1075.         char *date = slash + 1;
  1076.         old = *slash;
  1077.         *slash = '\0'; /* temporarily null terminate url */
  1078.         /* check if exists - use raw lookup because value can be 0 */
  1079.         keyHash = (*ht->keyHash)(url);
  1080.         hep = PL_HashTableRawLookup(ht, keyHash, url);
  1081.         if ((he = *hep) == 0) {
  1082.             URL_Struct *url_s = NET_CreateURLStruct(url, NET_DONT_RELOAD);
  1083.             if (url_s != NULL) {
  1084.                 url_s->SARCache = crawler->cache;
  1085.                 NET_RemoveURLFromCache(url_s);
  1086.                 crawler->error |= CRAWL_REMOVED_LINK;
  1087.                 NET_FreeURLStruct(url_s);
  1088.             } else {
  1089.                 crawler->error |= CRAWL_NO_MEMORY;
  1090.                 return(-1);
  1091.             }
  1092.         } else {
  1093.             /* there is an entry in the table so check the modified date */
  1094.             char *end = NULL;
  1095.             CRAWL_LinkInfoStruc *info = (CRAWL_LinkInfoStruc*)he->value;
  1096.             time_t oldDate = XP_STRTOUL(date, &end, 10);
  1097.             if (info->lastModifiedDate > oldDate) {
  1098.                 info->status = REPLACED_LINK;
  1099.             } else {
  1100.                 info->status = OLD_LINK; /* could either be old or a new one with no last modified date reported */
  1101.             }
  1102.         }
  1103.         *slash = old;
  1104.     }
  1105.     return(0);
  1106. }
  1107.  
  1108. /* returns -1 on error, 0 for no error */
  1109. static int 
  1110. crawl_processCacheInfoLine(CRAWL_Crawler crawler, char *line) {
  1111.     if (line != NULL) {
  1112.         switch (*line) {
  1113.         case 'L':
  1114.             return(crawl_processCacheInfoEntry(crawler, line, crawler->pagesParsed));
  1115.         case 'I':
  1116.             return(crawl_processCacheInfoEntry(crawler, line, crawler->imagesCached));
  1117.         case 'R':
  1118.             return(crawl_processCacheInfoEntry(crawler, line, crawler->resourcesCached));
  1119.         default:
  1120.             return(-1);
  1121.         }
  1122.     }
  1123.     return(-1);
  1124. }
  1125.  
  1126. #define CACHE_INFO_BUF_SIZE 10
  1127. /* Reads the existing cache info file and for each entry, does a lookup in the appropriate table
  1128.    of pages, images, or resources cached. If not found, removes the file from the cache.
  1129. */
  1130. static void 
  1131. crawl_removeDanglingLinksFromCache(CRAWL_Crawler crawler) {
  1132.     static char buf[CACHE_INFO_BUF_SIZE];
  1133.     char *line = NULL;
  1134.     char *eol;
  1135.     int32 n = 0, status;
  1136.     char *filename = crawl_makeCacheInfoFilename(crawler);
  1137.     if (filename != NULL) {
  1138.         PRFileDesc *fd;
  1139.         fd = PR_Open(filename,  PR_RDONLY, 0644);
  1140.         if (fd == NULL) return;
  1141.         while ((status = PR_Read(fd, buf, CACHE_INFO_BUF_SIZE)) > 0) {
  1142.             while (n < status) {
  1143.                 if ((eol = XP_STRCHR(buf + n, '\n')) == NULL) {
  1144.                     /* no end of line detected so add to line and continue */
  1145.                     if (line == NULL) line = (char *)PR_CALLOC(status+1);
  1146.                     else line = (char *)PR_REALLOC(line, XP_STRLEN(line) + status + 1);
  1147.                     if (line == NULL) {
  1148.                         PR_Close(fd);
  1149.                         return;
  1150.                     }
  1151.                     XP_STRNCAT(line, buf + n, status);
  1152.                     n += status;
  1153.                 } else {
  1154.                     /* end of line detected so copy line up to there */
  1155.                     int32 len = eol - (buf + n);
  1156.                     if (line == NULL) line = (char *)PR_CALLOC(len + 1);
  1157.                     else line = (char *)PR_REALLOC(line, XP_STRLEN(line) + len + 1);
  1158.                     if (line == NULL) {
  1159.                         PR_Close(fd);
  1160.                         return;
  1161.                     }
  1162.                     XP_STRNCAT(line, buf + n, len);
  1163.                     if (crawl_processCacheInfoLine(crawler, line) != 0) {
  1164.                         PR_Close(fd); /* abort on bad data */
  1165.                         return;
  1166.                     }
  1167.                     PR_Free(line);
  1168.                     line = NULL;
  1169.                     n += (len + 1);
  1170.                 }
  1171.             }
  1172.             n = 0;
  1173.         }
  1174.         PR_Close(fd);
  1175.     } else crawler->error |= CRAWL_NO_MEMORY;
  1176. }
  1177.  
  1178. static int
  1179. crawl_updateCrawlerErrors(PLHashEntry *he, int i, void *arg) {
  1180. #if defined(XP_MAC)
  1181. #pragma unused(i)
  1182. #endif    
  1183.     CRAWL_LinkInfoStruc *info = (CRAWL_LinkInfoStruc *)he->value;
  1184.     CRAWL_Crawler crawler = (CRAWL_Crawler)arg;
  1185.     switch (info->status) {
  1186.     case NEW_LINK:
  1187.         crawler->error |= CRAWL_NEW_LINK;
  1188.         break;
  1189.     case REPLACED_LINK:
  1190.         crawler->error |= CRAWL_REPLACED_LINK;
  1191.         break;
  1192.     case OLD_LINK:
  1193.     default:
  1194.         break;
  1195.     }
  1196.     return HT_ENUMERATE_NEXT;
  1197. }
  1198.  
  1199. /* called when we're done processing all the items */
  1200. static void 
  1201. crawl_crawlerFinish(CRAWL_Crawler crawler) {
  1202.     /* remove old cache items */
  1203.     if (crawler->manageCache && 
  1204.         (crawler->cache != NULL) && 
  1205.         ((crawler->error & CRAWL_NO_MEMORY) == 0) &&
  1206.         (crawler->pagesParsed->nentries > 0)) {
  1207.         crawl_removeDanglingLinksFromCache(crawler);
  1208.         PL_HashTableEnumerateEntries(crawler->pagesParsed, crawl_updateCrawlerErrors, (void*)crawler);
  1209.         PL_HashTableEnumerateEntries(crawler->imagesCached, crawl_updateCrawlerErrors, (void*)crawler);
  1210.         PL_HashTableEnumerateEntries(crawler->resourcesCached, crawl_updateCrawlerErrors, (void*)crawler);
  1211.         crawl_writeCacheList(crawler);
  1212.     }
  1213.     crawler->status = CRAWL_STOPPED;
  1214.     crawler->sizeSlop = SIZE_SLOP; /* reset, in case someone decides to use this crawler again (although docs say not to use again) */
  1215.     if (crawler->exitFn != NULL) (crawler->exitFn)(crawler, crawler->exitData);
  1216. }
  1217.  
  1218. /****************************************************************************************/
  1219. /* stream routines for images and resources                                                */
  1220. /****************************************************************************************/
  1221.  
  1222. /* prototypes */
  1223. PRIVATE int crawl_ResourceConvPut(NET_StreamClass *stream, char *s, int32 l);
  1224. PRIVATE int crawl_ResourceConvWriteReady(NET_StreamClass *stream);
  1225. PRIVATE void crawl_ResourceConvComplete(NET_StreamClass *stream);
  1226. PRIVATE void crawl_ResourceConvAbort(NET_StreamClass *stream, int status);
  1227.  
  1228. PRIVATE int
  1229. crawl_ResourceConvPut(NET_StreamClass *stream, char *s, int32 l)
  1230. {    
  1231. #if defined(XP_MAC)
  1232. #pragma unused(stream, s, l)
  1233. #endif    
  1234.     return(0);
  1235. }
  1236.  
  1237. PRIVATE int
  1238. crawl_ResourceConvWriteReady(NET_StreamClass *stream)
  1239. {    
  1240. #if defined(XP_MAC)
  1241. #pragma unused(stream)
  1242. #endif    
  1243.     return(MAX_WRITE_READY);
  1244. }
  1245.  
  1246. PRIVATE void
  1247. crawl_ResourceConvComplete(NET_StreamClass *stream)
  1248. {    
  1249. #if defined(XP_MAC)
  1250. #pragma unused(stream)
  1251. #endif
  1252.     /* do nothing */
  1253. }
  1254.  
  1255. PRIVATE void
  1256. crawl_ResourceConvAbort(NET_StreamClass *stream, int status)
  1257. {    
  1258. #if defined(XP_MAC)
  1259. #pragma unused(stream, status)
  1260. #endif
  1261.     /* do nothing */
  1262. }
  1263.  
  1264. /* 
  1265.     The reason for using a converter for images and resources is efficiency -
  1266.     to prevent netlib from getting a url if we can tell in advance that
  1267.     it will exceed the cache size. Otherwise netlib will get the url, determine
  1268.     that it has exceeded the cache size and immediately delete it. 
  1269.  
  1270.     I haven't done enough testing to determine if this is a big win or not.
  1271. */
  1272. PUBLIC NET_StreamClass *
  1273. CRAWL_CrawlerResourceConverter(int format_out,
  1274.                                 void *data_object,
  1275.                                 URL_Struct *URL_s,
  1276.                                 MWContext  *window_id)
  1277. {
  1278. #if defined(XP_MAC)
  1279. #pragma unused(format_out)
  1280. #endif
  1281.     NET_StreamClass *stream = NULL;
  1282.  
  1283.     TRACEMSG(("Setting up display stream. Have URL: %s\n", URL_s->address));
  1284.  
  1285.     XP_TRACE(("CRAWL_CrawlerResourceConverter: %d %s", URL_s->server_status, URL_s->address));
  1286.  
  1287.     if (URL_s->SARCache != NULL) {
  1288.         /* if the content length would exceed the cache limit, don't convert this */
  1289.         if (((uint32)URL_s->content_length >= (URL_s->SARCache->MaxSize - URL_s->SARCache->DiskCacheSize)) &&
  1290.             ((uint32)URL_s->content_length < BOGUS_CONTENT_LENGTH)) {
  1291.                 XP_TRACE(("not converting %s", URL_s->address));
  1292.                 return(NULL);
  1293.         }
  1294.     }
  1295.  
  1296.     stream = XP_NEW(NET_StreamClass);
  1297.     if(stream == NULL)
  1298.         return(NULL);
  1299.  
  1300.     stream->name           = "Crawler Resource Converter";
  1301.     stream->complete       = (MKStreamCompleteFunc) crawl_ResourceConvComplete;
  1302.     stream->abort          = (MKStreamAbortFunc) crawl_ResourceConvAbort;
  1303.     stream->put_block      = (MKStreamWriteFunc) crawl_ResourceConvPut;
  1304.     stream->is_write_ready = (MKStreamWriteReadyFunc) crawl_ResourceConvWriteReady;
  1305.     stream->data_object    = data_object;  /* document info object */
  1306.     stream->window_id      = window_id;
  1307.     return(stream);
  1308. }
  1309.  
  1310. #ifdef CRAWLERTEST
  1311. static void myPostProcessFn(CRAWL_Crawler crawler, URL_Struct *url_s, PRBool isCached, void *data) {
  1312.     if (isCached) XP_TRACE(("%s was cached, content length=%d", url_s->address, url_s->content_length));
  1313.     else XP_TRACE(("%s wasn't cached, content length=%d", url_s->address, url_s->content_length));
  1314.     XP_TRACE(("cache size=%d, size slop=%d", crawler->cache->DiskCacheSize, crawler->sizeSlop));
  1315. }
  1316.  
  1317. static void myExitFn(CRAWL_Crawler crawler, void *data) {
  1318.     char *msg;
  1319.     PRIntervalTime startTime = (PRIntervalTime)data;
  1320.     msg = PR_smprintf("Crawler finished in %lu milliseconds - cache full=%d", 
  1321.                 PR_IntervalToMilliseconds(PR_IntervalNow() - startTime),
  1322.                 (crawler->error & CRAWL_CACHE_FULL));
  1323.     FE_Alert(crawler->context, msg);
  1324.     PR_smprintf_free(msg);
  1325.     CRAWL_DestroyCrawler(crawler);
  1326. }
  1327.  
  1328. void testCrawler(char *name, char *inURL, uint8 depth, uint32 maxSize, PRBool stayInSite) {
  1329.     CRAWL_Crawler crawler;
  1330.     char *url = XP_STRDUP(inURL);
  1331. #ifdef XP_MAC
  1332.     MWContext *context = XP_FindSomeContext(); /* FE_GetNetHelpContext didn't work with netlib on Mac */
  1333. #else
  1334.     MWContext *context = FE_GetNetHelpContext();
  1335. #endif
  1336.     ExtCacheDBInfo *cache = PR_NEWZAP(ExtCacheDBInfo);
  1337.  
  1338.     cache->name = "test cache";
  1339.     cache->filename = name;
  1340.     cache->path = "\\"; /* ignored */
  1341.     cache->MaxSize = maxSize;
  1342.  
  1343.     cache = CACHE_GetCache(cache);
  1344.     crawler = CRAWL_MakeCrawler(context, 
  1345.                                 url, 
  1346.                                 depth, 
  1347.                                 stayInSite, 
  1348.                                 PR_TRUE, 
  1349.                                 cache,
  1350.                                 myPostProcessFn,
  1351.                                 NULL,
  1352.                                 myExitFn,
  1353.                                 (void*)PR_IntervalNow());
  1354.     CRAWL_StartCrawler(crawler, url);
  1355. }
  1356. #endif
  1357.  
  1358. #if 0
  1359. /****************************************************************************************/
  1360. /* test harness code fragment                                                            */
  1361. /****************************************************************************************/
  1362.  
  1363. /* 
  1364.     Here is a code fragment which may be helpful for testing inside of the client. 
  1365.     You can modify NET_GetURL so it will recognize about:crawler and invoke the crawler.
  1366.     Also modify net_output_about_url so it returns -1 for about:crawler.
  1367.     These functions are defined in ns/lib/libnet/mkgeturl.c
  1368.  
  1369.     The following parameters are available
  1370.         url - starting url to crawl from (defaults to http://w3.mcom.com/)
  1371.         depth - how many levels to crawl (defaults to 1)
  1372.         maxsize - maximum cache size in bytes (defaults to 200000)
  1373.         stayinsite - if 1, restricts crawling to the site of the initial url (defaults to 1),
  1374.             otherwise crawling is unrestricted.
  1375.         name - name of the cache (defaults to test.db)
  1376.  
  1377.     The parameters are separated by &.
  1378.  
  1379.     Example:
  1380.  
  1381.     about:crawler?url=http://home&depth=2&stayinsite=0
  1382.  
  1383. */
  1384.  
  1385. NET_GetURL (URL_Struct *URL_s,
  1386.         FO_Present_Types output_format,
  1387.         MWContext *window_id,
  1388.         Net_GetUrlExitFunc* exit_routine)
  1389. {
  1390.     ...
  1391.  
  1392.         case ABOUT_TYPE_URL:
  1393.             ...
  1394.  
  1395.           if (URL_s && XP_STRNCMP(URL_s->address, "about:crawler?", 14) == 0)
  1396.           {
  1397.               uint8 depth = 1;
  1398.               uint32 maxsize = 200000;
  1399.               PRBool stayInSite = PR_TRUE;
  1400.               char temp;
  1401.               char * end;
  1402.               char * item;
  1403.               char * url = "http://w3.mcom.com/";
  1404.               char * name = "test.db";
  1405.               item = XP_STRSTR(URL_s->address, "url=");
  1406.               if (item != NULL) {
  1407.                     item += 4;
  1408.                     end = XP_STRCHR(item, '&');
  1409.                     if (end != NULL) {
  1410.                         temp = *end;
  1411.                         *end = '\0';
  1412.                         url = XP_STRDUP(item);
  1413.                         *end = temp;
  1414.                     } else url = XP_STRDUP(item);
  1415.               }
  1416.               item = XP_STRSTR(URL_s->address, "name=");
  1417.               if (item != NULL) {
  1418.                     item += 5;
  1419.                     end = XP_STRCHR(item, '&');
  1420.                     if (end != NULL) {
  1421.                         temp = *end;
  1422.                         *end = '\0';
  1423.                         name = XP_STRDUP(item);
  1424.                         *end = temp;
  1425.                     } else name = XP_STRDUP(item);
  1426.               }
  1427.               item = XP_STRSTR(URL_s->address, "depth=");
  1428.               if (item != NULL) {
  1429.                     item += 6;
  1430.                     depth = (uint8)XP_STRTOUL(item, &end, 10);
  1431.               }
  1432.               item = XP_STRSTR(URL_s->address, "maxsize=");
  1433.               if (item != NULL) {
  1434.                   item += 8;
  1435.                   maxsize = XP_STRTOUL(item, &end, 10);
  1436.               }
  1437.               item = XP_STRSTR(URL_s->address, "stayinsite=");
  1438.               if (item != NULL) {
  1439.                   item += 8;
  1440.                   if (XP_STRTOUL(item, &end, 10) == 0) stayInSite = PR_FALSE;
  1441.                   else stayInSite = PR_TRUE;
  1442.               }            
  1443.                 testCrawler(name, url, (uint8)depth, (uint32)maxsize, stayInSite);
  1444.  
  1445.                 ...
  1446.  
  1447.             }
  1448.     ...
  1449. }
  1450.  
  1451. PRIVATE int net_output_about_url(ActiveEntry * cur_entry)
  1452. {
  1453.     ...
  1454.     else if (!strncasecomp(which, "crawler", 7)) 
  1455.     {
  1456.         return (-1);
  1457.     }
  1458. }
  1459. #endif
  1460.