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