Annotation of libwww/Library/src/HTNet.c, revision 2.30

2.23      frystyk     1: /*                                                                  HTNet.c
                      2: **     ASYNCRONOUS SOCKET MANAGEMENT
2.1       frystyk     3: **
2.10      frystyk     4: **     (c) COPYRIGHT MIT 1995.
2.4       frystyk     5: **     Please first read the full copyright statement in the file COPYRIGH.
                      6: **
                      7: **     This is the implementation of the internal library multithreading
2.1       frystyk     8: **     functions. This includes an interrupt handler and a event loop.
                      9: **     
                     10: ** History:
2.14      frystyk    11: **     12 June 94      Written by Henrik Frystyk, frystyk@w3.org
2.17      frystyk    12: **      31 May  95      Charlie Brooks cbrooks@osf.org
                     13: **
2.1       frystyk    14: */
                     15: 
2.9       frystyk    16: /* Implemention dependent include files */
                     17: #include "tcp.h"
                     18: 
2.1       frystyk    19: /* Library include files */
                     20: #include "HTUtils.h"
2.16      frystyk    21: #include "HTProt.h"
2.1       frystyk    22: #include "HTError.h"
2.25      frystyk    23: #include "HTAlert.h"
2.23      frystyk    24: #include "HTReqMan.h"
2.17      frystyk    25: #include "HTEvntrg.h"
2.23      frystyk    26: #include "HTStream.h"
2.24      frystyk    27: #include "HTNetMan.h"                                   /* Implemented here */
2.1       frystyk    28: 
2.9       frystyk    29: #ifdef WIN32
                     30: #include <io.h>
                     31: #endif
                     32: 
2.23      frystyk    33: #ifndef HT_MAX_SOCKETS
                     34: #define HT_MAX_SOCKETS 6
                     35: #endif
                     36: 
                     37: typedef struct _CBFInfo {
2.30    ! frystyk    38:     HTNetCallback *    cbf;
2.23      frystyk    39:     int                status;      /* Status associated with this callback */
                     40: } CBFInfo;
                     41: 
                     42: struct _HTStream {
                     43:     CONST HTStreamClass *      isa;
                     44:     /* ... */
                     45: };
                     46: 
                     47: PRIVATE int    HTMaxActive = HT_MAX_SOCKETS;         /* Max active requests */
2.24      frystyk    48: PRIVATE HTList *HTNetCBF = NULL;             /* List of call back functions */
                     49: 
2.23      frystyk    50: PRIVATE HTList *HTNetActive = NULL;               /* List of active requests */
                     51: PRIVATE HTList *HTNetPending = NULL;            /* List of pending requests */
2.24      frystyk    52: PRIVATE HTList *HTNetPersistent = NULL;           /* List of persistent connections */
2.1       frystyk    53: 
                     54: /* ------------------------------------------------------------------------- */
                     55: 
2.23      frystyk    56: /*
                     57: **     Set the max number of simultanous sockets. Default is HT_MAX_SOCKETS
2.1       frystyk    58: */
2.23      frystyk    59: PUBLIC BOOL HTNet_setMaxSocket (int newmax)
                     60: {
                     61:     if (newmax > 0) {
                     62:        HTMaxActive = newmax;
                     63:        return YES;
                     64:     }
                     65:     return NO;
                     66: }
                     67: 
                     68: PUBLIC int HTNet_maxSocket (void)
2.1       frystyk    69: {
2.23      frystyk    70:     return HTMaxActive;
                     71: }
                     72: 
                     73: /* ------------------------------------------------------------------------- */
                     74: /*                       Termination Call Back Functions                    */
                     75: /* ------------------------------------------------------------------------- */
                     76: 
                     77: /*     HTNet_Register
                     78: **     --------------
                     79: **     Register a call back function that is to be called on every
                     80: **     termination of a request. Several call back functions can be registered
                     81: **     in which case all of them are called in the order of which they
                     82: **     were registered.
                     83: **
                     84: **     The status signifies which call back function to call depending of the 
                     85: **     result of the request. This can be
                     86: **
                     87: **             HT_ERROR        An error occured
                     88: **             HT_LOADED       The document was loaded
                     89: **             HT_NO_DATA      OK, but no data
                     90: **             HT_RETRY        Retry request after at a later time
                     91: **             HT_ALL          All of above
                     92: */
