Annotation of libwww/Library/src/HTGopher.c, revision 2.27
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.27 ! duns 13: ** 8 Jul 94 FM Insulate free() from _free structure element.
2.21 frystyk 14: **
15: ** NOTE:
16: ** When parsing a gopher menu, we can't use the default HTParseSocket
17: ** but we will hav eto take care of the stram ourselves :-(
18: **
1.1 timbl 19: */
20:
2.20 frystyk 21: /* Implementation dependent include files */
1.1 timbl 22: #include "tcp.h"
23:
2.20 frystyk 24: /* Library include files */
25: #include "HTParse.h"
26: #include "HTUtils.h"
27: #include "HTTCP.h"
2.17 frystyk 28: #include "HTIcons.h"
2.20 frystyk 29: #include "HTAccess.h"
1.1 timbl 30: #include "HTFormat.h"
2.20 frystyk 31: #include "HTError.h"
32: #include "HTFile.h"
1.2 timbl 33: #include "HTML.h"
2.20 frystyk 34: #include "HTMLPDTD.h"
35: #include "HTDirBrw.h"
36: #include "HTGopher.h" /* Implemented here */
37:
38: /* Macros and other defines */
39: #ifndef GOPHER_PORT
40: #define GOPHER_PORT 70 /* See protocol spec */
41: #endif
1.2 timbl 42:
2.20 frystyk 43: /* Hypertext object building machinery */
2.17 frystyk 44: #define PUTC(c) (*target->isa->put_character)(target, c)
45: #define PUTS(s) (*target->isa->put_string)(target, s)
46: #define START(e) (*target->isa->start_element)(target, e, 0, 0)
47: #define END(e) (*target->isa->end_element)(target, e)
2.27 ! duns 48: #define FREE_TARGET (*target->isa->_free)(target)
2.20 frystyk 49:
50: /* Type definitions and global variables etc. local to this module */
51: typedef enum _HTGopherType {
52: GOPHER_TEXT = '0',
53: GOPHER_MENU = '1',
54: GOPHER_CSO = '2',
55: GOPHER_ERROR = '3',
56: GOPHER_MACBINHEX = '4',
57: GOPHER_PCBINHEX = '5',
58: GOPHER_UUENCODED = '6',
59: GOPHER_INDEX = '7',
60: GOPHER_TELNET = '8',
61: GOPHER_BINARY = '9',
62: GOPHER_GIF = 'g',
63: GOPHER_HTML = 'h', /* HTML */
64: GOPHER_SOUND = 's',
65: GOPHER_WWW = 'w', /* W3 address */
66: GOPHER_IMAGE = 'I',
67: GOPHER_TN3270 = 'T',
68: GOPHER_DUPLICATE = '+',
69: GOPHER_PLUS_IMAGE = ':', /* Addition from Gopher Plus */
70: GOPHER_PLUS_MOVIE = ';',
71: GOPHER_PLUS_SOUND = '<'
72: } HTGopherType;
73:
1.2 timbl 74: struct _HTStructured {
75: CONST HTStructuredClass * isa;
76: /* ... */
77: };
78:
2.26 frystyk 79: /* This is the local definition of HTRequest->net_info */
2.20 frystyk 80: typedef struct _gopher_info {
2.26 frystyk 81: int sockfd; /* Socket number for communication */
82: HTInputSocket * isoc; /* Input buffer */
83: HTRequest * request; /* Request structure */
2.20 frystyk 84: HTGopherType type; /* Gopher item type */
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: **
167: */
2.20 frystyk 168: PRIVATE int parse_menu ARGS3(HTRequest *, request,
169: gopher_info *, gopher,
170: CONST char *, url)
171: #define TAB '\t'
172: #define HEX_ESCAPE '%'
1.1 timbl 173: {
2.20 frystyk 174: int status = -1;
2.17 frystyk 175: unsigned int files = 0;
176: int ch;
2.20 frystyk 177: HTChunk *chunk = HTChunkCreate(128);
178: char *message = NULL; /* For a gopher message */
2.21 frystyk 179: HTStructured *target = NULL;
2.26 frystyk 180:
181: gopher->isoc = HTInputSocket_new(gopher->sockfd);
2.20 frystyk 182:
183: /* Output the list */
2.26 frystyk 184: while ((ch = HTInputSocket_getCharacter(gopher->isoc)) >= 0) {
2.20 frystyk 185: if (ch == CR || ch == LF) {
186: if (chunk->size) { /* Got some text */
187: char *name = NULL; /* Gopher menu fields */
188: char *selector = NULL;
189: char *host = NULL;
190: char *port = NULL;
191: char *strptr;
192: char *errptr;
193: char gtype;
194: HTChunkTerminate(chunk);
195: strptr = chunk->data; /* Scan it to parse it */
196: if (TRACE)
197: fprintf(stderr, "HTGopher.... Menu item: `%s\'\n",
198: chunk->data);
199: gtype = *strptr++;
200:
201: /* If first item is an error, then don't put any header out
202: but wait and see if there is a next item in the list. If not
203: then make error message, else use as list message. */
204: if (gtype == GOPHER_ERROR) {
205: StrAllocCopy(message, chunk->data+1);
206: break;
207: }
1.1 timbl 208:
2.20 frystyk 209: if (!files && (strstr(chunk->data, "error.host") ||
210: strstr(chunk->data, "errorhost"))) {
2.18 luotonen 211:
2.20 frystyk 212: /* If message is already initialized, then add this one. */
213: /* We don't want the gopher type character */
214: if ((errptr = strchr(chunk->data, '\t')) != NULL)
215: *errptr = '\0';
216: if (message) {
217: StrAllocCat(message, "\n");
218: StrAllocCat(message, chunk->data+1);
219: } else
220: StrAllocCopy(message, chunk->data+1);
221: HTChunkClear(chunk);
222: continue;
223: }
2.17 frystyk 224:
2.21 frystyk 225: /* Stop listing if line with a dot by itself */
2.25 frystyk 226: if (!files && message && gtype=='.' && !*strptr) {
2.21 frystyk 227: status = -1;
228: break;
229: }
230:
2.20 frystyk 231: /* Output title, maybe top message and list top */
232: if (!files) {
233: CONST char *title = HTAnchor_title(request->anchor);
234: char *outstr = NULL;
2.21 frystyk 235: target = HTML_new(request, NULL, WWW_HTML,
236: request->output_format,
237: request->output_stream);
2.20 frystyk 238: if (title) {
239: StrAllocCopy(outstr, title);
240: HTUnEscape(outstr);
241: } else
242: StrAllocCopy(outstr, "Gopher Menu");
243: START(HTML_TITLE);
244: PUTS(outstr);
245: END(HTML_TITLE);
246: START(HTML_H1);
247: PUTS(outstr);
248: END(HTML_H1);
249: FREE(outstr);
250:
251: /* Output any message on top of list */
252: if (message && HTDirInfo == HT_DIR_INFO_TOP) {
253: PUTS(message);
254: START(HTML_BR);
255: }
2.17 frystyk 256:
2.20 frystyk 257: /* Make everything in list preformatted */
258: START(HTML_PRE);
1.1 timbl 259:
2.20 frystyk 260: /* Output the header line of the list */
261: if (!icon_blank) icon_blank = icon_unknown;
262: if (HTDirShowMask & HT_DIR_SHOW_ICON && icon_blank) {
263: HTMLPutImg(target, icon_blank->icon_url,
264: HTIcon_alt_string(icon_blank->icon_alt, NO),
265: NULL);
266: }
2.17 frystyk 267: PUTC(' ');
2.20 frystyk 268: PUTS("Name");
269: PUTC('\n');
270: START(HTML_HR);
271: PUTC('\n');
2.17 frystyk 272: }
273:
2.20 frystyk 274: /* Stop listing if line with a dot by itself */
275: if (gtype=='.' && !*strptr) {
276: status = (!files && message) ? -1 : HT_LOADED;
277: break;
2.7 secret 278: }
2.20 frystyk 279:
280: /* Parse menu item */
281: if (*strptr) {
282: name = strptr;
283: selector = strchr(name, TAB);
284: if (selector) {
285: *selector++ = 0; /* Terminate name */
286: host = strchr(selector, TAB);
287: if (host) {
288: *host++ = 0; /* Terminate selector */
289: port = strchr(host, TAB);
290: if (port) {
291: char *junk;
292: *port = ':'; /* delimit host a la W3 */
293: if ((junk = strchr(port, TAB)) != NULL)
294: *junk = '\0'; /* Chop port */
295: if (*(port+1) == '0' && !*(port+2))
296: *port = '\0';
297: } /* port */
298: } /* host */
299: } /* selector */
300: } /* gtype and name */
301:
302: /* Get Icon type and output the icon */
303: if (HTDirShowMask & HT_DIR_SHOW_ICON) {
304: HTIconNode *icon = get_gopher_icon(url, gtype);
305: if (icon && icon->icon_url) {
306: HTMLPutImg(target, icon->icon_url,
307: HTIcon_alt_string(icon->icon_alt, YES),
308: NULL);
309: PUTC(' ');
310: }
2.7 secret 311: }
2.20 frystyk 312:
313: if (gtype == GOPHER_WWW) { /* Gopher pointer to W3 */
314: char *escaped = NULL;
315: escaped = HTEscape(selector, URL_PATH);
316: HTStartAnchor(target, NULL, escaped);
317: PUTS(name);
318: END(HTML_A);
319: free(escaped);
320: } else if (port) { /* Other types need port */
321: char *escaped = NULL;
322: char *address = NULL;
323: int addr_len;
324:
325: /* Calculate the length of the WWW-address */
326: if (selector && *selector) {
327: escaped = HTEscape(selector, URL_PATH);
328: addr_len = 15 + strlen(escaped) + strlen(host) + 1;
329: } else {
330: addr_len = 15 + strlen(host) + 1;
331: }
332: if ((address = (char *) malloc(addr_len)) == NULL)
333: outofmem(__FILE__, "Gopher ParseMenu");
334: *address = '\0';
335:
336: if (gtype == GOPHER_TELNET) {
337: if (escaped)
338: sprintf(address, "telnet://%s@%s/",
339: escaped, host);
340: else
341: sprintf(address, "telnet://%s/", host);
342: }
343: else if (gtype == GOPHER_TN3270) {
344: if (escaped)
345: sprintf(address, "tn3270://%s@%s/",
346: escaped, host);
347: else
348: sprintf(address, "tn3270://%s/", host);
349: } else {
350: if (escaped)
351: sprintf(address, "//%s/%c%s", host, gtype,
352: escaped);
353: else
354: sprintf(address, "//%s/%c", host, gtype);
1.1 timbl 355: }
2.20 frystyk 356:
357: /* Now output the anchor if not a Gopher error */
358: if (gtype != GOPHER_ERROR &&
359: !strstr(address, "error.host") &&
360: !strstr(address, "errorhost")) {
361: HTStartAnchor(target, NULL, address);
362: PUTS(name);
363: END(HTML_A);
364: } else
365: PUTS(name+1); /* Just put it out, but skip type */
366: FREE(address);
367: FREE(escaped);
368: } else { /* If parse error */
369: if (TRACE)
370: fprintf(stderr, "HTGopher.... Bad menu item, `%s\'\n",
371: chunk->data);
372: PUTS(chunk->data);
1.1 timbl 373: }
2.17 frystyk 374: PUTC('\n');
2.20 frystyk 375: HTChunkClear(chunk);
376: ++files; /* Update number of files */
377: }
378: } else
379: HTChunkPutc(chunk, ch);
380: }
381: if (ch < 0)
382: status = ch;
1.2 timbl 383:
2.20 frystyk 384: /* If no files and message is initialized then make error message,
385: else output the bottom part of the list*/
386: if (status != HT_INTERRUPTED) {
387: if (!files && status < 0) {
388: if (message) {
389: HTErrorAdd(request, ERR_FATAL, NO, HTERR_GOPHER_SERVER,
390: (void *) message, strlen(message), "parse_menu");
391: } else {
392: HTErrorAdd(request, ERR_FATAL, NO, HTERR_GOPHER_SERVER,
393: chunk->data, chunk->size, "parse_menu");
394: }
2.21 frystyk 395: } else if (target) {
2.20 frystyk 396: char *outstr;
397: if ((outstr = (char *) malloc(100)) == NULL)
398: outofmem(__FILE__, "parse_menu");
399: if (files == 0)
400: sprintf(outstr, "Empty directory");
401: else if (files == 1)
402: sprintf(outstr, "1 file");
403: else
404: sprintf(outstr, "%u files", files);
405: START(HTML_HR);
406: PUTS(outstr);
407: free(outstr);
408: END(HTML_PRE);
1.1 timbl 409:
2.20 frystyk 410: /* Put out any messages */
411: if (message && HTDirInfo == HT_DIR_INFO_BOTTOM) {
412: PUTS(message);
413: START(HTML_BR);
414: }
2.21 frystyk 415: FREE_TARGET;
416: } else {
417: if (TRACE)
418: fprintf(stderr, "HTGopher.... Interrupted before any stream was put up.\n");
2.20 frystyk 419: }
2.17 frystyk 420: }
421:
2.20 frystyk 422: /* Cleanup */
423: FREE(message);
2.26 frystyk 424: HTInputSocket_free(gopher->isoc);
2.20 frystyk 425: HTChunkFree(chunk);
426: return status;
1.1 timbl 427: }
2.11 timbl 428:
429:
2.7 secret 430: /* Parse a Gopher CSO document
2.20 frystyk 431: ** ============================
432: **
433: ** Accepts an open socket to a CSO server waiting to send us
434: ** data and puts it on the screen in a reasonable manner.
435: **
436: ** Perhaps this data can be automatically linked to some
437: ** other source as well???
438: **
439: ** Taken from hacking by Lou Montulli@ukanaix.cc.ukans.edu
440: ** on XMosaic-1.1, and put on libwww 2.11 by Arthur Secret,
441: ** secret@dxcern.cern.ch.
442: **
443: ** Returns HT_LOADED on succed, HT_INTERRUPTED if interrupted and -1
444: ** if other error.
445: */
446: PRIVATE int parse_cso ARGS3(HTRequest *, request,
447: gopher_info *, gopher,
448: CONST char *, url)
2.7 secret 449: {
2.20 frystyk 450: int status = -1;
451: unsigned int records = 0;
2.17 frystyk 452: int ch;
2.20 frystyk 453: char *cur_code = NULL;
454: HTChunk *chunk = HTChunkCreate(128);
2.21 frystyk 455: HTStructured *target = NULL;
2.26 frystyk 456:
457: gopher->isoc = HTInputSocket_new(gopher->sockfd);
2.20 frystyk 458:
459: /* Start grabbing chars from the network */
2.26 frystyk 460: while ((ch = HTInputSocket_getCharacter(gopher->isoc)) >= 0) {
2.20 frystyk 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.26 frystyk 613: HTInputSocket_free(gopher->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.26 frystyk 669: PRIVATE int HTGopher_send_cmd ARGS3(gopher_info *, gopher,
2.20 frystyk 670: char *, url,
2.26 frystyk 671: char *, command)
1.1 timbl 672: {
2.20 frystyk 673: int status = 0;
2.26 frystyk 674: if (!gopher || !command) {
2.20 frystyk 675: if (TRACE)
676: fprintf(stderr, "Gopher Tx... Bad argument!\n");
677: return -1;
678: }
2.26 frystyk 679: if ((status = HTDoConnect((HTNetInfo *) gopher, url, GOPHER_PORT,
680: NULL)) < 0) {
2.20 frystyk 681: if (TRACE)
682: fprintf(stderr, "HTLoadGopher Connection not established!\n");
683: return status;
684: }
685: if (TRACE)
2.26 frystyk 686: fprintf(stderr, "Gopher...... Connected, socket %d\n", gopher->sockfd);
2.20 frystyk 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)
2.26 frystyk 698: fprintf(stderr, "Gopher Tx... %s", command);
699: if ((status = NETWRITE(gopher->sockfd, command,
700: (int) strlen(command))) < 0) {
2.20 frystyk 701: if (TRACE) fprintf(stderr, "Gopher...... Error sending command: %s\n",
2.26 frystyk 702: command);
703: HTErrorSysAdd(gopher->request, ERR_FATAL, NO, "NETWRITE");
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;
2.26 frystyk 726: char *command = NULL;
2.20 frystyk 727: gopher_info *gopher;
728:
2.22 frystyk 729: if (!request || !request->anchor) {
2.20 frystyk 730: if (TRACE) fprintf(stderr, "HTLoadGopher Bad argument\n");
731: return -1;
732: }
2.22 frystyk 733: url = HTAnchor_physical(request->anchor);
2.20 frystyk 734: if (TRACE) fprintf(stderr, "HTGopher.... Looking for `%s\'\n", url);
735:
2.26 frystyk 736: /* Initiate a new gopher structure and bind to resuest structure */
2.20 frystyk 737: if ((gopher = (gopher_info *) calloc(1, sizeof(gopher_info))) == NULL)
738: outofmem(__FILE__, "HTLoadGopher");
2.26 frystyk 739: gopher->sockfd = -1;
740: gopher->request = request;
741: request->net_info = (HTNetInfo *) gopher;
2.20 frystyk 742: gopher->type = GOPHER_MENU;
1.1 timbl 743:
2.20 frystyk 744: /* Get entity type, and selector string and generate command */
1.1 timbl 745: {
2.20 frystyk 746: char *path = HTParse(url, "", PARSE_PATH);
747: char *selector = path;
748: char *query = NULL;
749: char *separator = NULL;
750: if (*selector)
751: gopher->type = *selector++; /* Pick up gtype */
752: if (gopher->type == GOPHER_INDEX) {
753: HTAnchor_setIndex(request->anchor); /* Search is allowed */
754: query = strchr(selector, '?'); /* Look for search string */
755:
756: /* Display local "cover page" only if no search requested */
757: if (!query || !*(query+1)) { /* No search required */
758: display_index(request, url);
759: status = HT_LOADED; /* Local function only */
760: } else {
761: *query++ = 0; /* Skip '?' */
762: separator = "\t";
1.1 timbl 763: }
2.20 frystyk 764: } else if (gopher->type == GOPHER_CSO) {
765: HTAnchor_setIndex(request->anchor); /* Search is allowed */
766: query = strchr(selector, '?'); /* Look for search string */
767:
768: /* Display local "cover page" only if no search requested */
769: if (!query || !*(query+1)) { /* No search required */
770: display_cso(request, url);
771: status = HT_LOADED; /* Local function only */
772: } else {
773: *query++ = 0; /* Skip '?' */
774: separator = "query ";
1.1 timbl 775: }
776: }
777:
2.20 frystyk 778: /* Now generate the final command */
779: if (status != HT_LOADED) {
2.24 frystyk 780: char crlf[3];
2.26 frystyk 781: StrAllocCopy(command, selector);
2.20 frystyk 782: if (query) {
783: char *p;
784: for (p=query; *p; p++) /* Remove plus signs 921006 */
785: if (*p == '+') *p = ' ';
2.26 frystyk 786: StrAllocCat(command, separator);
787: StrAllocCat(command, query);
2.20 frystyk 788: }
2.26 frystyk 789: HTUnEscape(command);
790: HTCleanTelnetString(command); /* Prevent security holes */
2.24 frystyk 791: *crlf = CR; /* Telnet termination */
792: *(crlf+1) = LF;
793: *(crlf+2) = '\0';
2.26 frystyk 794: StrAllocCat(command, crlf);
2.20 frystyk 795: }
796: free(path);
1.1 timbl 797: }
798:
2.20 frystyk 799: /* Now we must ask the server for real data :-( */
800: if (status != HT_LOADED) {
2.26 frystyk 801: if ((status = HTGopher_send_cmd(gopher, url, command)) == 0) {
2.20 frystyk 802:
803: /* Now read the data from the socket: */
804: switch (gopher->type) {
805: case GOPHER_HTML:
2.26 frystyk 806: status = HTParseSocket(WWW_HTML, gopher->sockfd, request);
2.20 frystyk 807: break;
808:
809: case GOPHER_GIF:
810: case GOPHER_IMAGE:
811: case GOPHER_PLUS_IMAGE:
2.26 frystyk 812: status = HTParseSocket(HTAtom_for("image/gif"), gopher->sockfd,
2.20 frystyk 813: request);
814: break;
815: case GOPHER_MENU:
816: case GOPHER_INDEX:
817: status = parse_menu(request, gopher, url);
818: break;
819:
820: case GOPHER_CSO:
821: status = parse_cso(request, gopher, url);
822: break;
823:
824: case GOPHER_MACBINHEX:
825: case GOPHER_PCBINHEX:
826: case GOPHER_UUENCODED:
827: case GOPHER_BINARY:
2.26 frystyk 828: {
2.20 frystyk 829: /* Do our own filetyping -- maybe we get lucky */
830: HTFormat format;
831: format = HTFileFormat(url, &request->content_encoding,
832: &request->content_language);
833: if (format) {
834: CTRACE(stderr,
835: "Gopher...... Figured out content-type myself: %s\n",
836: HTAtom_name(format));
2.26 frystyk 837: status = HTParseSocket(format, gopher->sockfd,
2.20 frystyk 838: request);
839: }
840: else {
841: CTRACE(stderr,"Gopher...... using www/unknown\n");
842: /* Specifying WWW_UNKNOWN forces dump to local disk */
2.26 frystyk 843: HTParseSocket(WWW_UNKNOWN, gopher->sockfd, request);
2.20 frystyk 844: }
845: }
846: break;
847:
848: case GOPHER_SOUND:
849: case GOPHER_PLUS_SOUND:
2.26 frystyk 850: status = HTParseSocket(WWW_AUDIO, gopher->sockfd, request);
2.20 frystyk 851: break;
852:
853: case GOPHER_PLUS_MOVIE:
2.26 frystyk 854: status = HTParseSocket(WWW_VIDEO, gopher->sockfd, request);
2.20 frystyk 855: break;
2.26 frystyk 856:
857: /* Try and look at the suffix - maybe it is a PostScript file
858: so that we should start an externam viewer. */
2.20 frystyk 859: case GOPHER_TEXT:
2.26 frystyk 860: default:
861: {
862: HTFormat format;
863: format = HTFileFormat(url, &request->content_encoding,
864: &request->content_language);
865: if (format) {
866: CTRACE(stderr,
867: "Gopher...... Figured out content-type myself: %s\n",
868: HTAtom_name(format));
869: status = HTParseSocket(format, gopher->sockfd,
870: request);
871: }
872: else {
873: status = HTParseSocket(WWW_PLAINTEXT, gopher->sockfd,
874: request);
875: }
876: }
2.20 frystyk 877: break;
2.16 luotonen 878: }
879: }
1.2 timbl 880:
2.20 frystyk 881: /* Close the connection */
2.26 frystyk 882: if (gopher->sockfd >= 0) {
2.25 frystyk 883: if (TRACE) fprintf(stderr, "Gopher...... Closing socket %d\n",
2.26 frystyk 884: gopher->sockfd);
885: if (NETCLOSE(gopher->sockfd) < 0)
886: status = HTErrorSysAdd(request, ERR_FATAL, NO, "NETCLOSE");
2.25 frystyk 887: }
2.20 frystyk 888: }
889: if (status == HT_INTERRUPTED) {
890: HTErrorAdd(request, ERR_WARNING, NO, HTERR_INTERRUPTED, NULL, 0,
891: "HTLoadGopher");
892: }
2.26 frystyk 893: FREE(command);
2.20 frystyk 894: free(gopher);
895:
896: if (status < 0 && status != HT_INTERRUPTED) {
2.21 frystyk 897: char *unescaped = NULL;
898: StrAllocCopy(unescaped, url);
899: HTUnEscape(unescaped);
900: HTErrorAdd(request, ERR_FATAL, NO, HTERR_INTERNAL, (void *) unescaped,
901: (int) strlen(unescaped), "HTLoadGopher");
2.20 frystyk 902: HTAnchor_clearIndex(request->anchor);
2.21 frystyk 903: free(unescaped);
2.20 frystyk 904: }
905: return status;
1.1 timbl 906: }
1.2 timbl 907:
2.10 timbl 908: GLOBALDEF PUBLIC HTProtocol HTGopher = { "gopher", HTLoadGopher, NULL, NULL };
1.1 timbl 909:
Webmaster