Annotation of libwww/Library/src/HTGopher.c, revision 2.25
1.1 timbl 1: /* GOPHER ACCESS HTGopher.c
2: ** =============
3: **
4: ** History:
5: ** 26 Sep 90 Adapted from other accesses (News, HTTP) TBL
6: ** 29 Nov 91 Downgraded to C, for portable implementation.
2.17 frystyk 7: ** 28 Apr 94 target no more global and icons implemented
8: ** HF, frystyk@dxcern.cern.ch
2.19 luotonen 9: ** 2 May 94 Fixed possible security hole when the URL contains
10: ** a newline, that could cause multiple commands to be
11: ** sent to a Gopher server. AL, luotonen@www.cern.ch
2.20 frystyk 12: ** 12 May 94 Checked and made ready for multi-threads, Frystyk
2.21 frystyk 13: **
14: ** NOTE:
15: ** When parsing a gopher menu, we can't use the default HTParseSocket
16: ** but we will hav eto take care of the stram ourselves :-(
17: **
1.1 timbl 18: */
19:
2.20 frystyk 20: /* Implementation dependent include files */
1.1 timbl 21: #include "tcp.h"
22:
2.20 frystyk 23: /* Library include files */
24: #include "HTParse.h"
25: #include "HTUtils.h"
26: #include "HTTCP.h"
2.17 frystyk 27: #include "HTIcons.h"
2.20 frystyk 28: #include "HTAccess.h"
1.1 timbl 29: #include "HTFormat.h"
2.20 frystyk 30: #include "HTError.h"
31: #include "HTFile.h"
1.2 timbl 32: #include "HTML.h"
2.20 frystyk 33: #include "HTMLPDTD.h"
34: #include "HTDirBrw.h"
35: #include "HTGopher.h" /* Implemented here */
36:
37: /* Macros and other defines */
38: #ifndef GOPHER_PORT
39: #define GOPHER_PORT 70 /* See protocol spec */
40: #endif
1.2 timbl 41:
2.20 frystyk 42: /* Hypertext object building machinery */
2.17 frystyk 43: #define PUTC(c) (*target->isa->put_character)(target, c)
44: #define PUTS(s) (*target->isa->put_string)(target, s)
45: #define START(e) (*target->isa->start_element)(target, e, 0, 0)
46: #define END(e) (*target->isa->end_element)(target, e)
47: #define FREE_TARGET (*target->isa->free)(target)
2.20 frystyk 48:
49: /* Type definitions and global variables etc. local to this module */
50: typedef enum _HTGopherType {
51: GOPHER_TEXT = '0',
52: GOPHER_MENU = '1',
53: GOPHER_CSO = '2',
54: GOPHER_ERROR = '3',
55: GOPHER_MACBINHEX = '4',
56: GOPHER_PCBINHEX = '5',
57: GOPHER_UUENCODED = '6',
58: GOPHER_INDEX = '7',
59: GOPHER_TELNET = '8',
60: GOPHER_BINARY = '9',
61: GOPHER_GIF = 'g',
62: GOPHER_HTML = 'h', /* HTML */
63: GOPHER_SOUND = 's',
64: GOPHER_WWW = 'w', /* W3 address */
65: GOPHER_IMAGE = 'I',
66: GOPHER_TN3270 = 'T',
67: GOPHER_DUPLICATE = '+',
68: GOPHER_PLUS_IMAGE = ':', /* Addition from Gopher Plus */
69: GOPHER_PLUS_MOVIE = ';',
70: GOPHER_PLUS_SOUND = '<'
71: } HTGopherType;
72:
1.2 timbl 73: struct _HTStructured {
74: CONST HTStructuredClass * isa;
75: /* ... */
76: };
77:
2.20 frystyk 78: /* ------------------------------------------------------------------------- */
79: /* TEMPORARY STUFF */
1.1 timbl 80:
2.20 frystyk 81: typedef struct _gopher_info {
82: int socket; /* Socket number for communication */
83: HTGopherType type; /* Gopher item type */
84: char * command; /* The whole command */
85: } gopher_info;
1.1 timbl 86:
2.20 frystyk 87: /* ------------------------------------------------------------------------- */
1.1 timbl 88:
2.20 frystyk 89: /* get_gopher_icon
1.1 timbl 90: **
2.20 frystyk 91: ** This function finds an appopriate icon for the item in the gopher
92: ** list. Actually it is only a shell build upon HTGetIcon().
2.17 frystyk 93: **
94: */
95: PRIVATE HTIconNode *get_gopher_icon ARGS2(CONST char *, filename,
96: int, gopher_type)
97: {
98: HTFormat content_type = NULL;
99: HTAtom *content_encoding = NULL;
100:
101: if (gopher_type == GOPHER_MENU)
102: return icon_dir ? icon_dir : icon_unknown;
103:
104: switch(gopher_type) {
105: case GOPHER_TEXT:
106: content_type = HTAtom_for("text/void");
107: break;
2.20 frystyk 108: case GOPHER_IMAGE:
109: case GOPHER_PLUS_IMAGE:
2.17 frystyk 110: case GOPHER_GIF:
111: content_type = HTAtom_for("image/void");
112: break;
2.20 frystyk 113: case GOPHER_WWW:
2.17 frystyk 114: case GOPHER_HTML:
115: content_type = HTAtom_for("text/void");
116: break;
117: case GOPHER_SOUND:
2.20 frystyk 118: case GOPHER_PLUS_SOUND:
2.17 frystyk 119: content_type = HTAtom_for("audio/void");
120: break;
2.20 frystyk 121: case GOPHER_PLUS_MOVIE:
122: content_type = HTAtom_for("video/void");
2.17 frystyk 123: break;
124: case GOPHER_INDEX:
125: content_type = HTAtom_for("application/x-gopher-index");
126: break;
127: case GOPHER_CSO:
128: content_type = HTAtom_for("application/x-gopher-cso");
129: break;
130: case GOPHER_TELNET:
131: content_type = HTAtom_for("application/x-gopher-telnet");
132: break;
133: case GOPHER_TN3270:
134: content_type = HTAtom_for("application/x-gopher-tn3270");
135: break;
136: case GOPHER_DUPLICATE:
137: content_type = HTAtom_for("application/x-gopher-duplicate");
138: break;
139: case GOPHER_ERROR:
140: content_type = HTAtom_for("www/unknown");
141: break;
142: case GOPHER_MACBINHEX:
143: case GOPHER_PCBINHEX:
144: case GOPHER_UUENCODED:
145: case GOPHER_BINARY:
146: { /* Do our own filetyping -- maybe we get lucky */
147: HTAtom *language;
148: content_type = HTFileFormat(filename, &content_encoding,
149: &language);
150: }
151: default:
152: content_type = HTAtom_for("www/unknown");
153: break;
154: }
155: return HTGetIcon(S_IFMT & S_IFREG, content_type, content_encoding);
156: }
157:
158:
2.20 frystyk 159: /* parse_menu
160: **
161: ** This function parses a gopher menu and puts it into a iconized
162: ** list.
163: **
164: ** Returns HT_LOADED on succed, HT_INTERRUPTED if interrupted and -1
165: ** if other error.
1.1 timbl 166: **
2.20 frystyk 167: ** BUG: The gopher type might be an illigal character, but it is NOT
168: ** escaped :-(
1.1 timbl 169: */
2.20 frystyk 170: PRIVATE int parse_menu ARGS3(HTRequest *, request,
171: gopher_info *, gopher,
172: CONST char *, url)
173: #define TAB '\t'
174: #define HEX_ESCAPE '%'
1.1 timbl 175: {
2.20 frystyk 176: int status = -1;
2.17 frystyk 177: unsigned int files = 0;
178: int ch;
2.20 frystyk 179: HTChunk *chunk = HTChunkCreate(128);
180: char *message = NULL; /* For a gopher message */
181: HTInputSocket *isoc = HTInputSocket_new(gopher->socket);
2.21 frystyk 182: HTStructured *target = NULL;
2.20 frystyk 183:
184: /* Output the list */
185: while ((ch = HTInputSocket_getCharacter(isoc)) >= 0) {
186: if (ch == CR || ch == LF) {
187: if (chunk->size) { /* Got some text */
188: char *name = NULL; /* Gopher menu fields */
189: char *selector = NULL;
190: char *host = NULL;
191: char *port = NULL;
192: char *strptr;
193: char *errptr;
194: char gtype;
195: HTChunkTerminate(chunk);
196: strptr = chunk->data; /* Scan it to parse it */
197: if (TRACE)
198: fprintf(stderr, "HTGopher.... Menu item: `%s\'\n",
199: chunk->data);
200: gtype = *strptr++;
201:
202: /* If first item is an error, then don't put any header out
203: but wait and see if there is a next item in the list. If not
204: then make error message, else use as list message. */
205: if (gtype == GOPHER_ERROR) {
206: StrAllocCopy(message, chunk->data+1);
207: break;
208: }
1.1 timbl 209:
2.20 frystyk 210: if (!files && (strstr(chunk->data, "error.host") ||
211: strstr(chunk->data, "errorhost"))) {
2.18 luotonen 212:
2.20 frystyk 213: /* If message is already initialized, then add this one. */
214: /* We don't want the gopher type character */
215: if ((errptr = strchr(chunk->data, '\t')) != NULL)
216: *errptr = '\0';
217: if (message) {
218: StrAllocCat(message, "\n");
219: StrAllocCat(message, chunk->data+1);
220: } else
221: StrAllocCopy(message, chunk->data+1);
222: HTChunkClear(chunk);
223: continue;
224: }
2.17 frystyk 225:
2.21 frystyk 226: /* Stop listing if line with a dot by itself */
2.25 ! frystyk 227: if (!files && message && gtype=='.' && !*strptr) {
2.21 frystyk 228: status = -1;
229: break;
230: }
231:
2.20 frystyk 232: /* Output title, maybe top message and list top */
233: if (!files) {
234: CONST char *title = HTAnchor_title(request->anchor);
235: char *outstr = NULL;
2.21 frystyk 236: target = HTML_new(request, NULL, WWW_HTML,
237: request->output_format,
238: request->output_stream);
2.20 frystyk 239: if (title) {
240: StrAllocCopy(outstr, title);
241: HTUnEscape(outstr);
242: } else
243: StrAllocCopy(outstr, "Gopher Menu");
244: START(HTML_TITLE);
245: PUTS(outstr);
246: END(HTML_TITLE);
247: START(HTML_H1);
248: PUTS(outstr);
249: END(HTML_H1);
250: FREE(outstr);
251:
252: /* Output any message on top of list */
253: if (message && HTDirInfo == HT_DIR_INFO_TOP) {
254: PUTS(message);
255: START(HTML_BR);
256: }
2.17 frystyk 257:
2.20 frystyk 258: /* Make everything in list preformatted */
259: START(HTML_PRE);
1.1 timbl 260:
2.20 frystyk 261: /* Output the header line of the list */
262: if (!icon_blank) icon_blank = icon_unknown;
263: if (HTDirShowMask & HT_DIR_SHOW_ICON && icon_blank) {
264: HTMLPutImg(target, icon_blank->icon_url,
265: HTIcon_alt_string(icon_blank->icon_alt, NO),
266: NULL);
267: }
2.17 frystyk 268: PUTC(' ');
2.20 frystyk 269: PUTS("Name");
270: PUTC('\n');
271: START(HTML_HR);
272: PUTC('\n');
2.17 frystyk 273: }
274:
2.20 frystyk 275: /* Stop listing if line with a dot by itself */
276: if (gtype=='.' && !*strptr) {
277: status = (!files && message) ? -1 : HT_LOADED;
278: break;
2.7 secret 279: }
2.20 frystyk 280:
281: /* Parse menu item */
282: if (*strptr) {
283: name = strptr;
284: selector = strchr(name, TAB);
285: if (selector) {
286: *selector++ = 0; /* Terminate name */
287: host = strchr(selector, TAB);
288: if (host) {
289: *host++ = 0; /* Terminate selector */
290: port = strchr(host, TAB);
291: if (port) {
292: char *junk;
293: *port = ':'; /* delimit host a la W3 */
294: if ((junk = strchr(port, TAB)) != NULL)
295: *junk = '\0'; /* Chop port */
296: if (*(port+1) == '0' && !*(port+2))
297: *port = '\0';
298: } /* port */
299: } /* host */
300: } /* selector */
301: } /* gtype and name */
302:
303: /* Get Icon type and output the icon */
304: if (HTDirShowMask & HT_DIR_SHOW_ICON) {
305: HTIconNode *icon = get_gopher_icon(url, gtype);
306: if (icon && icon->icon_url) {
307: HTMLPutImg(target, icon->icon_url,
308: HTIcon_alt_string(icon->icon_alt, YES),
309: NULL);
310: PUTC(' ');
311: }
2.7 secret 312: }
2.20 frystyk 313:
314: if (gtype == GOPHER_WWW) { /* Gopher pointer to W3 */
315: char *escaped = NULL;
316: escaped = HTEscape(selector, URL_PATH);
317: HTStartAnchor(target, NULL, escaped);
318: PUTS(name);
319: END(HTML_A);
320: free(escaped);
321: } else if (port) { /* Other types need port */
322: char *escaped = NULL;
323: char *address = NULL;
324: int addr_len;
325:
326: /* Calculate the length of the WWW-address */
327: if (selector && *selector) {
328: escaped = HTEscape(selector, URL_PATH);
329: addr_len = 15 + strlen(escaped) + strlen(host) + 1;
330: } else {
331: addr_len = 15 + strlen(host) + 1;
332: }
333: if ((address = (char *) malloc(addr_len)) == NULL)
334: outofmem(__FILE__, "Gopher ParseMenu");
335: *address = '\0';
336:
337: if (gtype == GOPHER_TELNET) {
338: if (escaped)
339: sprintf(address, "telnet://%s@%s/",
340: escaped, host);
341: else
342: sprintf(address, "telnet://%s/", host);
343: }
344: else if (gtype == GOPHER_TN3270) {
345: if (escaped)
346: sprintf(address, "tn3270://%s@%s/",
347: escaped, host);
348: else
349: sprintf(address, "tn3270://%s/", host);
350: } else {
351: if (escaped)
352: sprintf(address, "//%s/%c%s", host, gtype,
353: escaped);
354: else
355: sprintf(address, "//%s/%c", host, gtype);
1.1 timbl 356: }
2.20 frystyk 357:
358: /* Now output the anchor if not a Gopher error */
359: if (gtype != GOPHER_ERROR &&
360: !strstr(address, "error.host") &&
361: !strstr(address, "errorhost")) {
362: HTStartAnchor(target, NULL, address);
363: PUTS(name);
364: END(HTML_A);
365: } else
366: PUTS(name+1); /* Just put it out, but skip type */
367: FREE(address);
368: FREE(escaped);
369: } else { /* If parse error */
370: if (TRACE)
371: fprintf(stderr, "HTGopher.... Bad menu item, `%s\'\n",
372: chunk->data);
373: PUTS(chunk->data);
1.1 timbl 374: }
2.17 frystyk 375: PUTC('\n');
2.20 frystyk 376: HTChunkClear(chunk);
377: ++files; /* Update number of files */
378: }
379: } else
380: HTChunkPutc(chunk, ch);
381: }
382: if (ch < 0)
383: status = ch;
1.2 timbl 384:
2.20 frystyk 385: /* If no files and message is initialized then make error message,
386: else output the bottom part of the list*/
387: if (status != HT_INTERRUPTED) {
388: if (!files && status < 0) {
389: if (message) {
390: HTErrorAdd(request, ERR_FATAL, NO, HTERR_GOPHER_SERVER,
391: (void *) message, strlen(message), "parse_menu");
392: } else {
393: HTErrorAdd(request, ERR_FATAL, NO, HTERR_GOPHER_SERVER,
394: chunk->data, chunk->size, "parse_menu");
395: }
2.21 frystyk 396: } else if (target) {
2.20 frystyk 397: char *outstr;
398: if ((outstr = (char *) malloc(100)) == NULL)
399: outofmem(__FILE__, "parse_menu");
400: if (files == 0)
401: sprintf(outstr, "Empty directory");
402: else if (files == 1)
403: sprintf(outstr, "1 file");
404: else
405: sprintf(outstr, "%u files", files);
406: START(HTML_HR);
407: PUTS(outstr);
408: free(outstr);
409: END(HTML_PRE);
1.1 timbl 410:
2.20 frystyk 411: /* Put out any messages */
412: if (message && HTDirInfo == HT_DIR_INFO_BOTTOM) {
413: PUTS(message);
414: START(HTML_BR);
415: }
2.21 frystyk 416: FREE_TARGET;
417: } else {
418: if (TRACE)
419: fprintf(stderr, "HTGopher.... Interrupted before any stream was put up.\n");
2.20 frystyk 420: }
2.17 frystyk 421: }
422:
2.20 frystyk 423: /* Cleanup */
424: FREE(message);
2.11 timbl 425: HTInputSocket_free(isoc);
2.20 frystyk 426: HTChunkFree(chunk);
427: return status;
1.1 timbl 428: }
2.11 timbl 429:
430:
2.7 secret 431: /* Parse a Gopher CSO document
2.20 frystyk 432: ** ============================
433: **
434: ** Accepts an open socket to a CSO server waiting to send us
435: ** data and puts it on the screen in a reasonable manner.
436: **
437: ** Perhaps this data can be automatically linked to some
438: ** other source as well???
439: **
440: ** Taken from hacking by Lou Montulli@ukanaix.cc.ukans.edu
441: ** on XMosaic-1.1, and put on libwww 2.11 by Arthur Secret,
442: ** secret@dxcern.cern.ch.
443: **
444: ** Returns HT_LOADED on succed, HT_INTERRUPTED if interrupted and -1
445: ** if other error.
446: */
447: PRIVATE int parse_cso ARGS3(HTRequest *, request,
448: gopher_info *, gopher,
449: CONST char *, url)
2.7 secret 450: {
2.20 frystyk 451: int status = -1;
452: unsigned int records = 0;
2.17 frystyk 453: int ch;
2.20 frystyk 454: char *cur_code = NULL;
455: HTChunk *chunk = HTChunkCreate(128);
456: HTInputSocket *isoc = HTInputSocket_new(gopher->socket);
2.21 frystyk 457: HTStructured *target = NULL;
2.20 frystyk 458:
459: /* Start grabbing chars from the network */
460: while ((ch = HTInputSocket_getCharacter(isoc)) >= 0) {
461: if (ch == CR || ch == LF) {
462: if (chunk->size) {
463: /* OK we now have a line in 'p' lets parse it and print it */
464: char *strptr;
465: HTChunkTerminate(chunk);
466: strptr = chunk->data;
467:
468: /* If line begins with a 1, then more data is coming, so we
469: put out the title */
470: if (*strptr == '1' ||
471: !strncmp(strptr, "501", 3) || !strncmp(strptr, "502", 3)) {
2.21 frystyk 472:
473: /* Put up new stream */
474: target = HTML_new(request, NULL, WWW_HTML,
475: request->output_format,
476: request->output_stream);
2.20 frystyk 477: START(HTML_H1);
478: PUTS("CSO Search Results");
479: END(HTML_H1);
480:
481: /* Output the header line of the list */
482: START(HTML_PRE); /* To make it look as the other headers */
483: if (!icon_blank) icon_blank = icon_unknown;
484: if (HTDirShowMask & HT_DIR_SHOW_ICON && icon_blank) {
485: HTMLPutImg(target, icon_blank->icon_url,
486: HTIcon_alt_string(icon_blank->icon_alt, NO),
487: NULL);
488: }
489: PUTC(' ');
490: PUTS("Record");
491: PUTC('\n');
492: START(HTML_HR);
493: PUTC('\n');
494: END(HTML_PRE);
495: }
2.7 secret 496:
2.20 frystyk 497: /* Break on line that begins with a 2. It's the end of data. */
498: if (*strptr == '2') {
499: status = HT_LOADED;
500: break;
501: }
502:
503: /* Lines beginning with 5 are errors, generate msg and quit */
504: if (*strptr == '5') {
505: char *msgptr = strchr(chunk->data, ':');
506: if (!msgptr)
507: msgptr = chunk->data;
508: else
509: ++msgptr;
510: if (!strncmp(strptr, "501", 3)) /* No entries */
511: status = HT_LOADED;
512: else if (!strncmp(strptr, "502", 3)) { /* Too many */
513: status = HT_LOADED;
514: PUTS(msgptr);
515: } else {
516: HTErrorAdd(request, ERR_FATAL, NO, HTERR_CSO_SERVER,
517: (void *) msgptr,
518: strlen(msgptr), "parse_cso");
519: }
520: break;
521: }
522:
523: if(*strptr == '-') {
524: /* data lines look like -200:#:
525: * where # is the search result number and can be
526: * multiple digits (infinate?)
527: * find the second colon and check the digit to the
528: * left of it to see if they are diferent
529: * if they are then a different person is starting.
530: * make this line an <h2>
2.7 secret 531: */
2.20 frystyk 532: char *code; /* format: -200:code:field:value */
533: char *field;
534: char *value;
535: if ((code = strchr(strptr, ':')) != NULL &&
536: (field = strchr(++code, ':')) != NULL) {
537: *field++ = '\0';
538:
539: /* Let's do a strcmp instead of numbers */
540: if (!records) { /* Header of first record */
541: records++;
542: START(HTML_H2);
543: PUTS("Record 1");
544: END(HTML_H2);
545: START(HTML_DL);
546: } else if (cur_code && strcmp(code, cur_code)) {
547: char recstr[20];
548: records++;
549: END(HTML_DL);
550: START(HTML_H3);
551: PUTS("Record ");
2.23 frystyk 552: sprintf(recstr, "%u", records);
2.20 frystyk 553: PUTS(recstr);
554: END(HTML_H3);
555: START(HTML_DL);
556: } else
557: START(HTML_DT);
558:
559: /* I'm not sure whether the name field comes in any
560: * special order or if its even required in a
561: * record, so for now the first line is the header
562: * no matter what it is (it's almost always the
563: * alias)
2.7 secret 564: */
2.20 frystyk 565: if ((value = strchr(field, ':')) == NULL)
566: value = "Empty?";
567: else
568: *value++ = '\0';
569: {
570: char *strip = HTStrip(field);
571: PUTS(strip);
572: START(HTML_DD);
573: strip = HTStrip(value);
574: PUTS(strip);
575: }
2.7 secret 576:
2.20 frystyk 577: /* save the code for comparison on the next pass */
578: StrAllocCopy(cur_code, code);
579: }
580: } /* end data line */
581: HTChunkClear(chunk);
582: } /* end new line */
583: } else
584: HTChunkPutc(chunk, ch);
585: }
586: if (ch < 0)
587: status = ch;
588:
589: /* Put out the bottom line */
590: if (status != HT_INTERRUPTED) {
2.21 frystyk 591: if (target) {
592: char *outstr;
593: if ((outstr = (char *) malloc(100)) == NULL)
594: outofmem(__FILE__, "parse_menu");
595: if (!records)
596: sprintf(outstr, "No records");
597: else if (records == 1)
598: sprintf(outstr, "1 record");
599: else
600: sprintf(outstr, "%u records", records);
601: START(HTML_PRE);
602: START(HTML_HR);
603: PUTS(outstr);
604: END(HTML_PRE);
605: free(outstr);
606: FREE_TARGET;
607: } else {
608: if (TRACE) fprintf(stderr, "HTGopher.... Interrupted before any stream was put up.\n");
609: }
2.20 frystyk 610: }
611:
612: /* Clean up */
2.11 timbl 613: HTInputSocket_free(isoc);
2.20 frystyk 614: HTChunkFree(chunk);
615: FREE(cur_code);
616: return status;
617: }
2.7 secret 618:
1.1 timbl 619:
620: /* Display a Gopher Index document
2.20 frystyk 621: ** -------------------------------
622: */
623: PRIVATE void display_index ARGS2(HTRequest *, request,
624: CONST char *, url)
1.1 timbl 625: {
2.20 frystyk 626: HTStructured *target = HTML_new(request, NULL, WWW_HTML,
627: request->output_format,
628: request->output_stream);
2.18 luotonen 629:
1.2 timbl 630: START(HTML_H1);
2.20 frystyk 631: PUTS("Searchable Gopher Index");
1.2 timbl 632: END(HTML_H1);
2.7 secret 633: START(HTML_ISINDEX);
2.20 frystyk 634: if (!HTAnchor_title(request->anchor))
635: HTAnchor_setTitle(request->anchor, url);
2.7 secret 636: FREE_TARGET;
637: return;
638: }
639:
640:
641: /* Display a CSO index document
642: ** -------------------------------
643: */
2.20 frystyk 644: PRIVATE void display_cso ARGS2(HTRequest *, request,
645: CONST char *, url)
2.7 secret 646: {
2.20 frystyk 647: HTStructured *target = HTML_new(request, NULL, WWW_HTML,
648: request->output_format,
649: request->output_stream);
2.7 secret 650: START(HTML_H1);
2.20 frystyk 651: PUTS("Searchable Index of a CSO Name Server");
2.7 secret 652: END(HTML_H1);
653: START(HTML_ISINDEX);
2.20 frystyk 654: if (!HTAnchor_title(request->anchor))
655: HTAnchor_setTitle(request->anchor, url);
1.2 timbl 656: FREE_TARGET;
1.1 timbl 657: return;
658: }
659:
660:
2.20 frystyk 661:
662: /* HTGopher_send_cmd
1.1 timbl 663: **
2.20 frystyk 664: ** This function creates a socket and writes the gopher command to it.
665: ** The command must be terminated with <CRLF>
666: **
667: ** Returns 0 on OK, else <0 but does NOT close the connection
1.1 timbl 668: */
2.20 frystyk 669: PRIVATE int HTGopher_send_cmd ARGS3(HTRequest *, request,
670: char *, url,
671: gopher_info *, gopher)
1.1 timbl 672: {
2.20 frystyk 673: int status = 0;
2.22 frystyk 674: if (!gopher || !request || !url) {
2.20 frystyk 675: if (TRACE)
676: fprintf(stderr, "Gopher Tx... Bad argument!\n");
677: return -1;
678: }
679: if ((status = HTDoConnect(request, url, GOPHER_PORT,
680: &gopher->socket, NULL)) < 0) {
681: if (TRACE)
682: fprintf(stderr, "HTLoadGopher Connection not established!\n");
683: return status;
684: }
685: if (TRACE)
686: fprintf(stderr, "Gopher...... Connected, socket %d\n", gopher->socket);
687:
688: /* Write the command to the socket */
689: #ifdef NOT_ASCII
690: {
691: char * p;
692: for(p = command; *p; p++) {
693: *p = TOASCII(*p);
1.1 timbl 694: }
695: }
2.20 frystyk 696: #endif
697: if (TRACE)
698: fprintf(stderr, "Gopher Tx... %s", gopher->command);
699: if ((status = NETWRITE(gopher->socket, gopher->command,
700: (int) strlen(gopher->command))) < 0) {
701: if (TRACE) fprintf(stderr, "Gopher...... Error sending command: %s\n",
702: gopher->command);
2.21 frystyk 703: HTErrorSysAdd(request, ERR_FATAL, NO, "write");
2.20 frystyk 704: } else
705: status = 0;
706: return status;
1.1 timbl 707: }
708:
709:
710: /* Load by name HTLoadGopher
711: ** ============
712: **
2.24 frystyk 713: ** Given a hypertext address, this routine loads a gopher document
714: **
715: ** On entry,
716: ** request This is the request structure
717: ** On exit,
718: ** returns <0 Error has occured
719: ** HT_LOADED OK
1.1 timbl 720: **
721: */
2.13 timbl 722: PUBLIC int HTLoadGopher ARGS1(HTRequest *, request)
1.1 timbl 723: {
2.22 frystyk 724: char *url;
2.20 frystyk 725: int status = -1;
726: gopher_info *gopher;
727:
2.22 frystyk 728: if (!request || !request->anchor) {
2.20 frystyk 729: if (TRACE) fprintf(stderr, "HTLoadGopher Bad argument\n");
730: return -1;
731: }
2.22 frystyk 732: url = HTAnchor_physical(request->anchor);
2.20 frystyk 733: if (TRACE) fprintf(stderr, "HTGopher.... Looking for `%s\'\n", url);
734:
735: /* Initiate a new gopher structure */
736: if ((gopher = (gopher_info *) calloc(1, sizeof(gopher_info))) == NULL)
737: outofmem(__FILE__, "HTLoadGopher");
738: gopher->socket = -1;
739: gopher->type = GOPHER_MENU;
1.1 timbl 740:
2.20 frystyk 741: /* Get entity type, and selector string and generate command */
1.1 timbl 742: {
2.20 frystyk 743: char *path = HTParse(url, "", PARSE_PATH);
744: char *selector = path;
745: char *query = NULL;
746: char *separator = NULL;
747: if (*selector)
748: gopher->type = *selector++; /* Pick up gtype */
749: if (gopher->type == GOPHER_INDEX) {
750: HTAnchor_setIndex(request->anchor); /* Search is allowed */
751: query = strchr(selector, '?'); /* Look for search string */
752:
753: /* Display local "cover page" only if no search requested */
754: if (!query || !*(query+1)) { /* No search required */
755: display_index(request, url);
756: status = HT_LOADED; /* Local function only */
757: } else {
758: *query++ = 0; /* Skip '?' */
759: separator = "\t";
1.1 timbl 760: }
2.20 frystyk 761: } else if (gopher->type == GOPHER_CSO) {
762: HTAnchor_setIndex(request->anchor); /* Search is allowed */
763: query = strchr(selector, '?'); /* Look for search string */
764:
765: /* Display local "cover page" only if no search requested */
766: if (!query || !*(query+1)) { /* No search required */
767: display_cso(request, url);
768: status = HT_LOADED; /* Local function only */
769: } else {
770: *query++ = 0; /* Skip '?' */
771: separator = "query ";
1.1 timbl 772: }
773: }
774:
2.20 frystyk 775: /* Now generate the final command */
776: if (status != HT_LOADED) {
2.24 frystyk 777: char crlf[3];
2.20 frystyk 778: StrAllocCopy(gopher->command, selector);
779: if (query) {
780: char *p;
781: for (p=query; *p; p++) /* Remove plus signs 921006 */
782: if (*p == '+') *p = ' ';
783: StrAllocCat(gopher->command, separator);
784: StrAllocCat(gopher->command, query);
785: }
786: HTUnEscape(gopher->command);
787: HTCleanTelnetString(gopher->command); /* Prevent security holes */
2.24 frystyk 788: *crlf = CR; /* Telnet termination */
789: *(crlf+1) = LF;
790: *(crlf+2) = '\0';
791: StrAllocCat(gopher->command, crlf);
2.20 frystyk 792: }
793: free(path);
1.1 timbl 794: }
795:
2.20 frystyk 796: /* Now we must ask the server for real data :-( */
797: if (status != HT_LOADED) {
798: if ((status = HTGopher_send_cmd(request, url, gopher)) == 0) {
799:
800: /* Now read the data from the socket: */
801: switch (gopher->type) {
802: case GOPHER_HTML:
803: status = HTParseSocket(WWW_HTML, gopher->socket, request);
804: break;
805:
806: case GOPHER_GIF:
807: case GOPHER_IMAGE:
808: case GOPHER_PLUS_IMAGE:
809: status = HTParseSocket(HTAtom_for("image/gif"), gopher->socket,
810: request);
811: break;
812: case GOPHER_MENU:
813: case GOPHER_INDEX:
814: status = parse_menu(request, gopher, url);
815: break;
816:
817: case GOPHER_CSO:
818: status = parse_cso(request, gopher, url);
819: break;
820:
821: case GOPHER_MACBINHEX:
822: case GOPHER_PCBINHEX:
823: case GOPHER_UUENCODED:
824: case GOPHER_BINARY:
825: {
826: /* Do our own filetyping -- maybe we get lucky */
827: HTFormat format;
828: format = HTFileFormat(url, &request->content_encoding,
829: &request->content_language);
830: if (format) {
831: CTRACE(stderr,
832: "Gopher...... Figured out content-type myself: %s\n",
833: HTAtom_name(format));
834: status = HTParseSocket(format, gopher->socket,
835: request);
836: }
837: else {
838: CTRACE(stderr,"Gopher...... using www/unknown\n");
839: /* Specifying WWW_UNKNOWN forces dump to local disk */
840: HTParseSocket(WWW_UNKNOWN, gopher->socket, request);
841: }
842: }
843: break;
844:
845: case GOPHER_SOUND:
846: case GOPHER_PLUS_SOUND:
847: status = HTParseSocket(WWW_AUDIO, gopher->socket, request);
848: break;
849:
850: case GOPHER_PLUS_MOVIE:
851: status = HTParseSocket(WWW_VIDEO, gopher->socket, request);
852: break;
853:
854: case GOPHER_TEXT:
855: default: /* @@ parse as plain text */
856: status = HTParseSocket(WWW_PLAINTEXT, gopher->socket, request);
857: break;
2.16 luotonen 858: }
859: }
1.2 timbl 860:
2.20 frystyk 861: /* Close the connection */
2.25 ! frystyk 862: if (gopher->socket >= 0) {
! 863: if (TRACE) fprintf(stderr, "Gopher...... Closing socket %d\n",
! 864: gopher->socket);
! 865: if (NETCLOSE(gopher->socket) < 0)
! 866: status = HTErrorSysAdd(request, ERR_FATAL, NO, "close");
! 867: }
2.20 frystyk 868: }
869: if (status == HT_INTERRUPTED) {
870: HTErrorAdd(request, ERR_WARNING, NO, HTERR_INTERRUPTED, NULL, 0,
871: "HTLoadGopher");
872: }
873: FREE(gopher->command);
874: free(gopher);
875:
876: if (status < 0 && status != HT_INTERRUPTED) {
2.21 frystyk 877: char *unescaped = NULL;
878: StrAllocCopy(unescaped, url);
879: HTUnEscape(unescaped);
880: HTErrorAdd(request, ERR_FATAL, NO, HTERR_INTERNAL, (void *) unescaped,
881: (int) strlen(unescaped), "HTLoadGopher");
2.20 frystyk 882: HTAnchor_clearIndex(request->anchor);
2.21 frystyk 883: free(unescaped);
2.20 frystyk 884: }
885: return status;
1.1 timbl 886: }
1.2 timbl 887:
2.10 timbl 888: GLOBALDEF PUBLIC HTProtocol HTGopher = { "gopher", HTLoadGopher, NULL, NULL };
1.1 timbl 889:
Webmaster