2.30    ! frystyk    93: PUBLIC BOOL HTNet_register (HTNetCallback *cbf, int status)
2.23      frystyk    94: {
                     95:     if (THD_TRACE) 
2.30    ! frystyk    96:        fprintf(TDEST, "Net register HTNetCallback %p\n", (void *) cbf);
2.23      frystyk    97:     if (cbf) {
                     98:        CBFInfo *cbfinfo = (CBFInfo *) calloc(1, sizeof(CBFInfo));
                     99:        if (!cbfinfo) outofmem(__FILE__, "HTNet_register");
                    100:        cbfinfo->cbf = cbf;
                    101:        cbfinfo->status = status;
                    102:        if (!HTNetCBF) HTNetCBF = HTList_new();
                    103:        return HTList_addObject(HTNetCBF, (void *) cbfinfo);
                    104:     }
                    105:     return NO;
                    106: }
                    107: 
                    108: /*     HTNet_unregister
                    109: **     --------------
                    110: **     Unregister a call back function that is to be called on every
                    111: **     termination of a request.
                    112: */
2.30    ! frystyk   113: PUBLIC BOOL HTNet_unregister (HTNetCallback *cbf)
2.23      frystyk   114: {
                    115:     if (THD_TRACE) 
2.30    ! frystyk   116:        fprintf(TDEST, "Net unreg.. HTNetCallback %p\n", (void *) cbf);
2.23      frystyk   117:     if (HTNetCBF && cbf) {
                    118:        HTList *cur = HTNetCBF;
                    119:        CBFInfo *pres;
                    120:        while ((pres = (CBFInfo *) HTList_nextObject(cur))) {
                    121:            if (pres->cbf == cbf) {
                    122:                HTList_removeObject(HTNetCBF, (void *) pres);
                    123:                free(pres);
                    124:                return YES;
                    125:            }
                    126:        }
                    127:     }
                    128:     return NO;
                    129: }
2.1       frystyk   130: 
2.23      frystyk   131: /*     HTNet_unregisterAll
                    132: **     -------------------
                    133: **     Unregisters all call back functions
                    134: */
                    135: PUBLIC BOOL HTNet_unregisterAll (void)
                    136: {
                    137:     if (THD_TRACE) 
                    138:        fprintf(TDEST, "Net unreg.. All callback functions\n");
                    139:     if (HTNetCBF) {
                    140:        HTList *cur = HTNetCBF;
                    141:        CBFInfo *pres;
                    142:        while ((pres = (CBFInfo *) HTList_nextObject(cur))) {
                    143:            HTList_removeObject(HTNetCBF, (void *) pres);
                    144:            free(pres);
                    145:        }
                    146:        HTList_delete(HTNetCBF);
                    147:        HTNetCBF = NULL;
                    148:        return YES;
                    149:     }
                    150:     return NO;
                    151: }
                    152: 
                    153: /*     HTNet_callback
                    154: **     --------------
                    155: **     Call all the call back functions registered in the list IF not the 
                    156: **     status is HT_IGNORE.
                    157: **     The callback functions are called in the order of which they
                    158: **     were registered. At the moment an application callback function is
                    159: **     called, it can free the request object - it is no longer used by the
                    160: **     Library.
                    161: **     Returns YES if OK, else NO.
                    162: */
                    163: PUBLIC BOOL HTNet_callback (HTRequest * request, int status)
                    164: {
                    165:     if (HTNetCBF && request && status != HT_IGNORE) {  
                    166:        int cnt = HTList_count(HTNetCBF);
                    167:        while (--cnt >= 0) {
2.26      frystyk   168:            CBFInfo *pres = (CBFInfo *) HTList_objectAt(HTNetCBF, cnt);
2.23      frystyk   169:            if (pres && (pres->status == status || pres->status == HT_ALL)) {
                    170:                if (THD_TRACE)
                    171:                    fprintf(TDEST, "Net callback %p (request=%p, status=%d)\n",
                    172:                            (void *) pres->cbf, request, status);
                    173:                (*(pres->cbf))(request, status);
                    174:            }
                    175:        }
                    176:        return YES;
2.1       frystyk   177:     }
2.23      frystyk   178:     return NO;
2.1       frystyk   179: }
                    180: 
