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