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