2.23      frystyk   181: /* ------------------------------------------------------------------------- */
                    182: /*                             Request Queue                                */
                    183: /* ------------------------------------------------------------------------- */
2.1       frystyk   184: 
2.23      frystyk   185: /*     HTNet_activeQueue
                    186: **     -----------------
                    187: **     Returns the list of active requests that are currently having an open
                    188: **     connection.
                    189: **     Returns list of HTNet objects or NULL if error
2.1       frystyk   190: */
2.23      frystyk   191: PUBLIC HTList *HTNet_activeQueue (void)
                    192: {
                    193:     return HTNetActive;
                    194: }
2.17      frystyk   195: 
2.28      frystyk   196: /*     HTNet_activeCount
                    197: **     ----------------
                    198: **     Returns the number of active requests
                    199: */
2.29      frystyk   200: PUBLIC BOOL HTNet_idle (void)
2.28      frystyk   201: {
2.29      frystyk   202:     return HTList_isEmpty(HTNetActive);
2.28      frystyk   203: }
                    204: 
2.23      frystyk   205: /*     HTNet_pendingQueue
                    206: **     ------------------
                    207: **     Returns the list of pending requests that are waiting to become active
                    208: **     Returns list of HTNet objects or NULL if error
                    209: */
                    210: PUBLIC HTList *HTNet_pendingQueue (void)
2.1       frystyk   211: {
2.23      frystyk   212:     return HTNetPending;
2.1       frystyk   213: }
                    214: 
2.23      frystyk   215: /* ------------------------------------------------------------------------- */
                    216: /*                       Creation and deletion methods                      */
                    217: /* ------------------------------------------------------------------------- */
                    218: 
2.27      frystyk   219: /*     HTNet_duplicate
                    220: **     ---------------
                    221: **     Creates a new HTNet object as a duplicate of the same request.
                    222: **     Returns YES if OK, else NO
                    223: **     BUG: We do not check if we have a socket free!
                    224: */
                    225: PUBLIC BOOL HTNet_dup (HTNet *src, HTNet **dest)
                    226: {
                    227:     *dest = NULL;
                    228:     if (!src) return NO;
                    229:     if ((*dest = (HTNet *) calloc(1, sizeof(HTNet))) == NULL)
                    230:        outofmem(__FILE__, "HTNet_dup");
                    231:     memcpy(*dest, src, sizeof(HTNet));
                    232:     return YES;
                    233: }
                    234: 
2.30    ! frystyk   235: /*     HTNet_priority
        !           236: **     --------------
        !           237: **     Get the current priority of the Net object
        !           238: */
        !           239: PUBLIC HTPriority HTNet_priority (HTNet * net)
        !           240: {
        !           241:     return (net ? net->priority : -1);
        !           242: }
        !           243: 
        !           244: /*     HTNet_setPriority
        !           245: **     -----------------
        !           246: **     Set the current priority of the Net object
        !           247: **     This will change the priority next time the thread is blocked
        !           248: */
        !           249: PUBLIC BOOL HTNet_setPriority (HTNet * net, HTPriority priority)
        !           250: {
        !           251:     if (net) {
        !           252:        net->priority = priority;
        !           253:        return YES;
        !           254:     }
        !           255:     return NO;
        !           256: }
        !           257: 
        !           258: /*     HTNet_new
        !           259: **     ---------
2.23      frystyk   260: **     Create a new HTNet object as a new request to be handled. If we have
                    261: **     more than HTMaxActive connections already then put this into the
                    262: **     pending queue, else start the request by calling the call back
                    263: **     function registered with this access method. 
                    264: **     Returns YES if OK, else NO
                    265: */
