Annotation of libwww/Library/src/HTGopher.c, revision 2.60
2.32 frystyk 1: /* HTGopher.c
2: ** GOPHER ACCESS
3: **
2.37 frystyk 4: ** (c) COPYRIGHT MIT 1995.
2.32 frystyk 5: ** Please first read the full copyright statement in the file COPYRIGH.
1.1 timbl 6: **
7: ** History:
8: ** 26 Sep 90 Adapted from other accesses (News, HTTP) TBL
9: ** 29 Nov 91 Downgraded to C, for portable implementation.
2.17 frystyk 10: ** 28 Apr 94 target no more global and icons implemented
2.39 frystyk 11: ** HF, frystyk@w3.org
2.19 luotonen 12: ** 2 May 94 Fixed possible security hole when the URL contains
13: ** a newline, that could cause multiple commands to be
14: ** sent to a Gopher server. AL, luotonen@www.cern.ch
2.20 frystyk 15: ** 12 May 94 Checked and made ready for multi-threads, Frystyk
2.27 duns 16: ** 8 Jul 94 FM Insulate free() from _free structure element.
2.42 frystyk 17: ** Sep 95 HFN Made non-blocking and state stream oriented
1.1 timbl 18: */
19:
2.20 frystyk 20: /* Library include files */
2.57 frystyk 21: #include "sysdep.h"
2.59 frystyk 22: #include "WWWUtil.h"
23: #include "WWWCore.h"
24: #include "WWWHTML.h"
25: #include "WWWDir.h"
2.60 ! frystyk 26: #include "WWWTrans.h"
2.43 frystyk 27: #include "HTNetMan.h"
2.20 frystyk 28: #include "HTGopher.h" /* Implemented here */
29:
30: /* Macros and other defines */
31: #ifndef GOPHER_PORT
32: #define GOPHER_PORT 70 /* See protocol spec */
33: #endif
1.2 timbl 34:
2.20 frystyk 35: /* Hypertext object building machinery */
2.17 frystyk 36: #define PUTC(c) (*target->isa->put_character)(target, c)
37: #define PUTS(s) (*target->isa->put_string)(target, s)
38: #define START(e) (*target->isa->start_element)(target, e, 0, 0)
39: #define END(e) (*target->isa->end_element)(target, e)
2.20 frystyk 40:
41: /* Type definitions and global variables etc. local to this module */
42: typedef enum _HTGopherType {
2.42 frystyk 43: GT_TEXT = '0',
44: GT_MENU = '1',
45: GT_CSO = '2',
46: GT_ERROR = '3',
47: GT_MACBINHEX = '4',
48: GT_PCBINHEX = '5',
49: GT_UUENCODED = '6',
50: GT_INDEX = '7',
51: GT_TELNET = '8',
52: GT_BINARY = '9',
53: GT_GIF = 'g',
54: GT_HTML = 'h', /* HTML */
55: GT_INFO = 'i',
56: GT_SOUND = 's',
57: GT_WWW = 'w', /* W3 address */
58: GT_IMAGE = 'I',
59: GT_TN3270 = 'T',
60: GT_DUPLICATE = '+',
61: GT_PLUS_IMAGE = ':', /* Addition from Gopher Plus */
62: GT_PLUS_MOVIE = ';',
63: GT_PLUS_SOUND = '<',
64: GT_EOF = '.'
2.20 frystyk 65: } HTGopherType;
66:
2.42 frystyk 67: /* Final states have negative value */
68: typedef enum _GopherState {
69: GOPHER_ERROR = -3,
70: GOPHER_NO_DATA = -2,
71: GOPHER_GOT_DATA = -1,
72: GOPHER_BEGIN = 0,
73: GOPHER_NEED_CONNECTION,
74: GOPHER_NEED_REQUEST,
75: GOPHER_NEED_RESPONSE
76: } GopherState;
1.2 timbl 77:
2.42 frystyk 78: /* This is the context structure for the this module */
2.20 frystyk 79: typedef struct _gopher_info {
2.30 frystyk 80: HTGopherType type; /* Gopher item type */
2.42 frystyk 81: GopherState state;
82: char * cmd;
2.20 frystyk 83: } gopher_info;
1.1 timbl 84:
2.42 frystyk 85: #define MAX_GOPHER_LINE 256
86:
87: struct _HTStructured {
2.57 frystyk 88: const HTStructuredClass * isa;
2.42 frystyk 89: };
90:
91: struct _HTStream {
2.57 frystyk 92: const HTStreamClass * isa;
2.42 frystyk 93: HTStructured * target;
94: HTRequest * request;
2.58 frystyk 95: HTEOLState state;
2.42 frystyk 96: char * url;
97: BOOL pre; /* Preformatted mode? */
98: BOOL junk; /* For too long lines */
99: BOOL CSO;
100: char cso_rec[10]; /* CSO record number */
101: char buffer[MAX_GOPHER_LINE+1];
102: int buflen;
103: };
104:
2.59 frystyk 105: struct _HTInputStream {
106: const HTInputStreamClass * isa;
107: };
108:
2.45 frystyk 109: PRIVATE HTDirShow dir_show = HT_DS_ICON;
110:
2.20 frystyk 111: /* ------------------------------------------------------------------------- */
1.1 timbl 112:
2.42 frystyk 113: /* GopherIcon
114: ** ----------
2.20 frystyk 115: ** This function finds an appopriate icon for the item in the gopher
116: ** list. Actually it is only a shell build upon HTGetIcon().
2.17 frystyk 117: */
2.42 frystyk 118: PRIVATE HTIconNode *GopherIcon (HTGopherType type)
2.17 frystyk 119: {
2.36 frystyk 120: HTFormat content_type = NULL;
121: HTEncoding content_encoding = NULL;
2.45 frystyk 122: HTFileMode mode = HT_IS_FILE;
2.42 frystyk 123: switch(type) {
2.45 frystyk 124: case GT_MENU:
125: mode = HT_IS_DIR;
2.42 frystyk 126: case GT_TEXT:
2.17 frystyk 127: content_type = HTAtom_for("text/void");
128: break;
2.42 frystyk 129: case GT_IMAGE:
130: case GT_PLUS_IMAGE:
131: case GT_GIF:
2.17 frystyk 132: content_type = HTAtom_for("image/void");
133: break;
2.42 frystyk 134: case GT_WWW:
135: case GT_HTML:
2.17 frystyk 136: content_type = HTAtom_for("text/void");
137: break;
2.42 frystyk 138: case GT_SOUND:
139: case GT_PLUS_SOUND:
2.17 frystyk 140: content_type = HTAtom_for("audio/void");
141: break;
2.42 frystyk 142: case GT_PLUS_MOVIE:
2.20 frystyk 143: content_type = HTAtom_for("video/void");
2.17 frystyk 144: break;
2.42 frystyk 145: case GT_INDEX:
2.17 frystyk 146: content_type = HTAtom_for("application/x-gopher-index");
147: break;
2.42 frystyk 148: case GT_CSO:
2.17 frystyk 149: content_type = HTAtom_for("application/x-gopher-cso");
150: break;
2.42 frystyk 151: case GT_TELNET:
2.17 frystyk 152: content_type = HTAtom_for("application/x-gopher-telnet");
153: break;
2.42 frystyk 154: case GT_TN3270:
2.17 frystyk 155: content_type = HTAtom_for("application/x-gopher-tn3270");
156: break;
2.42 frystyk 157: case GT_DUPLICATE:
2.17 frystyk 158: content_type = HTAtom_for("application/x-gopher-duplicate");
159: break;
2.42 frystyk 160: case GT_ERROR:
2.17 frystyk 161: content_type = HTAtom_for("www/unknown");
162: break;
2.42 frystyk 163: case GT_BINARY:
2.36 frystyk 164: content_type = WWW_BINARY;
165: break;
2.17 frystyk 166: default:
167: content_type = HTAtom_for("www/unknown");
168: break;
169: }
2.45 frystyk 170: return HTGetIcon(mode, content_type, content_encoding);
2.17 frystyk 171: }
172:
2.42 frystyk 173: /* ------------------------------------------------------------------------- */
174: /* STREAMS */
175: /* ------------------------------------------------------------------------- */
2.17 frystyk 176:
2.42 frystyk 177: /* GopherTitle
178: ** -----------
179: ** Create the top part of the page
1.1 timbl 180: */
2.42 frystyk 181: PRIVATE BOOL GopherTitle (HTStream *me)
182: {
183: HTStructured *target = me->target;
184: char *str = NULL;
185: StrAllocCopy(str, me->CSO ? "CSO Search " : "GopherMenu");
2.28 frystyk 186:
2.42 frystyk 187: START(HTML_HTML);
188: START(HTML_HEAD);
189: START(HTML_TITLE);
190: if (me->CSO) {
191: char *keyword = strchr(me->url, '?');
192: if (keyword) {
193: StrAllocCat(str, "for ");
194: StrAllocCat(str, ++keyword);
195: }
196: }
197: PUTS(str);
198: END(HTML_TITLE);
199: END(HTML_HEAD);
2.26 frystyk 200:
2.42 frystyk 201: START(HTML_BODY);
202: START(HTML_H1);
203: PUTS(str);
204: END(HTML_H1);
2.55 frystyk 205: HT_FREE(str);
2.42 frystyk 206: return YES;
207: }
1.1 timbl 208:
2.42 frystyk 209: /* GopherBottom
210: ** ------------
211: ** Create the bottom part of the page
212: */
213: PRIVATE BOOL GopherBottom (HTStream *me)
214: {
215: HTStructured *target = me->target;
216: if (me->pre)
217: END(HTML_PRE);
218: END(HTML_BODY);
219: END(HTML_HTML);
220: return YES;
221: }
2.17 frystyk 222:
2.42 frystyk 223: /* GopherMenuLine
224: ** --------------
225: ** Parses a Gopher Menu Line
226: ** Return YES if more data else NO
227: */
228: PRIVATE BOOL GopherMenuLine (HTStream *me, char *line)
229: {
230: HTStructured *target = me->target;
231: HTGopherType gtype = (HTGopherType) *line++;
232: if (PROT_TRACE)
2.56 eric 233: HTTrace("HTGopher.... Menu line: `%s\'\n", line);
2.42 frystyk 234: if (gtype == GT_INFO) {
235: char *stop = strchr(line, '\t');
236: if (stop) *stop = '\0';
237: PUTS(line);
238: } else if (gtype == GT_ERROR) {
239: char *stop = strchr(line, '\t');
240: if (stop) *stop = '\0';
241: PUTS(line);
242: } else if ((strstr(line, "error.host") || strstr(line, "errorhost"))) {
243: char *stop = strchr(line, '\t'); /* Chop off error.host */
244: if (stop) *stop = '\0';
245: PUTS(line);
246: } else if (gtype == GT_EOF) {
247: return NO;
248: } else { /* Parse normal gopher menu line */
249: char *name = line; /* Get link information */
250: char *selector = strchr(name, '\t');
251: char *host = NULL;
252: char *port = NULL;
253: if (selector) {
254: *selector++ = '\0';
255: if ((host = strchr(selector, '\t'))) {
256: *host++ = '\0';
257: if ((port = strchr(host, '\t'))) {
258: char *junk;
259: *port = ':'; /* delimit host a la W3 */
260: if ((junk = strchr(port, '\t')) != NULL)
261: *junk = '\0'; /* Chop port */
262: if (*(port+1) == '0' && !*(port+2))
263: *port = '\0';
2.21 frystyk 264: }
2.42 frystyk 265: }
266: }
267: if (!me->pre) { /* For now we use preformatted listing */
268: START(HTML_PRE);
269: me->pre = YES;
270: }
2.45 frystyk 271: if (dir_show & HT_DS_ICON) { /* Put out the icon */
2.42 frystyk 272: HTIconNode *icon = GopherIcon(gtype);
2.52 frystyk 273: if (icon) {
2.42 frystyk 274: HTMLPutImg(target, icon->icon_url,
275: HTIcon_alt_string(icon->icon_alt, YES), NULL);
276: PUTC(' ');
277: }
278: }
279: if (gtype == GT_WWW) { /* Gopher pointer to W3 */
280: char *escaped = NULL;
281: escaped = HTEscape(selector, URL_PATH);
282: HTStartAnchor(target, NULL, escaped);
283: PUTS(name);
284: END(HTML_A);
2.55 frystyk 285: HT_FREE(escaped);
2.42 frystyk 286: } else if (port) { /* Other types need port */
287: char *escaped = NULL;
288: char *address = NULL;
289: int addr_len;
290:
291: /* Calculate the length of the WWW-address */
292: if (selector && *selector) {
293: escaped = HTEscape(selector, URL_PATH);
294: addr_len = 15 + strlen(escaped) + strlen(host) + 1;
295: } else {
296: addr_len = 15 + strlen(host) + 1;
297: }
2.55 frystyk 298: if ((address = (char *) HT_MALLOC(addr_len)) == NULL)
299: HT_OUTOFMEM("GopherMenuLine");
2.42 frystyk 300: *address = '\0';
301:
302: if (gtype == GT_TELNET) {
303: if (escaped)
304: sprintf(address, "telnet://%s@%s/", escaped, host);
305: else
306: sprintf(address, "telnet://%s/", host);
307: } else if (gtype == GT_TN3270) {
308: if (escaped)
309: sprintf(address, "tn3270://%s@%s/", escaped, host);
310: else
311: sprintf(address, "tn3270://%s/", host);
312: } else {
313: if (escaped)
314: sprintf(address, "//%s/%c%s", host, gtype, escaped);
315: else
316: sprintf(address, "//%s/%c", host, gtype);
317: }
318:
319: HTStartAnchor(target, NULL, address);
320: PUTS(name);
321: END(HTML_A);
2.55 frystyk 322: HT_FREE(address);
323: HT_FREE(escaped);
2.42 frystyk 324: PUTC('\n');
325: } else { /* If parse error */
326: if (PROT_TRACE)
2.56 eric 327: HTTrace("HTGopher.... Bad menu item, `%s\'\n", line);
2.42 frystyk 328: }
329: }
330: return YES;
331: }
2.21 frystyk 332:
2.42 frystyk 333: /* GopherCSOLine
334: ** --------------
335: ** Parses a Gopher Menu Line
336: ** Return YES if more data else NO
337: */
338: PRIVATE BOOL GopherCSOLine (HTStream *me, char *line)
339: {
340: HTStructured *target = me->target;
341: if (*line == '1') { /* Information line */
342: char *start = strchr(line, ':');
343: start = start ? ++start : line;
344: PUTS(start);
345: } else if (*line == '2') { /* Transfer complete */
346: return NO;
347: } else if (*line == '5') { /* Error */
348: char *start = strchr(line, ':');
349: start = start ? ++start : line;
350: PUTS(start);
351: } else if (*line == '-') { /* data */
352: /* data lines look like '-200:code:field:value'
353: * where code is the search result number and can be
354: * multiple digits (infinte?)
355: * find the second colon and check the digit to the
356: * left of it to see if they are diferent
357: * if they are then a different person is starting.
358: */
359: char *code;
360: char *field;
361: if ((code = strchr(line, ':')) && (field = strchr(++code, ':'))) {
362: BOOL newrec = YES;
363: *field++ = '\0';
364: if (!*me->cso_rec) { /* Header of first record */
365: START(HTML_DL);
366: } else if (strcmp(me->cso_rec, code)) { /* Another new record */
367: START(HTML_B);
368: } else
369: newrec = NO;
370: START(HTML_DT);
371:
372: /* I'm not sure whether the name field comes in any
373: * special order or if its even required in a
374: * record, so for now the first line is the header
375: * no matter what it is (it's almost always the
376: * alias)
377: */
378: {
379: char *value = strchr(field, ':');
380: if (!value)
381: value = "Empty value";
382: else
383: *value++ = '\0';
384: {
385: char *strip = HTStrip(field);
386: PUTS(strip);
387: START(HTML_DD);
388: strip = HTStrip(value);
389: if (newrec) {
390: PUTS(strip);
391: END(HTML_B);
2.20 frystyk 392: } else
2.42 frystyk 393: PUTS(strip);
394: }
2.20 frystyk 395:
2.42 frystyk 396: /* Save the code for comparison on the next pass */
397: strcpy(me->cso_rec, code);
2.20 frystyk 398: }
2.42 frystyk 399: }
400: } else { /* Unknown line */
401: char *start = strchr(line, ':');
402: start = start ? ++start : line;
403: PUTS(start);
2.20 frystyk 404: }
2.42 frystyk 405: return YES;
406: }
1.2 timbl 407:
2.42 frystyk 408: /*
409: ** Searches for Gopher line until buffer fills up or a CRLF or LF is found
410: */
2.57 frystyk 411: PRIVATE int GopherMenu_put_block (HTStream * me, const char * b, int l)
2.42 frystyk 412: {
413: while (l-- > 0) {
414: if (me->state == EOL_FCR) {
415: if (*b == LF && me->buflen) {
416: if (!me->junk) {
417: BOOL cont;
418: *(me->buffer+me->buflen) = '\0';
419: cont = me->CSO ? GopherCSOLine(me, me->buffer) :
420: GopherMenuLine(me, me->buffer);
421: if (cont == NO) return HT_LOADED;
422: } else
423: me->junk = NO; /* back to normal */
2.20 frystyk 424: }
2.45 frystyk 425: me->buflen = 0;
426: me->state = EOL_BEGIN;
2.42 frystyk 427: } else if (*b == CR) {
428: me->state = EOL_FCR;
429: } else if (*b == LF && me->buflen) {
430: if (!me->junk) {
431: BOOL cont;
432: *(me->buffer+me->buflen) = '\0';
433: cont = me->CSO ? GopherCSOLine(me, me->buffer) :
434: GopherMenuLine(me, me->buffer);
435: if (cont == NO) return HT_LOADED;
436: } else
437: me->junk = NO; /* back to normal */
438: me->buflen = 0;
439: me->state = EOL_BEGIN;
440: } else {
441: *(me->buffer+me->buflen++) = *b;
442: if (me->buflen >= MAX_GOPHER_LINE) {
443: if (PROT_TRACE)
2.56 eric 444: HTTrace("Gopher...... Line too long - ignored\n");
2.42 frystyk 445: me->buflen = 0;
446: me->junk = YES;
2.20 frystyk 447: }
448: }
2.42 frystyk 449: b++;
2.17 frystyk 450: }
2.42 frystyk 451: return HT_OK;
452: }
2.17 frystyk 453:
2.57 frystyk 454: PRIVATE int GopherMenu_put_string (HTStream * me, const char* s)
2.42 frystyk 455: {
456: return GopherMenu_put_block(me, s, (int) strlen(s));
1.1 timbl 457: }
2.11 timbl 458:
2.50 frystyk 459: PRIVATE int GopherMenu_put_character (HTStream * me, char c)
2.42 frystyk 460: {
461: return GopherMenu_put_block(me, &c, 1);
462: }
2.11 timbl 463:
2.50 frystyk 464: PRIVATE int GopherMenu_flush (HTStream * me)
2.42 frystyk 465: {
466: return (*me->target->isa->flush)(me->target);
467: }
468:
2.50 frystyk 469: PRIVATE int GopherMenu_free (HTStream * me)
2.42 frystyk 470: {
471: int status = HT_OK;
472: GopherBottom(me);
473: if ((status = (*me->target->isa->_free)(me->target)) == HT_WOULD_BLOCK)
474: return HT_WOULD_BLOCK;
2.55 frystyk 475: HT_FREE(me);
2.42 frystyk 476: return HT_OK;
477: }
478:
2.50 frystyk 479: PRIVATE int GopherMenu_abort (HTStream * me, HTList * e)
2.42 frystyk 480: {
481: (*me->target->isa->abort)(me->target, e);
2.55 frystyk 482: HT_FREE(me);
2.42 frystyk 483: if (PROT_TRACE)
2.56 eric 484: HTTrace("Gopher...... ABORTING...\n");
2.42 frystyk 485: return HT_ERROR;
486: }
487:
488: /* Goper Menu Stream
489: ** -----------------
2.20 frystyk 490: */
2.57 frystyk 491: PRIVATE const HTStreamClass GopherMenuClass =
2.42 frystyk 492: {
493: "GopherMenu",
494: GopherMenu_flush,
495: GopherMenu_free,
496: GopherMenu_abort,
497: GopherMenu_put_character,
498: GopherMenu_put_string,
499: GopherMenu_put_block
500: };
2.26 frystyk 501:
2.42 frystyk 502: /*
503: ** Stream for creating a HTML object out of a Gopher Menu object
504: */
505: PRIVATE HTStream * GopherMenu_new (HTRequest * request, char *url, BOOL CSO)
506: {
2.55 frystyk 507: HTStream * me;
508: if ((me = (HTStream *) HT_CALLOC(1, sizeof(HTStream))) == NULL)
509: HT_OUTOFMEM("GopherMenu_new");
2.42 frystyk 510: me->isa = &GopherMenuClass;
511: me->target = HTMLGenerator(request, NULL, WWW_HTML,
2.60 ! frystyk 512: HTRequest_outputFormat(request),
! 513: HTRequest_outputStream(request));
! 514: HTAnchor_setFormat(HTRequest_anchor(request), WWW_HTML);
2.42 frystyk 515: me->request = request;
516: me->state = EOL_BEGIN;
517: me->url = url;
518: me->CSO = CSO;
519: GopherTitle(me);
520: return me;
521: }
2.20 frystyk 522:
2.42 frystyk 523: /* ------------------------------------------------------------------------- */
524: /* GOPHER STATE MACHINE */
525: /* ------------------------------------------------------------------------- */
2.7 secret 526:
2.42 frystyk 527: /* GopherCleanup
528: ** -------------
529: ** This function closes the connection and frees memory.
530: ** Returns YES if OK, else NO
531: */
2.60 ! frystyk 532: PRIVATE BOOL GopherCleanup (HTRequest * request, int status)
2.42 frystyk 533: {
2.60 ! frystyk 534: HTNet * net = HTRequest_net(request);
! 535: gopher_info * gopher = (gopher_info *) HTNet_context(net);
! 536: HTStream * input = HTRequest_inputStream(request);
2.20 frystyk 537:
2.42 frystyk 538: /* Free stream with data TO network */
2.60 ! frystyk 539: if (input) {
2.42 frystyk 540: if (status == HT_INTERRUPTED)
2.60 ! frystyk 541: (*input->isa->abort)(input, NULL);
2.42 frystyk 542: else
2.60 ! frystyk 543: (*input->isa->_free)(input);
2.20 frystyk 544: }
545:
2.42 frystyk 546: /* Remove the request object and our own context structure for gopher */
547: HTNet_delete(net, status);
548: if (gopher) {
2.55 frystyk 549: HT_FREE(gopher->cmd);
550: HT_FREE(gopher);
2.34 frystyk 551: }
2.42 frystyk 552: return YES;
2.20 frystyk 553: }
2.7 secret 554:
1.1 timbl 555: /* Display a Gopher Index document
2.20 frystyk 556: ** -------------------------------
2.42 frystyk 557: ** We create a small HTML object as we have no network activity
2.20 frystyk 558: */
2.42 frystyk 559: PRIVATE void display_index (HTRequest * request)
1.1 timbl 560: {
2.60 ! frystyk 561: HTParentAnchor * anchor = HTRequest_anchor(request);
2.40 frystyk 562: HTStructured *target = HTMLGenerator(request, NULL, WWW_HTML,
2.60 ! frystyk 563: HTRequest_outputFormat(request),
! 564: HTRequest_outputStream(request));
! 565:
2.42 frystyk 566: /* Update anchor information */
2.60 ! frystyk 567: HTAnchor_setFormat(anchor, WWW_HTML);
! 568: HTAnchor_setTitle(anchor, "Searchable Gopher Index");
2.42 frystyk 569: /* @@@ We don't set Content-Length */
570:
2.34 frystyk 571: START(HTML_HTML);
572: START(HTML_HEAD);
573: START(HTML_TITLE);
574: PUTS("Searchable Gopher Index");
575: END(HTML_TITLE);
576: END(HTML_HEAD);
577: START(HTML_BODY);
578:
1.2 timbl 579: START(HTML_H1);
2.20 frystyk 580: PUTS("Searchable Gopher Index");
1.2 timbl 581: END(HTML_H1);
2.7 secret 582: START(HTML_ISINDEX);
2.34 frystyk 583: END(HTML_BODY);
584: END(HTML_HTML);
2.42 frystyk 585: (*target->isa->_free)(target);
2.7 secret 586: }
587:
588:
589: /* Display a CSO index document
590: ** -------------------------------
2.42 frystyk 591: ** We create a small HTML object as we have no network activity
2.7 secret 592: */
2.42 frystyk 593: PRIVATE void display_cso (HTRequest * request)
2.7 secret 594: {
2.60 ! frystyk 595: HTParentAnchor * anchor = HTRequest_anchor(request);
2.40 frystyk 596: HTStructured *target = HTMLGenerator(request, NULL, WWW_HTML,
2.60 ! frystyk 597: HTRequest_outputFormat(request),
! 598: HTRequest_outputStream(request));
! 599:
2.42 frystyk 600: /* Update anchor information */
2.60 ! frystyk 601: HTAnchor_setFormat(anchor, WWW_HTML);
! 602: HTAnchor_setTitle(anchor, "Searchable SCO Index");
2.42 frystyk 603: /* @@@ We don't set Content-Length */
604:
2.34 frystyk 605: START(HTML_HTML);
606: START(HTML_HEAD);
607: START(HTML_TITLE);
608: PUTS("Searchable Index of a CSO Name Server");
609: END(HTML_TITLE);
610: END(HTML_HEAD);
611: START(HTML_BODY);
612:
2.7 secret 613: START(HTML_H1);
2.20 frystyk 614: PUTS("Searchable Index of a CSO Name Server");
2.7 secret 615: END(HTML_H1);
2.34 frystyk 616: PUTS("A CSO Name Server usually provides directory information about people.");
2.7 secret 617: START(HTML_ISINDEX);
2.34 frystyk 618: END(HTML_BODY);
619: END(HTML_HTML);
2.42 frystyk 620: (*target->isa->_free)(target);
1.1 timbl 621: }
622:
2.42 frystyk 623: /* HTLoadGopher
624: ** ------------
2.24 frystyk 625: ** Given a hypertext address, this routine loads a gopher document
626: **
627: ** On entry,
2.42 frystyk 628: ** request This is the request structure
629: ** On Exit
630: ** returns HT_ERROR Error has occured in call back
631: ** HT_OK Call back was OK
1.1 timbl 632: */
2.42 frystyk 633: PUBLIC int HTLoadGopher (SOCKET soc, HTRequest * request, SockOps ops)
1.1 timbl 634: {
2.42 frystyk 635: int status = HT_ERROR;
2.60 ! frystyk 636: HTNet * net = HTRequest_net(request);
! 637: HTParentAnchor * anchor = HTRequest_anchor(request);
! 638: char * url = HTAnchor_physical(anchor);
! 639: gopher_info * gopher;
2.20 frystyk 640:
2.42 frystyk 641: /*
642: ** Initiate a new gopher structure and bind to request structure
643: ** This is actually state GOPHER_BEGIN, but it can't be in the state
644: ** machine as we need the structure first.
645: */
646: if (ops == FD_NONE) {
2.56 eric 647: if (PROT_TRACE) HTTrace("Gopher...... Looking for `%s\'\n",url);
2.55 frystyk 648: if ((gopher = (gopher_info *) HT_CALLOC(1, sizeof(gopher_info))) == NULL)
649: HT_OUTOFMEM("HTLoadGopher");
2.42 frystyk 650: gopher->type = GT_MENU;
651: gopher->state = GOPHER_BEGIN;
2.60 ! frystyk 652: HTNet_setContext(net, gopher);
2.44 frystyk 653: } else if (ops == FD_CLOSE) { /* Interrupted */
2.42 frystyk 654: GopherCleanup(request, HT_INTERRUPTED);
655: return HT_OK;
656: } else
2.60 ! frystyk 657: gopher = (gopher_info *) HTNet_context(net); /* Get existing copy */
2.42 frystyk 658:
659: /* Now jump into the machine. We know the state from the previous run */
660: while (1) {
661: switch (gopher->state) {
662:
663: case GOPHER_BEGIN: /* Get entity type, and selector string */
664: {
665: char *path = HTParse(url, "", PARSE_PATH);
666: char *selector = path;
667: char *query = NULL;
668: char *separator = NULL;
669: if (*selector) gopher->type = (HTGopherType) *selector++;
670: if (gopher->type == GT_INDEX) {
2.60 ! frystyk 671: HTAnchor_setIndex(anchor); /* Is index */
2.42 frystyk 672: query = strchr(selector, '?');
673:
674: /* Display local cover page only if no search requested */
675: if (!query || !*(query+1)) { /* No search required */
676: display_index(request);
677: gopher->state = GOPHER_GOT_DATA;
2.55 frystyk 678: HT_FREE(path);
2.42 frystyk 679: break;
680: } else {
681: *query++ = 0; /* Skip '?' */
682: separator = "\t";
683: }
684: } else if (gopher->type == GT_CSO) {
2.60 ! frystyk 685: HTAnchor_setIndex(anchor); /* Search is allowed */
2.42 frystyk 686: query = strchr(selector, '?'); /* Look for search string */
687:
688: /* Display local cover page only if no search requested */
689: if (!query || !*(query+1)) { /* No search required */
690: display_cso(request);
691: gopher->state = GOPHER_GOT_DATA;
2.55 frystyk 692: HT_FREE(path);
2.42 frystyk 693: break;
694: } else {
695: *query++ = 0; /* Skip '?' */
696: *selector = 0;
697: separator = "query ";
698: }
699: }
2.20 frystyk 700:
2.42 frystyk 701: /* Now generate the final command */
702: {
703: char crlf[3];
704: StrAllocCopy(gopher->cmd, selector);
705: if (query) {
706: char *p;
707: for (p=query; *p; p++) /* Remove plus signs 921006 */
708: if (*p == '+') *p = ' ';
709: StrAllocCat(gopher->cmd, separator);
710: StrAllocCat(gopher->cmd, query);
711: }
712: HTUnEscape(gopher->cmd);
713: HTCleanTelnetString(gopher->cmd); /* Prevent sec holes */
714: *crlf = CR; /* Telnet termination */
715: *(crlf+1) = LF;
716: *(crlf+2) = '\0';
717: StrAllocCat(gopher->cmd, crlf);
718: }
2.55 frystyk 719: HT_FREE(path);
2.42 frystyk 720: gopher->state = GOPHER_NEED_CONNECTION;
1.1 timbl 721: }
2.42 frystyk 722: break;
723:
724: case GOPHER_NEED_CONNECTION:
2.43 frystyk 725: status = HTDoConnect(net, url, GOPHER_PORT);
2.42 frystyk 726: if (status == HT_OK) {
2.59 frystyk 727: /*
728: ** Check the protocol class to see if we have connected to a
729: ** the right class of server, in this case HTTP.
730: */
731: {
732: HTHost * host = HTNet_host(net);
733: char * s_class = HTHost_class(host);
734: if (s_class && strcasecomp(s_class, "gopher")) {
735: HTRequest_addError(request, ERR_FATAL, NO, HTERR_CLASS,
736: NULL, 0, "HTLoadGopher");
737: gopher->state = GOPHER_ERROR;
738: break;
739: }
740: HTHost_setClass(host, "gopher");
741: }
2.42 frystyk 742:
2.59 frystyk 743: /*
744: ** Create the stream pipe FROM the channel to the application.
745: ** The target for the input stream pipe is set up using the
746: ** stream stack.
747: */
748: {
749: HTStream * target;
750: if (gopher->type == GT_MENU || gopher->type == GT_INDEX)
751: target = GopherMenu_new(request, url, NO);
752: else if (gopher->type == GT_CSO)
753: target = GopherMenu_new(request, url, YES);
754: else
755: target = HTStreamStack(WWW_UNKNOWN,
2.60 ! frystyk 756: HTRequest_outputFormat(request),
! 757: HTRequest_outputStream(request),
2.59 frystyk 758: request, NO);
759: HTNet_getInput(net, target, NULL, 0);
760: }
2.42 frystyk 761:
2.59 frystyk 762: /*
763: ** Create the stream pipe TO the channel from the application
764: ** and hook it up to the request object
765: */
766: {
767: HTOutputStream * output = HTNet_getOutput(net, NULL, 0);
768: HTRequest_setInputStream(request, (HTStream *) output);
769: }
2.42 frystyk 770: gopher->state = GOPHER_NEED_REQUEST;
2.59 frystyk 771:
2.42 frystyk 772: } else if (status == HT_WOULD_BLOCK)
773: return HT_OK;
774: else
775: gopher->state = GOPHER_ERROR;
776: break;
777:
778: case GOPHER_NEED_REQUEST:
2.56 eric 779: if (PROT_TRACE) HTTrace("Gopher Tx... `%s\'", gopher->cmd);
2.60 ! frystyk 780: {
! 781: HTStream * input = HTRequest_inputStream(request);
! 782: status = (*input->isa->put_block)
! 783: (input, gopher->cmd, strlen(gopher->cmd));
! 784: if (status == HT_WOULD_BLOCK)
! 785: return HT_OK;
! 786: else if (status == HT_ERROR)
! 787: gopher->state = GOPHER_ERROR;
! 788: else
! 789: gopher->state = GOPHER_NEED_RESPONSE;
! 790: }
2.42 frystyk 791: break;
1.1 timbl 792:
2.42 frystyk 793: case GOPHER_NEED_RESPONSE:
2.59 frystyk 794: status = (*net->input->isa->read)(net->input);
2.42 frystyk 795: if (status == HT_WOULD_BLOCK)
796: return HT_OK;
2.53 frystyk 797: else if (status == HT_LOADED || status == HT_CLOSED)
2.42 frystyk 798: gopher->state = GOPHER_GOT_DATA;
799: else
800: gopher->state = GOPHER_ERROR;
801: break;
1.2 timbl 802:
2.42 frystyk 803: case GOPHER_GOT_DATA:
804: GopherCleanup(request, HT_LOADED);
805: return HT_OK;
806: break;
807:
808: case GOPHER_NO_DATA:
809: GopherCleanup(request, HT_NO_DATA);
810: return HT_OK;
811: break;
812:
813: case GOPHER_ERROR:
814: GopherCleanup(request, HT_ERROR);
815: return HT_OK;
816: break;
2.25 frystyk 817: }
2.42 frystyk 818: } /* while(1) */
1.1 timbl 819: }
Webmaster