home *** CD-ROM | disk | FTP | other *** search
/ Tools / WinSN5.0Ver.iso / NETSCAP.50 / WIN1998.ZIP / ns / lib / libnet / robotxt.c < prev    next >
Encoding:
C/C++ Source or Header  |  1998-04-08  |  17.4 KB  |  552 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. /*** robotxt.c ****************************************************/
  19. /*   description:        implementation of robots.txt parser       */
  20.   
  21.  
  22.  /********************************************************************
  23.  
  24.   $Revision: 3.1 $
  25.   $Date: 1998/03/28 03:31:59 $
  26.  
  27.  *********************************************************************/
  28.  
  29. #include "xp.h"
  30. #include "xp_str.h"
  31. #include "ntypes.h" /* for MWContext */
  32. #include "net.h"
  33. #include "robotxt.h"
  34. #include "prmem.h"
  35. #include "prthread.h"
  36. #include "prinrval.h"
  37. #include "prio.h" /* for testing */
  38.  
  39. #define USER_AGENT "User-agent"
  40. #define DISALLOW "Disallow"
  41. #define ALLOW "Allow"
  42. #define ASTERISK "*"
  43. #define MOZILLA "mozilla"
  44.  
  45. typedef uint8 CRAWL_RobotControlAvailability;
  46.  
  47. #define ROBOT_CONTROL_AVAILABLE            ((CRAWL_RobotControlAvailability)0x00)
  48. #define ROBOT_CONTROL_NOT_AVAILABLE        ((CRAWL_RobotControlAvailability)0x01)
  49. #define ROBOT_CONTROL_NOT_YET_QUERIED    ((CRAWL_RobotControlAvailability)0x02)
  50.  
  51. #define PARSE_STATE_ALLOW 1
  52. #define    PARSE_STATE_DISALLOW 2
  53. #define    PARSE_STATE_AGENT 3
  54.  
  55. #define PARSE_NO_ERR 0
  56. #define PARSE_ERR 1
  57. #define PARSE_NO_MEMORY 2
  58. #define MOZILLA_RECORD_READ 3 /* found the Mozilla record so we're done */
  59.  
  60. extern int crawl_appendString(char **str, uint16 *len, uint16 *size, char c);
  61.  
  62. typedef struct _CRAWL_RobotControlStruct {
  63.     /* char *host; */
  64.     char *siteURL;
  65.     CRAWL_RobotControlAvailability status;
  66.     char **line;
  67.     uint16 numLines;
  68.     uint16 sizeLines;
  69.     PRBool *allowed;
  70.     MWContext *context;
  71.     CRAWL_RobotControlStatusFunc completion_func;
  72.     void *owner_data;
  73.     PRBool freeData;
  74.     /* char *requested_url; */
  75. } CRAWL_RobotControlStruct;
  76.  
  77. typedef struct _CRAWL_RobotParseStruct {
  78.     uint8 state;
  79.     char *token;
  80.     uint16 lenToken;
  81.     uint16 sizeToken;
  82.     PRBool inComment;
  83.     PRBool isProcessing;
  84.     PRBool skipWhitespace;
  85.     PRBool mozillaSeen; /* true if we saw a mozilla user agent */
  86.     PRBool defaultSeen; /* true if we saw a default user agent */
  87.     PRBool foundRecord; /* true if we read a mozilla or default record */
  88. } CRAWL_RobotParseStruct;
  89.  
  90. typedef CRAWL_RobotParseStruct *CRAWL_RobotParse;
  91.  
  92. /* prototypes */
  93. static int crawl_unescape (char *str, char *reserved, int numReserved);
  94. PRBool crawl_startsWith (char *pattern, char *uuid);
  95. PRBool crawl_endsWith (char *pattern, char *uuid);
  96. void crawl_stringToLower(char *str);
  97. static void crawl_destroyLines(CRAWL_RobotControl control);
  98. static void crawl_addRobotControlDirective(CRAWL_RobotControl control, char *token, PRBool isAllowed);
  99. static int crawl_parseRobotControlInfo(CRAWL_RobotControl control, CRAWL_RobotParse parse, char *str, uint32 len);
  100. static CRAWL_RobotControlStatus crawl_isRobotAllowed(CRAWL_RobotControl control, char *url);
  101.  
  102. /* this stuff is adapted from mkparse.c */
  103. #define HEX_ESCAPE '%'
  104. #define RESERVED_CHARS ";/:@=&"
  105. #define NUM_RESERVED 6
  106.  
  107. /* decode % escaped hex codes into character values
  108.  */
  109. #define UNHEX(C) \
  110.     ((C >= '0' && C <= '9') ? C - '0' : \
  111.      ((C >= 'A' && C <= 'F') ? C - 'A' + 10 : \
  112.      ((C >= 'a' && C <= 'f') ? C - 'a' + 10 : 0)))
  113.  
  114. /* unescapes a string, but leaves octets encoded if they match one of the supplied reserved characters.
  115.    this was adapted from NET_UnescapeCnt */
  116. static int
  117. crawl_unescape (char *str, char *reserved, int numReserved)
  118. {
  119.     int i;
  120.     register char *src = str;
  121.     register char *dst = str;
  122.  
  123.     while(*src)
  124.         if (*src != HEX_ESCAPE)
  125.           {
  126.             *dst++ = *src++;
  127.           }
  128.         else     
  129.           {
  130.             src++; /* walk over escape */
  131.             if (*src)
  132.               {
  133.                 *dst = UNHEX(*src) << 4;
  134.                 src++;
  135.               }
  136.             if (*src)
  137.               {
  138.                 *dst = (*dst + UNHEX(*src));
  139.                 src++;
  140.               }
  141.             /* check if it belongs to the reserved characters */
  142.             for (i = 0; i < numReserved; i++) {
  143.                 if (*dst == reserved[i]) {
  144.                     /* put it back */
  145.                     *dst++ = HEX_ESCAPE;
  146.                     *dst++ = *(src-2);
  147.                     *dst = *(src-1);
  148.                 }
  149.             }
  150.             dst++;
  151.           }
  152.  
  153.     *dst = 0;
  154.  
  155.     return (int)(dst - str);
  156. }
  157.  
  158. #define CHAR_CMP(x, y) ((x == y) || (XP_TO_LOWER(x) == XP_TO_LOWER(y)))
  159.  
  160. PRBool crawl_startsWith (char *pattern, char *uuid) {
  161.   short l1 = strlen(pattern);
  162.   short l2 = strlen(uuid);
  163.   short index;
  164.   
  165.   if (l2 < l1) return PR_FALSE;
  166.   
  167.   for (index = 0; index < l1; index++) {
  168.     if (!(CHAR_CMP(pattern[index], uuid[index]))) return PR_FALSE;
  169.   }
  170.     
  171.   return PR_TRUE;
  172. }
  173.  
  174. PRBool crawl_endsWith (char *pattern, char *uuid) {
  175.   short l1 = strlen(pattern);
  176.   short l2 = strlen(uuid);
  177.   short index;
  178.   
  179.   if (l2 < l1) return PR_FALSE;
  180.   
  181.   for (index = 0; index < l1; index++) {
  182.     if (!(CHAR_CMP(pattern[l1-index], uuid[l2-index]))) return PR_FALSE;
  183.   }
  184.   
  185.   return PR_TRUE;
  186. }
  187.  
  188. void crawl_stringToLower(char *str) {
  189.     register char *src = str;
  190.     register char *dst = str;
  191.     while(*src) {
  192.         *dst++ = tolower(*src++);
  193.     }
  194.     *dst = 0;
  195. }
  196.  
  197. PR_IMPLEMENT(CRAWL_RobotControl) CRAWL_MakeRobotControl(MWContext *context, char *siteURL) {
  198.     CRAWL_RobotControl control = PR_NEWZAP(CRAWL_RobotControlStruct);
  199.     if (control == NULL) return(NULL);
  200.     control->siteURL = XP_STRDUP(siteURL);
  201.     if (siteURL == NULL) return(NULL);
  202.     control->status = ROBOT_CONTROL_NOT_YET_QUERIED;
  203.     control->context = context;
  204.     return control;
  205. }
  206.  
  207. static void 
  208. crawl_destroyLines(CRAWL_RobotControl control) {
  209.     uint16 i;
  210.     for (i = 0; i < control->numLines; i++) {
  211.         PR_Free(control->line[i]);
  212.     }
  213.     if (control->line != NULL) PR_Free(control->line);
  214.     if (control->allowed != NULL) PR_Free(control->allowed);
  215.     control->allowed = NULL;
  216.     control->line = NULL;
  217.     control->numLines = control->sizeLines = 0;
  218. }
  219.  
  220. PR_IMPLEMENT(void) CRAWL_DestroyRobotControl(CRAWL_RobotControl control) {
  221.     if (control->siteURL != NULL) PR_Free(control->siteURL);
  222.     crawl_destroyLines(control);
  223.     PR_Free(control);
  224. }
  225.  
  226. static void 
  227. crawl_addRobotControlDirective(CRAWL_RobotControl control, char *token, PRBool isAllowed) {
  228.     /* convert token to lower case and unescape it */
  229.     crawl_stringToLower(token);
  230.     crawl_unescape(token, RESERVED_CHARS, NUM_RESERVED);
  231.     if (control->numLines == control->sizeLines) {
  232.         char **newLines;
  233.         char **old;
  234.         PRBool *newAllowed;
  235.         PRBool *oldAllowed;
  236.         /* copy the paths array */
  237.         newLines = (char**)PR_MALLOC(sizeof(char**) * (control->sizeLines + 10));
  238.         if (newLines == NULL) return;
  239.         old = control->line;
  240.         memcpy((char*)newLines, (char*)control->line, (sizeof(char**) * control->numLines));
  241.         control->line = newLines;
  242.         if (old != NULL) PR_Free(old);
  243.         /* copy the boolean array */
  244.         newAllowed = (PRBool*)PR_MALLOC(sizeof(PRBool) * (control->sizeLines + 10));
  245.         if (newAllowed == NULL) return;
  246.         oldAllowed = control->allowed;
  247.         memcpy((char*)newAllowed, (char*)control->allowed, (sizeof(PRBool) * control->numLines));
  248.         control->allowed = newAllowed;
  249.         if (oldAllowed != NULL) PR_Free(oldAllowed);
  250.         control->sizeLines += 10;
  251.     }
  252.     *(control->line + control->numLines) = token;
  253.     *(control->allowed + control->numLines) = isAllowed;
  254.     control->numLines++;
  255. }
  256.  
  257. static int 
  258. crawl_parseRobotControlInfo(CRAWL_RobotControl control, CRAWL_RobotParse parse, char *str, uint32 len) { 
  259.     uint32 n = 0; /* where we are in the buffer */
  260.     char c;
  261.     while (n < len) {
  262.         c = *(str + n);
  263.         if (parse->skipWhitespace) {
  264.             if ((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t')) {
  265.                 n++;
  266.             } else parse->skipWhitespace = PR_FALSE;
  267.         } else {
  268.             if (c == '#') {
  269.                 parse->inComment = PR_TRUE;
  270.                 n++;
  271.             } else if (parse->inComment) {
  272.                 if ((c == '\n') || (c == '\r')) {
  273.                     parse->inComment = PR_FALSE;
  274.                     parse->skipWhitespace = PR_TRUE;
  275.                     n++;
  276.                 } else n++; /* skip all other characters */
  277.             } else if (c == ':') { /* directive */
  278.                 PRBool mozillaRecordRead = PR_FALSE;
  279.                 if (crawl_appendString(&parse->token, &parse->lenToken, &parse->sizeToken, '\0') != 0) /* null terminate */
  280.                     return PARSE_NO_MEMORY;
  281.                 if (XP_STRCASECMP(parse->token, USER_AGENT) == 0) {
  282.                     if ((parse->state == PARSE_STATE_DISALLOW) || (parse->state == PARSE_STATE_ALLOW)) {
  283.                         /* already read a disallow or allow directive so the previous record is done */
  284.                         if (parse->isProcessing) {
  285.                             if (parse->mozillaSeen) mozillaRecordRead = PR_TRUE;
  286.                             if (parse->mozillaSeen || parse->defaultSeen) parse->foundRecord = PR_TRUE;
  287.                             parse->isProcessing = PR_FALSE;
  288.                         }
  289.                     }
  290.                     parse->state = PARSE_STATE_AGENT;
  291.                 } else if (XP_STRCASECMP(parse->token, DISALLOW) == 0) {
  292.                     parse->state = PARSE_STATE_DISALLOW;
  293.                 } else if (XP_STRCASECMP(parse->token, ALLOW) == 0)
  294.                     parse->state = PARSE_STATE_ALLOW;
  295.                 /* else it is an unknown directive */
  296.                 PR_Free(parse->token);
  297.                 parse->token = NULL;
  298.                 parse->lenToken = parse->sizeToken = 0;
  299.                 parse->skipWhitespace = PR_TRUE;
  300.                 n++;
  301.                 if (mozillaRecordRead) return MOZILLA_RECORD_READ; /* read the mozilla record so we're outta here */
  302.             } else if ((c == '\n') || (c == '\r')) {
  303.                 if (crawl_appendString(&parse->token, &parse->lenToken, &parse->sizeToken, '\0') != 0) /* null terminate */
  304.                     return PARSE_NO_MEMORY;
  305.                 switch (parse->state) {
  306.                 case PARSE_STATE_AGENT:
  307.                     if (XP_STRCASESTR(parse->token, MOZILLA) != NULL) {
  308.                         parse->mozillaSeen = PR_TRUE;
  309.                         crawl_destroyLines(control); /* destroy previous default data */
  310.                         parse->isProcessing = PR_TRUE; /* start processing */
  311.                     } else if ((XP_STRCMP(parse->token, ASTERISK) == 0) && (!parse->mozillaSeen)) {
  312.                         parse->defaultSeen = PR_TRUE;
  313.                         parse->isProcessing = PR_TRUE; /* start processing */
  314.                     }
  315.                     PR_Free(parse->token);
  316.                     break;
  317.                 case PARSE_STATE_DISALLOW:
  318.                     /* if processing, add to disallowed */
  319.                     if (parse->isProcessing) {
  320.                         crawl_addRobotControlDirective(control, parse->token, PR_FALSE);
  321.                     }
  322.                     break;
  323.                 case PARSE_STATE_ALLOW:
  324.                     /* if processing, add to allowed */
  325.                     if (parse->isProcessing) {
  326.                         crawl_addRobotControlDirective(control, parse->token, PR_TRUE);
  327.                     }
  328.                     break;
  329.                 default:
  330.                     PR_Free(parse->token);
  331.                     break;
  332.                 }
  333.                 parse->token = NULL;
  334.                 parse->lenToken = parse->sizeToken = 0;
  335.                 parse->skipWhitespace = PR_TRUE;
  336.             } else {
  337.                 if (crawl_appendString(&parse->token, &parse->lenToken, &parse->sizeToken, c) != 0)
  338.                     return PARSE_NO_MEMORY;
  339.                 n++;
  340.             }
  341.         }
  342.     }
  343.     return PARSE_NO_ERR;
  344. }
  345.  
  346. static CRAWL_RobotControlStatus 
  347. crawl_isRobotAllowed(CRAWL_RobotControl control, char *url) {
  348.     /* extract file component (after host) from url and decode it */
  349.     uint16 i;
  350.     char *file = NET_ParseURL(url, GET_PATH_PART);
  351.     if (file == NULL) return CRAWL_ROBOT_ALLOWED;
  352.     crawl_unescape(file, RESERVED_CHARS, NUM_RESERVED);
  353.     
  354.     for (i = 0; i < control->numLines; i++) {
  355.         if (crawl_startsWith(control->line[i], file))
  356.             return (control->allowed[i] ? CRAWL_ROBOT_ALLOWED : CRAWL_ROBOT_DISALLOWED);
  357.     }
  358.     PR_Free(file);
  359.     return CRAWL_ROBOT_ALLOWED; /* no matches */
  360. }
  361.  
  362. static void
  363. crawl_get_robots_txt_exit(URL_Struct *URL_s, int status, MWContext *window_id)
  364. {
  365. #if defined(XP_MAC)
  366. #pragma unused(window_id)
  367. #endif    
  368.     CRAWL_RobotControl control = (CRAWL_RobotControl)URL_s->owner_data;
  369.     if (status < 0) {
  370.         control->status = ROBOT_CONTROL_NOT_AVAILABLE;
  371.         if (control->owner_data != NULL) {
  372.             (control->completion_func)(control->owner_data);
  373.             if (control->freeData) PR_DELETE(control->owner_data);
  374.         }
  375.     }
  376.     if(status != MK_CHANGING_CONTEXT)
  377.         NET_FreeURLStruct(URL_s);
  378. }
  379.  
  380. /* issues a request for the robots.txt file.
  381.    returns PR_TRUE if the request was issued succesfully, PR_FALSE if not.
  382. */
  383. PR_IMPLEMENT(PRBool) CRAWL_ReadRobotControlFile(CRAWL_RobotControl control, CRAWL_RobotControlStatusFunc func, void *data, PRBool freeData) {
  384.     /* create new cache request for site + /robots.txt" */
  385.     char *url = NET_MakeAbsoluteURL(control->siteURL, "/robots.txt");
  386.     if (url != NULL) {
  387.         URL_Struct *url_s = NET_CreateURLStruct(url, NET_NORMAL_RELOAD);
  388.         if (url_s != NULL) {
  389.             control->completion_func = func;
  390.             control->owner_data = data;
  391.             control->freeData = freeData;
  392.             url_s->owner_data = control;
  393.             NET_GetURL(url_s, FO_CACHE_AND_ROBOTS_TXT, control->context, crawl_get_robots_txt_exit);
  394.             /* func(data); */
  395.             return PR_TRUE;
  396.         }
  397.     } 
  398.     control->status = ROBOT_CONTROL_NOT_AVAILABLE;
  399.     return PR_FALSE;
  400. }
  401.  
  402. PR_IMPLEMENT(CRAWL_RobotControlStatus) CRAWL_GetRobotControl(CRAWL_RobotControl control, char *url) {
  403.     /* return ROBOT_ALLOWED; */
  404.     switch (control->status) {
  405.     case ROBOT_CONTROL_NOT_YET_QUERIED:
  406.         return CRAWL_ROBOTS_TXT_NOT_QUERIED;
  407.     case ROBOT_CONTROL_AVAILABLE:
  408.         return crawl_isRobotAllowed(control, url);
  409.         break;
  410.     case ROBOT_CONTROL_NOT_AVAILABLE:
  411.         return CRAWL_ROBOT_ALLOWED; /* no robots.txt file found so assume we can crawl */
  412.         break;
  413.     default:
  414.         return CRAWL_ROBOT_ALLOWED;
  415.         break;
  416.     }
  417. }
  418.  
  419. /* content type conversion */
  420. typedef struct {
  421.     CRAWL_RobotParse parse_obj;
  422.     CRAWL_RobotControl control;
  423. } crawl_robots_txt_stream;
  424.  
  425. PRIVATE int
  426. crawl_RobotsTxtConvPut(NET_StreamClass *stream, char *s, int32 l)
  427. {
  428.     crawl_robots_txt_stream *obj=stream->data_object;
  429.     int status = crawl_parseRobotControlInfo(obj->control, obj->parse_obj, s, l);        
  430.  
  431.     if ((status == MOZILLA_RECORD_READ) || 
  432.         (status == PARSE_NO_MEMORY)) {
  433.         return (MK_UNABLE_TO_CONVERT); /* abort since we read the mozilla record, no need to read any others */
  434.     }
  435.  
  436.     return(status);
  437. }
  438.  
  439. PRIVATE int
  440. crawl_RobotsTxtConvWriteReady(NET_StreamClass *stream)
  441. {    
  442. #if defined(XP_MAC)
  443. #pragma unused(stream)
  444. #endif
  445.     return(MAX_WRITE_READY);
  446. }
  447.  
  448. PRIVATE void
  449. crawl_RobotsTxtConvComplete(NET_StreamClass *stream)
  450. {
  451.     crwal_robots_txt_stream*obj=stream->data_object;    
  452.     if (obj->parse_obj->foundRecord) obj->control->status = ROBOT_CONTROL_AVAILABLE; 
  453.     if (obj->control->owner_data != NULL) {
  454.         (obj->control->completion_func)(obj->control->owner_data);
  455.         if (obj->control->freeData) PR_DELETE(obj->control->owner_data);
  456.     }
  457.     PR_Free(obj->parse_obj);
  458. }
  459.  
  460. PRIVATE void
  461. crawl_RobotsTxtConvAbort(NET_StreamClass *stream, int status)
  462. {
  463.     crawl_robots_txt_stream *obj=stream->data_object;    
  464.     if(status == MK_UNABLE_TO_CONVERT) { /* special case, we read the mozilla record and exited early */
  465.         obj->control->status = ROBOT_CONTROL_AVAILABLE;
  466.     } else obj->control->status = ROBOT_CONTROL_NOT_AVAILABLE; 
  467.     if (obj->control->owner_data != NULL) {
  468.         (obj->control->completion_func)(obj->control->owner_data);
  469.         if (obj->control->freeData) PR_DELETE(obj->control->owner_data);
  470.     }
  471.     PR_Free(obj->parse_obj);
  472. }
  473.  
  474. PUBLIC NET_StreamClass *
  475. CRAWL_RobotsTxtConverter(int format_out,
  476.                         void *data_object,
  477.                         URL_Struct *URL_s,
  478.                         MWContext  *window_id)
  479. {
  480. #if defined(XP_MAC)
  481. #pragma unused(format_out, data_object)
  482. #endif
  483.     crawl_robots_txt_stream *obj;
  484.     NET_StreamClass *stream;
  485.     CRAWL_RobotControl control = (CRAWL_RobotControl)URL_s->owner_data;
  486.  
  487.     TRACEMSG(("Setting up display stream. Have URL: %s\n", URL_s->address));
  488.  
  489.     if (URL_s->server_status < 400) {
  490.         stream = XP_NEW(NET_StreamClass);
  491.         if(stream == NULL) {
  492.             control->status = ROBOT_CONTROL_NOT_AVAILABLE;
  493.             return(NULL);
  494.         }
  495.  
  496.         obj = XP_NEW(crawl_robots_txt_stream);
  497.         if (obj == NULL)
  498.           {
  499.             XP_FREE(stream);
  500.             control->status = ROBOT_CONTROL_NOT_AVAILABLE;
  501.             return(NULL);
  502.           }
  503.         obj->parse_obj = PR_NEWZAP(CRAWL_RobotParseStruct);
  504.         if (obj->parse_obj == NULL) return(NULL);
  505.         obj->control = URL_s->owner_data;
  506.  
  507.         stream->name           = "robots.txt Converter";
  508.         stream->complete       = (MKStreamCompleteFunc) crawl_RobotsTxtConvComplete;
  509.         stream->abort          = (MKStreamAbortFunc) crawl_RobotsTxtConvAbort;
  510.         stream->put_block      = (MKStreamWriteFunc) crawl_RobotsTxtConvPut;
  511.         stream->is_write_ready = (MKStreamWriteReadyFunc) crawl_RobotsTxtConvWriteReady;
  512.         stream->data_object    = obj;  /* document info object */
  513.         stream->window_id      = window_id;
  514.  
  515.         return(stream);
  516.     } else {
  517.         control->status = ROBOT_CONTROL_NOT_AVAILABLE;
  518.         if (control->owner_data != NULL) {
  519.             control->completion_func(control->owner_data);
  520.             if (control->freeData) PR_DELETE(control->owner_data);
  521.         }
  522.         return (NULL);
  523.     }
  524. }
  525.  
  526. #if DEBUG_TEST_ROBOT
  527. void testRobotControlParser(char *url) {
  528.     /* this will be done through libnet but for now use PR_OpenFile */
  529.     PRFileDesc *fp;
  530.     int32 len;
  531.     char *path;
  532.     static char buf[512]; /* xxx alloc */
  533.     CRAWL_RobotParse parse;
  534.     CRAWL_RobotControl control = MakeRobotControl("foo");
  535.     /* XXX need to unescape URL */
  536.     path=&(url[8]);
  537.     fp = PR_Open(path,  PR_RDONLY, 0644);
  538.     if(fp == NULL)
  539.     {
  540.         return;
  541.     }
  542.     parse = PR_NEWZAP(CRAWL_RobotParseStruct);
  543.     while((len=PR_Read(fp, buf, 512))>0) {
  544.         if (crawl_parseRobotControlInfo(control, parse, buf, len) == MOZILLA_RECORD_READ) break;
  545.     }
  546.     PR_Close(fp);
  547.     PR_Free(parse);
  548.     DestroyRobotControl(control);
  549.     return;
  550. }
  551. #endif
  552.