2.30    ! frystyk   266: PUBLIC BOOL HTNet_new (HTRequest * request)
2.23      frystyk   267: {
                    268:     HTNet *me;
                    269:     HTProtocol *prot;
2.27      frystyk   270:     if (!request) return NO;
2.23      frystyk   271:     if (!HTNetActive) HTNetActive = HTList_new();
2.26      frystyk   272:     prot = (HTProtocol *) HTAnchor_protocol(request->anchor);
2.23      frystyk   273: 
                    274:     /* Create new net object and bind it to the request object */
                    275:     if ((me = (HTNet *) calloc(1, sizeof(HTNet))) == NULL)
                    276:        outofmem(__FILE__, "HTNet_new");
                    277:     me->request = request;
                    278:     request->net = me;
                    279:     me->preemtive = (HTProtocol_preemtive(prot) || request->preemtive);
2.30    ! frystyk   280:     me->priority = request->priority;
2.23      frystyk   281:     me->sockfd = INVSOC;
                    282:     if (!(me->cbf = HTProtocol_callback(prot))) {
                    283:        if (THD_TRACE)
                    284:            fprintf(TDEST, "HTNet_new... NO CALL BACK FUNCTION!\n");
                    285:        free(me);
                    286:        return NO;
                    287:     }
2.25      frystyk   288:     request->retrys++;
2.23      frystyk   289: 
                    290:     /*
                    291:     ** Check if we can start the request, else put it into pending queue
                    292:     ** If so then call the call back function associated with the anchor.
                    293:     ** We use the INVSOC as we don't have a valid socket yet!
                    294:     */
                    295:     if (HTList_count(HTNetActive) < HTMaxActive) {
                    296:        HTList_addObject(HTNetActive, (void *) me);
                    297:        if (THD_TRACE)
                    298:            fprintf(TDEST, "HTNet_new... starting request %p (retry=%d)\n",
2.25      frystyk   299:                    request, request->retrys);
                    300:        (*(me->cbf))(me->sockfd, request, FD_NONE);
2.23      frystyk   301:     } else {
                    302:        if (!HTNetPending) HTNetPending = HTList_new();
                    303:        if (THD_TRACE)
                    304:            fprintf(TDEST, "HTNet_new... request %p registered as pending\n",
2.25      frystyk   305:                    request);
                    306:        HTProgress(request, HT_PROG_WAIT, NULL);
                    307:        HTList_addObject(HTNetPending, (void *) me);    
2.23      frystyk   308:     }
                    309:     return YES;
                    310: }
                    311: 
                    312: /*     delete_object
                    313: **     -------------
                    314: **     Deletes an HTNet object
2.15      frystyk   315: */
2.23      frystyk   316: PRIVATE BOOL delete_object (HTNet *net, int status)
2.15      frystyk   317: {
2.23      frystyk   318:     if (THD_TRACE)
                    319:        fprintf(TDEST, "HTNet_delete Remove net object %p\n", net);
                    320:     if (net) {
                    321:        int status = 0;
                    322: 
                    323:        /* Free stream with data FROM network to application */
                    324:        if (net->target) {
                    325:            if (status == HT_INTERRUPTED)
                    326:                (*net->target->isa->abort)(net->target, NULL);
                    327:            else
                    328:                (*net->target->isa->_free)(net->target);
                    329:        }
                    330: 
                    331:        /* Close socket */
                    332:        if (net->sockfd != INVSOC) {
2.24      frystyk   333:            if (HTDNS_socket(net->dns) == INVSOC) {
                    334:                if ((status = NETCLOSE(net->sockfd)) < 0)
                    335:                    HTErrorSysAdd(net->request, ERR_FATAL, socerrno, NO,
                    336:                                  "NETCLOSE");
                    337:                if (THD_TRACE)
                    338:                    fprintf(TDEST, "HTNet_delete closing %d\n", net->sockfd);
2.25      frystyk   339:                HTEvent_UnRegister(net->sockfd, (SockOps) FD_ALL);
2.24      frystyk   340:            } else {
                    341:                if (THD_TRACE)
                    342:                    fprintf(TDEST, "HTNet_delete keeping %d\n", net->sockfd);
2.25      frystyk   343:                HTDNS_clearActive(net->dns);
                    344:                /* Here we should probably use a low priority */
2.27      frystyk   345:                HTEvent_UnRegister(net->sockfd, (SockOps) FD_ALL);
2.25      frystyk   346:                HTEvent_Register(net->sockfd, net->request, (SockOps) FD_READ,
                    347:                                 HTDNS_closeSocket, net->priority);
2.24      frystyk   348:            }
2.23      frystyk   349:        }
                    350:        if (net->isoc)
                    351:            HTInputSocket_free(net->isoc);
                    352:        if (net->request)
                    353:            net->request->net = NULL;               /* Break link to request */
                    354:        free(net);
                    355:        return status ? NO : YES;
                    356:     }
                    357:     return NO;
                    358: }
                    359: 
                    360: /*     HTNet_delete
                    361: **     ------------
                    362: **     Deletes the HTNet object from the list of active requests and calls
                    363: **     any registered call back functions IF not the status is HT_IGNORE.
                    364: **     This is used if we have internal requests that the app doesn't know
                    365: **     about. We also see if we have pending requests that can be started
                    366: **     up now when we have a socket free.
                    367: **     The callback functions are called in the reverse order of which they
                    368: **     were registered (last one first)
                    369: */
                    370: PUBLIC BOOL HTNet_delete (HTNet * net, int status)
                    371: {
                    372:     if (THD_TRACE) 
                    373:        fprintf(TDEST,"HTNetDelete. Net Object and call callback functions\n");
                    374:     if (HTNetActive && net) {
2.25      frystyk   375:        SOCKFD cs = net->sockfd;                           /* Current sockfd */
2.23      frystyk   376: 
                    377:        /* Remove object and call callback functions */
                    378:        HTRequest *request = net->request;
                    379:        HTList_removeObject(HTNetActive, (void *) net);
                    380:        delete_object(net, status);
                    381:        HTNet_callback(request, status);
                    382: 
2.25      frystyk   383:        /*
                    384:        ** See first if we have a persistent request queued up for this socket
                    385:        ** If not then see if there is a pending request
                    386:        */
                    387:        if (HTNetPersistent) {
                    388:            HTList *cur = HTNetPersistent;
                    389:            HTNet *next;
                    390:            while ((next = (HTNet *) HTList_nextObject(cur))) {
                    391:                if (next->sockfd == cs) {
                    392:                    if (THD_TRACE)
                    393:                        fprintf(TDEST, "HTNet delete launch WARM request %p\n",
                    394:                                next->request);
2.28      frystyk   395:                    HTList_addObject(HTNetActive, (void *) next);
                    396:                    HTList_removeObject(HTNetPersistent, (void *) next);
2.25      frystyk   397:                    (*(next->cbf))(next->sockfd, next->request, FD_NONE);
                    398:                    break;
                    399:                }
                    400:            }
                    401:        } else if (HTList_count(HTNetActive) < HTMaxActive &&
                    402:                   HTList_count(HTNetPending)) {
2.23      frystyk   403:            HTNet *next = (HTNet *) HTList_removeFirstObject(HTNetPending);
                    404:            if (next) {
                    405:                HTList_addObject(HTNetActive, (void *) next);
                    406:                if (THD_TRACE)
2.25      frystyk   407:                    fprintf(TDEST,"HTNet delete launch PENDING request %p\n",
2.23      frystyk   408:                            next->request);
                    409:                (*(next->cbf))(INVSOC, next->request, FD_NONE);
                    410:            }
                    411:        }
                    412:        return YES;
                    413:     }
                    414:     return NO;
                    415: }
                    416: 
                    417: /*     HTNet_deleteAll
                    418: **     ---------------
                    419: **     Deletes all HTNet object that might either be active or pending
2.25      frystyk   420: **     We DO NOT call the call back functions - A crude way of saying goodbye!
2.23      frystyk   421: */
                    422: PUBLIC BOOL HTNet_deleteAll (void)
                    423: {
                    424:     if (THD_TRACE) 
2.25      frystyk   425:        fprintf(TDEST, "HTNetDelete. Remove all Net objects, NO callback\n"); 
                    426:     if (HTNetPersistent) {
                    427:        HTList *cur = HTNetPersistent;
                    428:        HTNet *pres;
                    429:        while ((pres = (HTNet *) HTList_nextObject(cur))) {
                    430:            pres->sockfd = INVSOC;          /* Don't close it more than once */
                    431:            delete_object(pres, HT_INTERRUPTED);
                    432:        }
                    433:        HTList_delete(HTNetPersistent);
                    434:        HTNetPersistent = NULL;
                    435:     }
2.23      frystyk   436:     if (HTNetPending) {
                    437:        HTList *cur = HTNetPending;
                    438:        HTNet *pres;
                    439:        while ((pres = (HTNet *) HTList_nextObject(cur)))
                    440:            delete_object(pres, HT_INTERRUPTED);
                    441:        HTList_delete(HTNetPending);
                    442:        HTNetPending = NULL;
                    443:     }
                    444:     if (HTNetActive) {
                    445:        HTList *cur = HTNetActive;
                    446:        HTNet *pres;
                    447:        while ((pres = (HTNet *) HTList_nextObject(cur)))
                    448:            delete_object(pres, HT_INTERRUPTED);
                    449:        HTList_delete(HTNetActive);
                    450:        HTNetActive = NULL;
                    451:     }
                    452:     return NO;
2.15      frystyk   453: }
                    454: 
