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