Annotation of libwww/Library/src/HTAccess.c, revision 1.60
1.1 timbl 1: /* Access Manager HTAccess.c
2: ** ==============
3: **
4: ** Authors
5: ** TBL Tim Berners-Lee timbl@info.cern.ch
1.4 timbl 6: ** JFG Jean-Francois Groff jfg@dxcern.cern.ch
1.1 timbl 7: ** DD Denis DeLaRoca (310) 825-4580 <CSP1DWD@mvs.oac.ucla.edu>
8: ** History
9: ** 8 Jun 92 Telnet hopping prohibited as telnet is not secure TBL
10: ** 26 Jun 92 When over DECnet, suppressed FTP, Gopher and News. JFG
1.42 frystyk 11: ** 6 Oct 92 Moved HTClientHost and HTlogfile into here. TBL
1.1 timbl 12: ** 17 Dec 92 Tn3270 added, bug fix. DD
1.2 timbl 13: ** 4 Feb 93 Access registration, Search escapes bad chars TBL
1.9 timbl 14: ** PARAMETERS TO HTSEARCH AND HTLOADRELATIVE CHANGED
15: ** 28 May 93 WAIS gateway explicit if no WAIS library linked in.
1.19 timbl 16: ** Dec 93 Bug change around, more reentrant, etc
1.42 frystyk 17: ** 09 May 94 logfile renamed to HTlogfile to avoid clash with WAIS
1.53 duns 18: ** 8 Jul 94 Insulate free() from _free structure element.
1.2 timbl 19: ** Bugs
20: ** This module assumes that that the graphic object is hypertext, as it
1.9 timbl 21: ** needs to select it when it has been loaded. A superclass needs to be
1.2 timbl 22: ** defined which accepts select and select_anchor.
1.1 timbl 23: */
24:
1.9 timbl 25: #ifndef DEFAULT_WAIS_GATEWAY
1.8 timbl 26: #define DEFAULT_WAIS_GATEWAY "http://info.cern.ch:8001/"
1.54 frystyk 27: #endif
1.8 timbl 28:
1.1 timbl 29: /* Implements:
30: */
31: #include "HTAccess.h"
32:
33: /* Uses:
34: */
35:
36: #include "HTParse.h"
37: #include "HTUtils.h"
1.4 timbl 38: #include "HTML.h" /* SCW */
1.2 timbl 39:
40: #ifndef NO_RULES
41: #include "HTRules.h"
42: #endif
43:
44: #include "HTList.h"
45: #include "HText.h" /* See bugs above */
46: #include "HTAlert.h"
1.17 timbl 47: #include "HTFWriter.h" /* for cache stuff */
48: #include "HTTee.h"
1.46 frystyk 49: #include "HTError.h"
1.57 howcome 50: #include "HTTCP.h" /* HWL: for HTFindRelatedName */
1.59 frystyk 51: #include "HTThread.h"
1.2 timbl 52:
1.54 frystyk 53: /* These flags may be set to modify the operation of this module */
54: PUBLIC char * HTCacheDir = NULL; /* Root for cached files or 0 for no cache */
55: PUBLIC char * HTSaveLocallyDir = SAVE_LOCALLY_HOME_DIR; /* Save & exe files */
56: PUBLIC char * HTClientHost = 0; /* Name of remote login host if any */
57: PUBLIC FILE * HTlogfile = 0; /* File to which to output one-liners */
1.41 luotonen 58:
1.34 frystyk 59: PUBLIC BOOL HTForceReload = NO; /* Force reload from cache or net */
1.12 timbl 60: PUBLIC BOOL HTSecure = NO; /* Disable access for telnet users? */
1.27 luotonen 61: PUBLIC BOOL using_proxy = NO; /* are we using a proxy gateway? */
1.43 luotonen 62: PUBLIC char * HTImServer = NULL;/* cern_httpd sets this to the translated URL*/
1.27 luotonen 63: PUBLIC BOOL HTImProxy = NO; /* cern_httpd as a proxy? */
1.1 timbl 64:
1.43 luotonen 65:
1.2 timbl 66: /* To generate other things, play with these:
67: */
68:
1.15 timbl 69: /* PUBLIC HTFormat HTOutputFormat = NULL; use request->output_format */
70: /* PUBLIC HTStream* HTOutputStream = NULL; use request->output_stream */
1.1 timbl 71:
72: PRIVATE HTList * protocols = NULL; /* List of registered protocol descriptors */
73:
1.24 timbl 74: /* Superclass defn */
1.1 timbl 75:
1.24 timbl 76: struct _HTStream {
77: HTStreamClass * isa;
78: /* ... */
79: };
80:
1.59 frystyk 81: /* --------------------------------------------------------------------------*/
82: /* Management of the HTRequest structure */
83: /* --------------------------------------------------------------------------*/
84:
1.15 timbl 85: /* Create a request structure
86: ** ---------------------------
87: */
88: PUBLIC HTRequest * HTRequest_new NOARGS
89: {
1.28 luotonen 90: HTRequest * me = (HTRequest*) calloc(1, sizeof(*me)); /* zero fill */
1.15 timbl 91: if (!me) outofmem(__FILE__, "HTRequest_new()");
92:
1.20 luotonen 93: me->conversions = HTList_new(); /* No conversions registerd yet */
94: me->output_format = WWW_PRESENT; /* default it to present to user */
95:
1.15 timbl 96: return me;
97: }
98:
99:
1.49 frystyk 100: /* Clear a request structure
101: ** ---------------------------
102: ** This function clears the reguest structure so that only the
103: ** conversions remain. Everything else is as if it was created from
104: ** scratch.
105: */
106: PUBLIC void HTRequest_clear ARGS1(HTRequest *, req)
107: {
108: HTList *conversions;
109: if (!req) {
110: if (TRACE)
111: fprintf(stderr, "Clear....... request: Bad argument!\n");
112: return;
113: }
114: conversions = req->conversions; /* Save the conversions */
115: HTErrorFree(req);
116: HTAACleanup(req);
117: memset(req, '\0', sizeof(HTRequest));
118:
119: /* Now initialize as from scratch but with the old list of conversions */
120: req->conversions = conversions;
121: req->output_format = WWW_PRESENT; /* default it to present to user */
122: }
123:
124:
1.20 luotonen 125: /* Delete a request structure
126: ** --------------------------
127: */
128: PUBLIC void HTRequest_delete ARGS1(HTRequest *, req)
129: {
130: if (req) {
1.59 frystyk 131: FREE(req->redirect);
132: FREE(req->authenticate);
133: HTFormatDelete(req);
1.46 frystyk 134: HTErrorFree(req);
1.34 frystyk 135: HTAACleanup(req);
136: FREE(req);
1.20 luotonen 137: }
138: }
139:
1.59 frystyk 140: /* --------------------------------------------------------------------------*/
141: /* Management of HTTP Methods */
142: /* --------------------------------------------------------------------------*/
1.20 luotonen 143:
1.22 luotonen 144: PRIVATE char * method_names[(int)MAX_METHODS + 1] =
145: {
146: "INVALID-METHOD",
147: "GET",
148: "HEAD",
149: "POST",
150: "PUT",
151: "DELETE",
152: "CHECKOUT",
153: "CHECKIN",
154: "SHOWMETHOD",
155: "LINK",
156: "UNLINK",
157: NULL
158: };
159:
160: /* Get method enum value
161: ** ---------------------
162: */
163: PUBLIC HTMethod HTMethod_enum ARGS1(char *, name)
164: {
165: if (name) {
166: int i;
167: for (i=1; i < (int)MAX_METHODS; i++)
168: if (!strcmp(name, method_names[i]))
169: return (HTMethod)i;
170: }
171: return METHOD_INVALID;
172: }
173:
174:
175: /* Get method name
176: ** ---------------
177: */
178: PUBLIC char * HTMethod_name ARGS1(HTMethod, method)
179: {
180: if ((int)method > (int)METHOD_INVALID &&
181: (int)method < (int)MAX_METHODS)
182: return method_names[(int)method];
183: else
184: return method_names[(int)METHOD_INVALID];
185: }
186:
187:
188: /* Is method in a list of method names?
189: ** -----------------------------------
190: */
191: PUBLIC BOOL HTMethod_inList ARGS2(HTMethod, method,
192: HTList *, list)
193: {
194: char * method_name = HTMethod_name(method);
195: HTList *cur = list;
196: char *item;
197:
198: while (NULL != (item = (char*)HTList_nextObject(cur))) {
199: CTRACE(stderr, " %s", item);
200: if (0==strcasecomp(item, method_name))
201: return YES;
202: }
203: return NO; /* Not found */
204: }
205:
206:
1.59 frystyk 207: /* --------------------------------------------------------------------------*/
208: /* Management of the HTProtocol structure */
209: /* --------------------------------------------------------------------------*/
1.22 luotonen 210:
1.1 timbl 211: /* Register a Protocol HTRegisterProtocol
212: ** -------------------
213: */
1.56 frystyk 214: PUBLIC BOOL HTRegisterProtocol ARGS1(HTProtocol *, protocol)
1.1 timbl 215: {
216: if (!protocols) protocols = HTList_new();
1.59 frystyk 217: HTList_addObject(protocols, (void *) protocol);
1.1 timbl 218: return YES;
219: }
220:
1.59 frystyk 221: PUBLIC BOOL HTProtocolBlocking ARGS1(HTRequest *, me)
222: {
223: if (me && me->anchor && me->anchor->protocol &&
224: ((HTProtocol *) (me->anchor->protocol))->block == SOC_BLOCK)
225: return YES;
226: else
227: return NO;
228: }
229:
1.1 timbl 230:
231: /* Register all known protocols
232: ** ----------------------------
233: **
234: ** Add to or subtract from this list if you add or remove protocol modules.
235: ** This routine is called the first time the protocol list is needed,
1.52 frystyk 236: ** unless any protocols are already registered, in which case it is not
237: ** called. Therefore the application can override this list.
1.1 timbl 238: **
239: ** Compiling with NO_INIT prevents all known protocols from being forced
240: ** in at link time.
241: */
242: #ifndef NO_INIT
243: PRIVATE void HTAccessInit NOARGS /* Call me once */
244: {
1.59 frystyk 245: GLOBALREF HTProtocol HTTP, HTFile, HTTelnet, HTTn3270, HTRlogin;
1.1 timbl 246: #ifndef DECNET
1.54 frystyk 247: #ifdef NEW_CODE
1.59 frystyk 248: GLOBALREF HTProtocol HTFTP, HTNews, HTNNTP, HTGopher;
249: #endif
250: GLOBALREF HTProtocol HTFTP, HTNews, HTGopher;
251: #ifdef DIRECT_WAIS
252: GLOBALREF HTProtocol HTWAIS;
1.54 frystyk 253: #endif
1.42 frystyk 254:
1.59 frystyk 255: HTThreadInit(); /* Initialize bit arrays and stdin */
256:
257: #ifdef LIB_SIG /* Set signals in library */
258: HTSetSignal();
1.3 timbl 259: #endif
1.59 frystyk 260:
1.2 timbl 261: HTRegisterProtocol(&HTFTP);
262: HTRegisterProtocol(&HTNews);
1.54 frystyk 263: #ifdef NEW_CODE
264: HTRegisterProtocol(&HTNNTP);
265: #endif
1.2 timbl 266: HTRegisterProtocol(&HTGopher);
1.42 frystyk 267:
1.3 timbl 268: #ifdef DIRECT_WAIS
269: HTRegisterProtocol(&HTWAIS);
270: #endif
1.1 timbl 271:
1.54 frystyk 272: #endif /* DECNET */
1.2 timbl 273: HTRegisterProtocol(&HTTP);
274: HTRegisterProtocol(&HTFile);
275: HTRegisterProtocol(&HTTelnet);
276: HTRegisterProtocol(&HTTn3270);
277: HTRegisterProtocol(&HTRlogin);
1.1 timbl 278: }
279: #endif
280:
1.59 frystyk 281: /* --------------------------------------------------------------------------*/
282: /* Physical Anchor Address Manager */
283: /* --------------------------------------------------------------------------*/
1.33 luotonen 284:
285: /* override_proxy()
286: **
287: ** Check the no_proxy environment variable to get the list
288: ** of hosts for which proxy server is not consulted.
289: **
290: ** no_proxy is a comma- or space-separated list of machine
291: ** or domain names, with optional :port part. If no :port
292: ** part is present, it applies to all ports on that domain.
293: **
294: ** Example:
295: ** no_proxy="cern.ch,some.domain:8001"
296: **
297: */
298: PRIVATE BOOL override_proxy ARGS1(CONST char *, addr)
299: {
300: CONST char * no_proxy = getenv("no_proxy");
301: char * p = NULL;
302: char * host = NULL;
303: int port = 0;
304: int h_len = 0;
305:
306: if (!no_proxy || !addr || !(host = HTParse(addr, "", PARSE_HOST)))
307: return NO;
308: if (!*host) { free(host); return NO; }
309:
1.34 frystyk 310: if ((p = strchr(host, ':')) != NULL) { /* Port specified */
1.33 luotonen 311: *p++ = 0; /* Chop off port */
312: port = atoi(p);
313: }
314: else { /* Use default port */
315: char * access = HTParse(addr, "", PARSE_ACCESS);
316: if (access) {
317: if (!strcmp(access,"http")) port = 80;
318: else if (!strcmp(access,"gopher")) port = 70;
319: else if (!strcmp(access,"ftp")) port = 21;
320: free(access);
321: }
322: }
323: if (!port) port = 80; /* Default */
324: h_len = strlen(host);
325:
326: while (*no_proxy) {
327: CONST char * end;
328: CONST char * colon = NULL;
329: int templ_port = 0;
330: int t_len;
331:
332: while (*no_proxy && (WHITE(*no_proxy) || *no_proxy==','))
333: no_proxy++; /* Skip whitespace and separators */
334:
335: end = no_proxy;
336: while (*end && !WHITE(*end) && *end != ',') { /* Find separator */
337: if (*end==':') colon = end; /* Port number given */
338: end++;
339: }
340:
341: if (colon) {
342: templ_port = atoi(colon+1);
343: t_len = colon - no_proxy;
344: }
345: else {
346: t_len = end - no_proxy;
347: }
348:
349: if ((!templ_port || templ_port == port) &&
350: (t_len > 0 && t_len <= h_len &&
351: !strncmp(host + h_len - t_len, no_proxy, t_len))) {
352: free(host);
353: return YES;
354: }
355: if (*end) no_proxy = end+1;
356: else break;
357: }
358:
359: free(host);
360: return NO;
361: }
362:
363:
364:
1.2 timbl 365: /* Find physical name and access protocol
366: ** --------------------------------------
1.1 timbl 367: **
368: **
369: ** On entry,
370: ** addr must point to the fully qualified hypertext reference.
371: ** anchor a pareent anchor with whose address is addr
372: **
1.59 frystyk 373: ** On exit,
374: ** returns HT_NO_ACCESS no protocol module found
375: ** HT_FORBIDDEN Error has occured.
1.2 timbl 376: ** HT_OK Success
1.1 timbl 377: **
378: */
1.21 luotonen 379: PRIVATE int get_physical ARGS1(HTRequest *, req)
380: {
1.1 timbl 381: char * access=0; /* Name of access method */
1.21 luotonen 382: char * addr = HTAnchor_address((HTAnchor*)req->anchor); /* free me */
1.27 luotonen 383:
1.2 timbl 384: #ifndef NO_RULES
1.47 luotonen 385: if (HTImServer) { /* cern_httpd has already done its own translations */
1.45 luotonen 386: HTAnchor_setPhysical(req->anchor, HTImServer);
1.47 luotonen 387: StrAllocCopy(addr, HTImServer); /* Oops, queries thru many proxies */
388: /* didn't work without this -- AL */
389: }
1.21 luotonen 390: else {
1.27 luotonen 391: char * physical = HTTranslate(addr);
1.21 luotonen 392: if (!physical) {
1.47 luotonen 393: free(addr);
1.21 luotonen 394: return HT_FORBIDDEN;
395: }
396: HTAnchor_setPhysical(req->anchor, physical);
397: free(physical); /* free our copy */
1.2 timbl 398: }
399: #else
1.21 luotonen 400: HTAnchor_setPhysical(req->anchor, addr);
1.2 timbl 401: #endif
402:
1.21 luotonen 403: access = HTParse(HTAnchor_physical(req->anchor),
1.27 luotonen 404: "file:", PARSE_ACCESS);
1.1 timbl 405:
406: /* Check whether gateway access has been set up for this
1.8 timbl 407: **
408: ** This function can be replaced by the rule system above.
1.1 timbl 409: */
1.8 timbl 410: #define USE_GATEWAYS
1.1 timbl 411: #ifdef USE_GATEWAYS
1.39 luotonen 412:
413: /* make sure the using_proxy variable is false */
414: using_proxy = NO;
415:
1.33 luotonen 416: if (!override_proxy(addr)) {
1.27 luotonen 417: char * gateway_parameter, *gateway, *proxy;
418:
1.2 timbl 419: gateway_parameter = (char *)malloc(strlen(access)+20);
420: if (gateway_parameter == NULL) outofmem(__FILE__, "HTLoad");
1.27 luotonen 421:
422: /* search for proxy gateways */
1.2 timbl 423: strcpy(gateway_parameter, "WWW_");
424: strcat(gateway_parameter, access);
425: strcat(gateway_parameter, "_GATEWAY");
426: gateway = (char *)getenv(gateway_parameter); /* coerce for decstation */
1.27 luotonen 427:
428: /* search for proxy servers */
429: strcpy(gateway_parameter, access);
430: strcat(gateway_parameter, "_proxy");
431: proxy = (char *)getenv(gateway_parameter);
432:
1.2 timbl 433: free(gateway_parameter);
1.27 luotonen 434:
435: if (TRACE && gateway)
1.60 ! frystyk 436: fprintf(stderr,"Gateway..... Found: `%s\'\n", gateway);
1.27 luotonen 437: if (TRACE && proxy)
1.60 ! frystyk 438: fprintf(stderr,"Proxy....... Found: `%s\'\n", proxy);
1.27 luotonen 439:
1.8 timbl 440: #ifndef DIRECT_WAIS
1.9 timbl 441: if (!gateway && 0==strcmp(access, "wais")) {
1.8 timbl 442: gateway = DEFAULT_WAIS_GATEWAY;
443: }
444: #endif
1.27 luotonen 445:
446: /* proxy servers have precedence over gateway servers */
1.60 ! frystyk 447: if (proxy && *proxy) {
1.27 luotonen 448: char * gatewayed=0;
449:
450: StrAllocCopy(gatewayed,proxy);
451: StrAllocCat(gatewayed,addr);
452: using_proxy = YES;
453: HTAnchor_setPhysical(req->anchor, gatewayed);
454: free(gatewayed);
455: free(access);
456:
457: access = HTParse(HTAnchor_physical(req->anchor),
458: "http:", PARSE_ACCESS);
1.60 ! frystyk 459: } else if (gateway && *gateway) {
1.9 timbl 460: char * path = HTParse(addr, "",
461: PARSE_HOST + PARSE_PATH + PARSE_PUNCTUATION);
462: /* Chop leading / off to make host into part of path */
463: char * gatewayed = HTParse(path+1, gateway, PARSE_ALL);
464: free(path);
1.21 luotonen 465: HTAnchor_setPhysical(req->anchor, gatewayed);
1.9 timbl 466: free(gatewayed);
1.2 timbl 467: free(access);
1.9 timbl 468:
1.21 luotonen 469: access = HTParse(HTAnchor_physical(req->anchor),
1.8 timbl 470: "http:", PARSE_ACCESS);
1.2 timbl 471: }
472: }
1.1 timbl 473: #endif
474:
1.19 timbl 475: free(addr);
1.1 timbl 476:
477:
478: /* Search registered protocols to find suitable one
479: */
480: {
1.20 luotonen 481: HTList *cur;
482: HTProtocol *p;
1.1 timbl 483: #ifndef NO_INIT
1.2 timbl 484: if (!protocols) HTAccessInit();
1.1 timbl 485: #endif
1.20 luotonen 486: cur = protocols;
487: while ((p = (HTProtocol*)HTList_nextObject(cur))) {
1.2 timbl 488: if (strcmp(p->name, access)==0) {
1.21 luotonen 489: HTAnchor_setProtocol(req->anchor, p);
1.2 timbl 490: free(access);
491: return (HT_OK);
1.1 timbl 492: }
493: }
494: }
495:
496: free(access);
1.2 timbl 497: return HT_NO_ACCESS;
1.1 timbl 498: }
499:
1.59 frystyk 500: /* --------------------------------------------------------------------------*/
501: /* Document Poster */
502: /* --------------------------------------------------------------------------*/
503:
504: /* Get a save stream for a document
505: ** --------------------------------
506: */
507: PUBLIC HTStream *HTSaveStream ARGS1(HTRequest *, request)
508: {
509: HTProtocol * p;
510: int status;
511: request->method = METHOD_PUT;
512: status = get_physical(request);
513: if (status == HT_FORBIDDEN) {
514: char *url = HTAnchor_address((HTAnchor *) request->anchor);
515: if (url) {
516: HTUnEscape(url);
517: HTErrorAdd(request, ERR_FATAL, NO, HTERR_FORBIDDEN,
518: (void *) url, (int) strlen(url), "HTLoad");
519: free(url);
520: } else {
521: HTErrorAdd(request, ERR_FATAL, NO, HTERR_FORBIDDEN,
522: NULL, 0, "HTLoad");
523: }
524: return NULL; /* should return error status? */
525: }
526: if (status < 0) return NULL; /* @@ error. Can't resolve or forbidden */
527:
528: p = (HTProtocol *) HTAnchor_protocol(request->anchor);
529: if (!p) return NULL;
530:
531: return (*p->saveStream)(request);
532:
533: }
534:
535:
536: /* --------------------------------------------------------------------------*/
537: /* Document Loader */
538: /* --------------------------------------------------------------------------*/
1.1 timbl 539:
540: /* Load a document
541: ** ---------------
542: **
1.2 timbl 543: ** This is an internal routine, which has an address AND a matching
544: ** anchor. (The public routines are called with one OR the other.)
545: **
546: ** On entry,
1.15 timbl 547: ** request->
1.35 luotonen 548: ** anchor a parent anchor with fully qualified
549: ** hypertext reference as its address set
1.15 timbl 550: ** output_format valid
551: ** output_stream valid on NULL
1.2 timbl 552: **
553: ** On exit,
1.59 frystyk 554: ** returns HT_WOULD_BLOCK An I/O operation would block
555: ** HT_ERROR Error has occured
1.2 timbl 556: ** HT_LOADED Success
557: ** HT_NO_DATA Success, but no document loaded.
1.8 timbl 558: ** (telnet sesssion started etc)
1.2 timbl 559: **
560: */
1.52 frystyk 561: PUBLIC int HTLoad ARGS2(HTRequest *, request, BOOL, keep_error_stack)
1.2 timbl 562: {
1.25 frystyk 563: char *arg = NULL;
564: HTProtocol *p;
565: int status;
566:
1.22 luotonen 567: if (request->method == METHOD_INVALID)
568: request->method = METHOD_GET;
1.52 frystyk 569: if (!keep_error_stack) {
570: HTErrorFree(request);
571: request->error_block = NO;
572: }
573:
1.59 frystyk 574: if ((status = get_physical(request)) < 0) {
575: if (status == HT_FORBIDDEN) {
576: char *url = HTAnchor_address((HTAnchor *) request->anchor);
577: if (url) {
578: HTUnEscape(url);
579: HTErrorAdd(request, ERR_FATAL, NO, HTERR_FORBIDDEN,
580: (void *) url, (int) strlen(url), "HTLoad");
581: free(url);
582: } else {
583: HTErrorAdd(request, ERR_FATAL, NO, HTERR_FORBIDDEN,
584: NULL, 0, "HTLoad");
585: }
586: }
587: return HT_ERROR; /* Can't resolve or forbidden */
1.2 timbl 588: }
1.25 frystyk 589:
590: if(!(arg = HTAnchor_physical(request->anchor)) || !*arg)
1.59 frystyk 591: return HT_ERROR;
1.27 luotonen 592:
1.56 frystyk 593: p = (HTProtocol *) HTAnchor_protocol(request->anchor);
1.17 timbl 594: return (*(p->load))(request);
1.2 timbl 595: }
596:
597:
598: /* Load a document - with logging etc
599: ** ----------------------------------
600: **
601: ** - Checks or documents already loaded
602: ** - Logs the access
603: ** - Allows stdin filter option
604: ** - Trace ouput and error messages
605: **
1.1 timbl 606: ** On Entry,
1.19 timbl 607: ** request->anchor valid for of the document to be accessed.
608: ** request->childAnchor optional anchor within doc to be selected
609: **
1.2 timbl 610: ** filter if YES, treat stdin as HTML
1.1 timbl 611: **
1.15 timbl 612: ** request->anchor is the node_anchor for the document
613: ** request->output_format is valid
614: **
1.59 frystyk 615: ** On exit,
616: ** returns HT_WOULD_BLOCK An I/O operation would block
617: ** HT_ERROR Error has occured
618: ** HT_LOADED Success
619: ** HT_NO_DATA Success, but no document loaded.
620: ** (telnet sesssion started etc)
1.1 timbl 621: */
622:
1.59 frystyk 623: PRIVATE int HTLoadDocument ARGS2(HTRequest *, request,
624: BOOL, keep_error_stack)
1.1 timbl 625:
626: {
627: int status;
628: HText * text;
1.19 timbl 629: char * full_address = HTAnchor_address((HTAnchor*)request->anchor);
1.54 frystyk 630:
1.59 frystyk 631: if (PROT_TRACE) fprintf (stderr, "HTAccess.... Loading document %s\n",
632: full_address);
1.1 timbl 633:
1.18 timbl 634: request->using_cache = NULL;
635:
1.15 timbl 636: if (!request->output_format) request->output_format = WWW_PRESENT;
1.25 frystyk 637:
1.31 frystyk 638: if (!HTForceReload && (text=(HText *)HTAnchor_document(request->anchor)))
1.15 timbl 639: { /* Already loaded */
1.59 frystyk 640: if (PROT_TRACE)
641: fprintf(stderr, "HTAccess.... Document already in memory.\n");
1.19 timbl 642: if (request->childAnchor) {
643: HText_selectAnchor(text, request->childAnchor);
644: } else {
645: HText_select(text);
646: }
647: free(full_address);
1.59 frystyk 648: return HT_LOADED;
1.1 timbl 649: }
1.17 timbl 650:
1.34 frystyk 651: /* Check the Cache */
1.17 timbl 652: /* Bug: for each format, we only check whether it is ok, we
653: don't check them all and chose the best */
1.54 frystyk 654: if (request->anchor->cacheItems) {
1.17 timbl 655: HTList * list = request->anchor->cacheItems;
1.20 luotonen 656: HTList * cur = list;
657: HTCacheItem * item;
658:
659: while ((item = (HTCacheItem*)HTList_nextObject(cur))) {
1.18 timbl 660: HTStream * s;
661:
662: request->using_cache = item;
663:
1.59 frystyk 664: s = HTStreamStack(item->format, request->output_format,
665: request->output_stream, request, NO);
1.17 timbl 666: if (s) { /* format was suitable */
667: FILE * fp = fopen(item->filename, "r");
1.59 frystyk 668: if (PROT_TRACE)
1.57 howcome 669: fprintf(stderr, "Cache: HIT file %s for %s\n",
1.20 luotonen 670: item->filename,
671: full_address);
1.17 timbl 672: if (fp) {
673: HTFileCopy(fp, s);
1.53 duns 674: (*s->isa->_free)(s); /* close up pipeline */
1.17 timbl 675: fclose(fp);
1.19 timbl 676: free(full_address);
1.59 frystyk 677: return HT_LOADED;
1.17 timbl 678: } else {
679: fprintf(stderr, "***** Can't read cache file %s !\n",
1.20 luotonen 680: item->filename);
1.17 timbl 681: } /* file open ok */
682: } /* stream ok */
683: } /* next cache item */
684: } /* if cache available for this anchor */
1.1 timbl 685:
1.52 frystyk 686: status = HTLoad(request, keep_error_stack);
1.2 timbl 687:
1.59 frystyk 688: /* Log the access if necessary */
1.42 frystyk 689: if (HTlogfile) {
1.1 timbl 690: time_t theTime;
691: time(&theTime);
1.42 frystyk 692: fprintf(HTlogfile, "%24.24s %s %s %s\n",
1.1 timbl 693: ctime(&theTime),
694: HTClientHost ? HTClientHost : "local",
695: status<0 ? "FAIL" : "GET",
696: full_address);
1.42 frystyk 697: fflush(HTlogfile); /* Actually update it on disk */
1.59 frystyk 698: if (PROT_TRACE) fprintf(stderr, "Log: %24.24s %s %s %s\n",
1.1 timbl 699: ctime(&theTime),
700: HTClientHost ? HTClientHost : "local",
701: status<0 ? "FAIL" : "GET",
702: full_address);
703: }
704:
1.52 frystyk 705: /* The error stack might contain general information to the client
706: about what has been going on in the library (not only errors) */
1.58 frystyk 707: if (!HTImProxy && request->error_stack)
1.52 frystyk 708: HTErrorMsg(request);
709:
1.59 frystyk 710: switch (status) {
711: case HT_LOADED:
712: if (PROT_TRACE) {
713: fprintf(stderr, "HTAccess.... OK: `%s' has been accessed.\n",
714: full_address);
715: }
716: break;
717:
718: case HT_NO_DATA:
719: if (PROT_TRACE) {
720: fprintf(stderr, "HTAccess.... OK BUT NO DATA: `%s'\n",
721: full_address);
1.1 timbl 722: }
1.59 frystyk 723: break;
724:
725: case HT_WOULD_BLOCK:
726: if (PROT_TRACE) {
727: fprintf(stderr, "HTAccess.... WOULD BLOCK: `%s'\n",
728: full_address);
1.1 timbl 729: }
1.59 frystyk 730: break;
731:
732: case HT_ERROR:
1.58 frystyk 733: if (HTImProxy)
734: HTErrorMsg(request); /* Only on a real error */
1.59 frystyk 735: if (PROT_TRACE) {
736: fprintf(stderr, "HTAccess.... ERROR: Can't access `%s'\n",
737: full_address);
738: }
739: break;
740:
741: default:
742: if (PROT_TRACE) {
743: fprintf(stderr, "HTAccess.... Internal software error in CERN WWWLib version %s ****\n\nPlease mail www-bug@info.cern.ch quoting what software and what version you are using\nand the URL: %s that caused the problem, thanks!\n",
744: HTLibraryVersion,
745: full_address);
746: }
747: status = HT_ERROR;
748: break;
1.1 timbl 749: }
1.19 timbl 750: free(full_address);
1.59 frystyk 751: return status;
1.58 frystyk 752: }
1.1 timbl 753:
754:
755: /* Load a document from absolute name
756: ** ---------------
757: **
1.59 frystyk 758: ** On Entry,
1.1 timbl 759: ** addr The absolute address of the document to be accessed.
760: ** filter if YES, treat document as HTML
761: **
1.59 frystyk 762: ** On exit,
763: ** returns HT_WOULD_BLOCK An I/O operation would block
764: ** HT_ERROR Error has occured
765: ** HT_LOADED Success
766: ** HT_NO_DATA Success, but no document loaded.
767: ** (telnet sesssion started etc)
1.1 timbl 768: */
769:
1.59 frystyk 770: PUBLIC int HTLoadAbsolute ARGS2(CONST char *,addr, HTRequest*, request)
1.2 timbl 771: {
1.19 timbl 772: HTAnchor * anchor = HTAnchor_findAddress(addr);
773: request->anchor = HTAnchor_parent(anchor);
774: request->childAnchor = ((HTAnchor*)request->anchor == anchor) ?
775: NULL : (HTChildAnchor*) anchor;
1.52 frystyk 776: return HTLoadDocument(request, NO);
1.2 timbl 777: }
778:
779:
780: /* Load a document from absolute name to stream
781: ** --------------------------------------------
782: **
1.59 frystyk 783: ** On Entry,
1.2 timbl 784: ** addr The absolute address of the document to be accessed.
1.15 timbl 785: ** request->output_stream if non-NULL, send data down this stream
1.2 timbl 786: **
1.59 frystyk 787: ** On exit,
788: ** returns HT_WOULD_BLOCK An I/O operation would block
789: ** HT_ERROR Error has occured
790: ** HT_LOADED Success
791: ** HT_NO_DATA Success, but no document loaded.
792: ** (telnet sesssion started etc)
1.2 timbl 793: */
794:
1.59 frystyk 795: PUBLIC int HTLoadToStream ARGS3(CONST char *, addr,
796: BOOL, filter,
797: HTRequest*, request)
1.1 timbl 798: {
1.19 timbl 799: HTAnchor * anchor = HTAnchor_findAddress(addr);
800: request->anchor = HTAnchor_parent(anchor);
801: request->childAnchor = ((HTAnchor*)request->anchor == anchor) ? NULL :
802: (HTChildAnchor*) anchor;
1.15 timbl 803: request->output_stream = request->output_stream;
1.52 frystyk 804: return HTLoadDocument(request, NO);
1.1 timbl 805: }
806:
807:
808: /* Load a document from relative name
809: ** ---------------
810: **
1.59 frystyk 811: ** On Entry,
1.2 timbl 812: ** relative_name The relative address of the document
813: ** to be accessed.
1.1 timbl 814: **
1.59 frystyk 815: ** On exit,
816: ** returns HT_WOULD_BLOCK An I/O operation would block
817: ** HT_ERROR Error has occured
818: ** HT_LOADED Success
819: ** HT_NO_DATA Success, but no document loaded.
820: ** (telnet sesssion started etc)
1.1 timbl 821: */
822:
1.59 frystyk 823: PUBLIC int HTLoadRelative ARGS3(CONST char *, relative_name,
824: HTParentAnchor *, here,
825: HTRequest *, request)
1.1 timbl 826: {
827: char * full_address = 0;
828: BOOL result;
829: char * mycopy = 0;
830: char * stripped = 0;
831: char * current_address =
1.2 timbl 832: HTAnchor_address((HTAnchor*)here);
1.1 timbl 833:
834: StrAllocCopy(mycopy, relative_name);
835:
836: stripped = HTStrip(mycopy);
837: full_address = HTParse(stripped,
838: current_address,
839: PARSE_ACCESS|PARSE_HOST|PARSE_PATH|PARSE_PUNCTUATION);
1.15 timbl 840: result = HTLoadAbsolute(full_address, request);
1.1 timbl 841: free(full_address);
842: free(current_address);
843: free(mycopy); /* Memory leak fixed 10/7/92 -- JFG */
844: return result;
845: }
846:
847:
848: /* Load if necessary, and select an anchor
849: ** --------------------------------------
850: **
1.59 frystyk 851: ** On Entry,
1.1 timbl 852: ** destination The child or parenet anchor to be loaded.
853: **
1.59 frystyk 854: ** On exit,
855: ** returns HT_WOULD_BLOCK An I/O operation would block
856: ** HT_ERROR Error has occured
857: ** HT_LOADED Success
858: ** HT_NO_DATA Success, but no document loaded.
859: ** (telnet sesssion started etc)
1.1 timbl 860: */
861:
1.59 frystyk 862: PUBLIC int HTLoadAnchor ARGS2(HTAnchor*, anchor, HTRequest *, request)
1.1 timbl 863: {
1.59 frystyk 864: if (!anchor) return HT_ERROR; /* No link */
1.1 timbl 865:
1.15 timbl 866: request->anchor = HTAnchor_parent(anchor);
1.59 frystyk 867: request->childAnchor = ((HTAnchor *) request->anchor == anchor) ?
868: NULL : (HTChildAnchor*) anchor;
869: return HTLoadDocument(request, NO);
870: }
1.52 frystyk 871:
872:
873: /* Load if necessary, and select an anchor
874: ** --------------------------------------
875: **
876: ** This function is almost identical to HTLoadAnchor, but it doesn't
877: ** clear the error stack so that the information in there is kept.
878: **
1.59 frystyk 879: ** On Entry,
1.52 frystyk 880: ** destination The child or parenet anchor to be loaded.
881: **
1.59 frystyk 882: ** On exit,
883: ** returns HT_WOULD_BLOCK An I/O operation would block
884: ** HT_ERROR Error has occured
885: ** HT_LOADED Success
886: ** HT_NO_DATA Success, but no document loaded.
887: ** (telnet sesssion started etc)
1.52 frystyk 888: */
889:
1.59 frystyk 890: PUBLIC int HTLoadAnchorRecursive ARGS2(HTAnchor*, anchor,
891: HTRequest *, request)
1.52 frystyk 892: {
1.59 frystyk 893: if (!anchor) return HT_ERROR; /* No link */
1.52 frystyk 894:
895: request->anchor = HTAnchor_parent(anchor);
1.59 frystyk 896: request->childAnchor = ((HTAnchor *) request->anchor == anchor) ?
897: NULL : (HTChildAnchor*) anchor;
1.52 frystyk 898:
1.59 frystyk 899: return HTLoadDocument(request, YES);
900: }
1.1 timbl 901:
902:
903: /* Search
904: ** ------
905: ** Performs a keyword search on word given by the user. Adds the keyword to
906: ** the end of the current address and attempts to open the new address.
907: **
908: ** On Entry,
909: ** *keywords space-separated keyword list or similar search list
1.2 timbl 910: ** here is anchor search is to be done on.
1.59 frystyk 911: **
912: ** On exit,
913: ** returns HT_WOULD_BLOCK An I/O operation would block
914: ** HT_ERROR Error has occured
915: ** HT_LOADED Success
916: ** HT_NO_DATA Success, but no document loaded.
917: ** (telnet sesssion started etc)
1.1 timbl 918: */
919:
1.56 frystyk 920: PRIVATE char hex ARGS1(int, i)
1.2 timbl 921: {
1.13 timbl 922: char * hexchars = "0123456789ABCDEF";
923: return hexchars[i];
1.2 timbl 924: }
1.1 timbl 925:
1.59 frystyk 926: PUBLIC int HTSearch ARGS3(CONST char *, keywords,
927: HTParentAnchor *, here,
928: HTRequest *, request)
1.1 timbl 929: {
1.2 timbl 930:
931: #define acceptable \
932: "1234567890abcdefghijlkmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-_"
933:
934: char *q, *u;
935: CONST char * p, *s, *e; /* Pointers into keywords */
936: char * address = HTAnchor_address((HTAnchor*)here);
1.1 timbl 937: BOOL result;
1.56 frystyk 938: char * escaped = (char *) malloc(strlen(keywords)*3+1);
1.2 timbl 939:
1.29 frystyk 940: /* static CONST BOOL isAcceptable[96] = */
941: /* static AND const is not good for a gnu compiler! Frystyk 25/02-94 */
1.30 luotonen 942: static BOOL isAcceptable[96] =
1.2 timbl 943: /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
944: { 0,0,0,0,0,0,0,0,0,0,1,0,0,1,1,0, /* 2x !"#$%&'()*+,-./ */
945: 1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0, /* 3x 0123456789:;<=>? */
946: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 4x @ABCDEFGHIJKLMNO */
947: 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1, /* 5X PQRSTUVWXYZ[\]^_ */
948: 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 6x `abcdefghijklmno */
949: 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0 }; /* 7X pqrstuvwxyz{\}~ DEL */
950:
951: if (escaped == NULL) outofmem(__FILE__, "HTSearch");
952:
1.29 frystyk 953: /* Convert spaces to + and hex escape unacceptable characters */
1.2 timbl 954:
1.29 frystyk 955: for(s=keywords; *s && WHITE(*s); s++); /*scan */ /* Skip white space */
956: for(e = s + strlen(s); e>s && WHITE(*(e-1)) ; e--); /* Skip trailers */
957: for(q=escaped, p=s; p<e; p++) { /* scan stripped field */
1.2 timbl 958: int c = (int)TOASCII(*p);
959: if (WHITE(*p)) {
960: *q++ = '+';
1.29 frystyk 961: } else if (c>=32 && c<=127 && isAcceptable[c-32] != 0) {
1.13 timbl 962: *q++ = *p; /* 930706 TBL for MVS bug */
1.2 timbl 963: } else {
964: *q++ = '%';
965: *q++ = hex(c / 16);
966: *q++ = hex(c % 16);
967: }
968: } /* Loop over string */
1.1 timbl 969:
1.2 timbl 970: *q=0;
971: /* terminate escaped sctring */
972: u=strchr(address, '?'); /* Find old search string */
973: if (u) *u = 0; /* Chop old search off */
1.1 timbl 974:
975: StrAllocCat(address, "?");
1.2 timbl 976: StrAllocCat(address, escaped);
977: free(escaped);
1.15 timbl 978: result = HTLoadRelative(address, here, request);
1.1 timbl 979: free(address);
1.2 timbl 980:
1.1 timbl 981: return result;
1.2 timbl 982: }
983:
984:
985: /* Search Given Indexname
986: ** ------
987: ** Performs a keyword search on word given by the user. Adds the keyword to
988: ** the end of the current address and attempts to open the new address.
989: **
1.59 frystyk 990: ** On Entry,
1.2 timbl 991: ** *keywords space-separated keyword list or similar search list
992: ** *addres is name of object search is to be done on.
1.59 frystyk 993: ** On exit,
994: ** returns HT_WOULD_BLOCK An I/O operation would block
995: ** HT_ERROR Error has occured
996: ** HT_LOADED Success
997: ** HT_NO_DATA Success, but no document loaded.
998: ** (telnet sesssion started etc)
1.2 timbl 999: */
1000:
1.59 frystyk 1001: PUBLIC int HTSearchAbsolute ARGS3(CONST char *, keywords,
1002: CONST char *, indexname,
1003: HTRequest *, request)
1.2 timbl 1004: {
1005: HTParentAnchor * anchor =
1006: (HTParentAnchor*) HTAnchor_findAddress(indexname);
1.15 timbl 1007: return HTSearch(keywords, anchor, request);
1.57 howcome 1008: }
1009:
1010:
1011: /*
1012: ** Find Related Name
1013: **
1014: ** Creates a string that can be used as a related name when
1015: ** calling HTParse initially.
1016: **
1017: ** The code for this routine originates from the Linemode
1018: ** browser and was moved here by howcome@dxcern.cern.ch
1019: ** in order for all clients to take advantage.
1020: **
1.59 frystyk 1021: ** The string returned must be freed by the caller
1.57 howcome 1022: */
1023: PUBLIC char * HTFindRelatedName NOARGS
1024: {
1.59 frystyk 1025: char* default_default = NULL; /* Parse home relative to this */
1026: CONST char *host = HTGetHostName();
1.57 howcome 1027: StrAllocCopy(default_default, "file://");
1.59 frystyk 1028: if (host)
1029: StrAllocCat(default_default, host);
1030: else
1031: StrAllocCat(default_default, "localhost");
1032: {
1033: char wd[HT_MAX_PATH+1];
1.57 howcome 1034:
1.59 frystyk 1035: #ifdef NO_GETWD
1036: #ifdef HAS_GETCWD /* System V variant SIGN CHANGED TBL 921006 !! */
1037: char *result = (char *) getcwd(wd, sizeof(wd));
1038: #else
1039: char *result = NULL;
1040: HTAlert("This platform does not support neither getwd nor getcwd\n");
1.57 howcome 1041: #endif
1.59 frystyk 1042: #else
1043: char *result = (char *) getwd(wd);
1044: #endif
1045: *(wd+HT_MAX_PATH) = '\0';
1.57 howcome 1046: if (result) {
1047: #ifdef VMS
1048: /* convert directory name to Unix-style syntax */
1049: char * disk = strchr (wd, ':');
1050: char * dir = strchr (wd, '[');
1051: if (disk) {
1052: *disk = '\0';
1053: StrAllocCat (default_default, "/"); /* needs delimiter */
1054: StrAllocCat (default_default, wd);
1055: }
1056: if (dir) {
1057: char *p;
1058: *dir = '/'; /* Convert leading '[' */
1059: for (p = dir ; *p != ']'; ++p)
1060: if (*p == '.') *p = '/';
1061: *p = '\0'; /* Cut on final ']' */
1062: StrAllocCat (default_default, dir);
1063: }
1064: #else /* not VMS */
1065: StrAllocCat (default_default, wd);
1.59 frystyk 1066: #endif /* not VMS */
1.57 howcome 1067: }
1.59 frystyk 1068: }
1.57 howcome 1069: StrAllocCat(default_default, "/default.html");
1070: return default_default;
1.2 timbl 1071: }
1072:
1073:
1074: /* Generate the anchor for the home page
1075: ** -------------------------------------
1076: **
1077: ** As it involves file access, this should only be done once
1078: ** when the program first runs.
1.10 timbl 1079: ** This is a default algorithm -- browser don't HAVE to use this.
1080: ** But consistency betwen browsers is STRONGLY recommended!
1.2 timbl 1081: **
1.10 timbl 1082: ** Priority order is:
1083: **
1084: ** 1 WWW_HOME environment variable (logical name, etc)
1085: ** 2 ~/WWW/default.html
1086: ** 3 /usr/local/bin/default.html
1087: ** 4 http://info.cern.ch/default.html
1088: **
1.2 timbl 1089: */
1090: PUBLIC HTParentAnchor * HTHomeAnchor NOARGS
1091: {
1.12 timbl 1092: char * my_home_document = NULL;
1093: char * home = (char *)getenv(LOGICAL_DEFAULT);
1.2 timbl 1094: char * ref;
1095: HTParentAnchor * anchor;
1.1 timbl 1096:
1.12 timbl 1097: if (home) {
1098: StrAllocCopy(my_home_document, home);
1099:
1100: /* Someone telnets in, they get a special home.
1101: */
1102: } else if (HTClientHost) { /* Telnet server */
1103: FILE * fp = fopen(REMOTE_POINTER, "r");
1104: char * status;
1105: if (fp) {
1.59 frystyk 1106: my_home_document = (char*) malloc(HT_MAX_PATH);
1107: status = fgets(my_home_document, HT_MAX_PATH, fp);
1.12 timbl 1108: if (!status) {
1109: free(my_home_document);
1110: my_home_document = NULL;
1111: }
1112: fclose(fp);
1113: }
1114: if (!my_home_document) StrAllocCopy(my_home_document, REMOTE_ADDRESS);
1115: }
1116:
1117:
1118:
1.2 timbl 1119: #ifdef unix
1.12 timbl 1120:
1.10 timbl 1121: if (!my_home_document) {
1122: FILE * fp = NULL;
1123: CONST char * home = (CONST char*)getenv("HOME");
1124: if (home) {
1125: my_home_document = (char *)malloc(
1126: strlen(home)+1+ strlen(PERSONAL_DEFAULT)+1);
1127: if (my_home_document == NULL) outofmem(__FILE__, "HTLocalName");
1128: sprintf(my_home_document, "%s/%s", home, PERSONAL_DEFAULT);
1129: fp = fopen(my_home_document, "r");
1130: }
1131:
1132: if (!fp) {
1133: StrAllocCopy(my_home_document, LOCAL_DEFAULT_FILE);
1134: fp = fopen(my_home_document, "r");
1135: }
1.2 timbl 1136: if (fp) {
1137: fclose(fp);
1138: } else {
1139: if (TRACE) fprintf(stderr,
1.10 timbl 1140: "HTBrowse: No local home document ~/%s or %s\n",
1141: PERSONAL_DEFAULT, LOCAL_DEFAULT_FILE);
1.11 timbl 1142: free(my_home_document);
1143: my_home_document = NULL;
1.2 timbl 1144: }
1145: }
1146: #endif
1.10 timbl 1147: ref = HTParse( my_home_document ? my_home_document :
1148: HTClientHost ? REMOTE_ADDRESS
1149: : LAST_RESORT,
1150: "file:",
1.2 timbl 1151: PARSE_ACCESS|PARSE_HOST|PARSE_PATH|PARSE_PUNCTUATION);
1.10 timbl 1152: if (my_home_document) {
1.2 timbl 1153: if (TRACE) fprintf(stderr,
1154: "HTAccess: Using custom home page %s i.e. address %s\n",
1.10 timbl 1155: my_home_document, ref);
1156: free(my_home_document);
1.2 timbl 1157: }
1158: anchor = (HTParentAnchor*) HTAnchor_findAddress(ref);
1159: free(ref);
1160: return anchor;
1.1 timbl 1161: }
1.26 frystyk 1162:
1163:
1164: /* Bind an Anchor to the request structure
1165: ** ---------------------------------------
1166: **
1167: ** On Entry,
1168: ** anchor The child or parenet anchor to be binded
1169: ** request The request sturcture
1170: ** On Exit,
1171: ** returns YES Success
1172: ** NO Failure
1173: **
1174: ** Note: Actually the same as HTLoadAnchor() but DOES NOT do the loading
1175: ** Henrik Frystyk 17/02-94
1176: */
1177:
1178: PUBLIC BOOL HTBindAnchor ARGS2(HTAnchor*, anchor, HTRequest *, request)
1179: {
1180: if (!anchor) return NO; /* No link */
1181:
1182: request->anchor = HTAnchor_parent(anchor);
1183: request->childAnchor = ((HTAnchor*)request->anchor == anchor) ? NULL
1184: : (HTChildAnchor*) anchor;
1185:
1.29 frystyk 1186: return YES;
1.26 frystyk 1187: } /* HTBindAnchor */
1.59 frystyk 1188:
1.26 frystyk 1189:
Webmaster