2.25      frystyk   455: /*     HTNet_wait
                    456: **     ----------
                    457: **     Let a net object wait for a persistent socket. It will be launched
                    458: **     from the HTNet_delete() function
                    459: */
                    460: PUBLIC BOOL HTNet_wait (HTNet *net)
                    461: {
                    462:     if (net) {
                    463:        if (THD_TRACE)
                    464:            fprintf(TDEST,"HTNet_wait.. request %p is waiting for socket %d\n",
                    465:                    net->request, net->sockfd);
                    466:        if (!HTNetPersistent) HTNetPersistent = HTList_new();
                    467:        HTList_addObject(HTNetPersistent, (void *) net);        
                    468:        return YES;
                    469:     }
                    470:     return NO;
                    471: }
                    472: 
2.23      frystyk   473: /* ------------------------------------------------------------------------- */
                    474: /*                             Killing requests                             */
                    475: /* ------------------------------------------------------------------------- */
                    476: 
                    477: /*     HTNet_kill
                    478: **     ----------
                    479: **     Kill the request by calling the call back function with a request for 
                    480: **     closing the connection. Does not remove the object. This is done by
                    481: **     HTNet_delete() function which is called by the load routine.
                    482: **     Returns OK if success, NO on error
                    483: */
                    484: PUBLIC BOOL HTNet_kill (HTNet * me)
                    485: {
                    486:     if (HTNetActive && me) {
2.25      frystyk   487:        HTList *cur = HTNetActive;
2.23      frystyk   488:        HTNet *pres;
2.25      frystyk   489:        while ((pres = (HTNet *) HTList_nextObject(cur))) {
2.23      frystyk   490:            if (pres == me) {
2.25      frystyk   491:                (*(pres->cbf))(pres->sockfd, pres->request, FD_CLOSE);
2.23      frystyk   492:                return YES;
                    493:            }
                    494:        }
                    495:     }
                    496:     if (THD_TRACE)
2.25      frystyk   497:        fprintf(TDEST, "HTNet_kill.. object %p is not registered\n", me);
2.23      frystyk   498:     return NO;
                    499: }
                    500: 
                    501: /*     HTNet_killAll
                    502: **     -------------
                    503: **     Kills all registered (active+pending) requests by calling the call
                    504: **     back function with a request for closing the connection. We do not
                    505: **     remove the HTNet object as it is done by HTNet_delete().
                    506: **     Returns OK if success, NO on error
                    507: */
                    508: PUBLIC BOOL HTNet_killAll (void)
                    509: {
                    510:     HTNet *pres;
                    511:     if (THD_TRACE)
                    512:        fprintf(TDEST, "HTNet_kill.. ALL registered requests!!!\n");
                    513: 
2.25      frystyk   514:     /* We start off in persistent queue so we avoid racing */
                    515:     if (HTNetPersistent) {
                    516:        while ((pres = (HTNet *) HTList_lastObject(HTNetPersistent)) != NULL) {
                    517:            pres->sockfd = INVSOC;
                    518:            (*(pres->cbf))(pres->sockfd, pres->request, FD_CLOSE);
                    519:            HTList_removeObject(HTNetPersistent, pres);
                    520:        }
                    521:     }
2.23      frystyk   522:     if (HTNetPending) {
                    523:        while ((pres = (HTNet *) HTList_lastObject(HTNetPending)) != NULL) {
2.25      frystyk   524:            (*(pres->cbf))(pres->sockfd, pres->request, FD_CLOSE);
2.23      frystyk   525:            HTList_removeObject(HTNetPending, pres);
                    526:        }
                    527:     }
                    528:     if (HTNetActive) {
                    529:        while ((pres = (HTNet *) HTList_lastObject(HTNetActive)) != NULL)
2.25      frystyk   530:            (*(pres->cbf))(pres->sockfd, pres->request, FD_CLOSE);
2.23      frystyk   531:     }
                    532:     return YES;
                    533: }

Webmaster