Annotation of libwww/Library/src/HTGopher.c, revision 2.28
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) {
332: HTIconNode *icon = get_gopher_icon(url, gtype);
333: if (icon && icon->icon_url) {
334: HTMLPutImg(target, icon->icon_url,
335: HTIcon_alt_string(icon->icon_alt, YES),
336: NULL);
337: PUTC(' ');
338: }
2.7 secret 339: }
2.20 frystyk 340:
341: if (gtype == GOPHER_WWW) { /* Gopher pointer to W3 */
342: char *escaped = NULL;
343: escaped = HTEscape(selector, URL_PATH);
344: HTStartAnchor(target, NULL, escaped);
345: PUTS(name);
346: END(HTML_A);
347: free(escaped);
348: } else if (port) { /* Other types need port */
349: char *escaped = NULL;
350: char *address = NULL;
351: int addr_len;
352:
353: /* Calculate the length of the WWW-address */
354: if (selector && *selector) {
355: escaped = HTEscape(selector, URL_PATH);
356: addr_len = 15 + strlen(escaped) + strlen(host) + 1;
357: } else {
358: addr_len = 15 + strlen(host) + 1;
359: }
360: if ((address = (char *) malloc(addr_len)) == NULL)
361: outofmem(__FILE__, "Gopher ParseMenu");
362: *address = '\0';
363:
364: if (gtype == GOPHER_TELNET) {
365: if (escaped)
366: sprintf(address, "telnet://%s@%s/",
367: escaped, host);
368: else
369: sprintf(address, "telnet://%s/", host);
370: }
371: else if (gtype == GOPHER_TN3270) {
372: if (escaped)
373: sprintf(address, "tn3270://%s@%s/",
374: escaped, host);
375: else
376: sprintf(address, "tn3270://%s/", host);
377: } else {
378: if (escaped)
379: sprintf(address, "//%s/%c%s", host, gtype,
380: escaped);
381: else
382: sprintf(address, "//%s/%c", host, gtype);
1.1 timbl 383: }
2.20 frystyk 384:
385: /* Now output the anchor if not a Gopher error */
386: if (gtype != GOPHER_ERROR &&
387: !strstr(address, "error.host") &&
388: !strstr(address, "errorhost")) {
389: HTStartAnchor(target, NULL, address);
390: PUTS(name);
391: END(HTML_A);
392: } else
2.28 ! frystyk 393: PUTS(name); /* Just put it out, but skip type */
2.20 frystyk 394: FREE(address);
395: FREE(escaped);
396: } else { /* If parse error */
2.28 ! frystyk 397: if (PROT_TRACE)
2.20 frystyk 398: fprintf(stderr, "HTGopher.... Bad menu item, `%s\'\n",
399: chunk->data);
400: PUTS(chunk->data);
1.1 timbl 401: }
2.17 frystyk 402: PUTC('\n');
2.20 frystyk 403: HTChunkClear(chunk);
404: ++files; /* Update number of files */
405: }
406: } else
407: HTChunkPutc(chunk, ch);
408: }
409: if (ch < 0)
410: status = ch;
1.2 timbl 411:
2.20 frystyk 412: /* If no files and message is initialized then make error message,
413: else output the bottom part of the list*/
414: if (status != HT_INTERRUPTED) {
415: if (!files && status < 0) {
416: if (message) {
417: HTErrorAdd(request, ERR_FATAL, NO, HTERR_GOPHER_SERVER,
418: (void *) message, strlen(message), "parse_menu");
419: } else {
420: HTErrorAdd(request, ERR_FATAL, NO, HTERR_GOPHER_SERVER,
421: chunk->data, chunk->size, "parse_menu");
422: }
2.21 frystyk 423: } else if (target) {
2.28 ! frystyk 424: #ifdef OLD_CODE
2.20 frystyk 425: char *outstr;
426: if ((outstr = (char *) malloc(100)) == NULL)
427: outofmem(__FILE__, "parse_menu");
428: if (files == 0)
429: sprintf(outstr, "Empty directory");
430: else if (files == 1)
431: sprintf(outstr, "1 file");
432: else
433: sprintf(outstr, "%u files", files);
434: START(HTML_HR);
435: PUTS(outstr);
436: free(outstr);
2.28 ! frystyk 437: #endif /* OLD_CODE */
! 438: START(HTML_HR);
! 439: if (!files) PUTS("Empty Gopher Menu");
2.20 frystyk 440: END(HTML_PRE);
1.1 timbl 441:
2.20 frystyk 442: /* Put out any messages */
2.28 ! frystyk 443: if ((message || info) && HTDirInfo == HT_DIR_INFO_BOTTOM) {
! 444: if (message) PUTS(message);
! 445: if (info) PUTS(info);
2.20 frystyk 446: START(HTML_BR);
447: }
2.28 ! frystyk 448: END(HTML_BODY);
! 449: END(HTML_HTML);
2.21 frystyk 450: FREE_TARGET;
451: } else {
2.28 ! frystyk 452: if (PROT_TRACE)
2.21 frystyk 453: fprintf(stderr, "HTGopher.... Interrupted before any stream was put up.\n");
2.20 frystyk 454: }
2.17 frystyk 455: }
456:
2.20 frystyk 457: /* Cleanup */
458: FREE(message);
2.28 ! frystyk 459: FREE(info);
2.26 frystyk 460: HTInputSocket_free(gopher->isoc);
2.20 frystyk 461: HTChunkFree(chunk);
462: return status;
1.1 timbl 463: }
2.11 timbl 464:
465:
2.7 secret 466: /* Parse a Gopher CSO document
2.20 frystyk 467: ** ============================
468: **
469: ** Accepts an open socket to a CSO server waiting to send us
470: ** data and puts it on the screen in a reasonable manner.
471: **
472: ** Perhaps this data can be automatically linked to some
473: ** other source as well???
474: **
475: ** Taken from hacking by Lou Montulli@ukanaix.cc.ukans.edu
476: ** on XMosaic-1.1, and put on libwww 2.11 by Arthur Secret,
477: ** secret@dxcern.cern.ch.
478: **
479: ** Returns HT_LOADED on succed, HT_INTERRUPTED if interrupted and -1
480: ** if other error.
481: */
482: PRIVATE int parse_cso ARGS3(HTRequest *, request,
483: gopher_info *, gopher,
484: CONST char *, url)
2.7 secret 485: {
2.20 frystyk 486: int status = -1;
487: unsigned int records = 0;
2.17 frystyk 488: int ch;
2.20 frystyk 489: char *cur_code = NULL;
490: HTChunk *chunk = HTChunkCreate(128);
2.21 frystyk 491: HTStructured *target = NULL;
2.26 frystyk 492:
493: gopher->isoc = HTInputSocket_new(gopher->sockfd);
2.20 frystyk 494:
495: /* Start grabbing chars from the network */
2.26 frystyk 496: while ((ch = HTInputSocket_getCharacter(gopher->isoc)) >= 0) {
2.20 frystyk 497: if (ch == CR || ch == LF) {
498: if (chunk->size) {
499: /* OK we now have a line in 'p' lets parse it and print it */
500: char *strptr;
501: HTChunkTerminate(chunk);
502: strptr = chunk->data;
503:
504: /* If line begins with a 1, then more data is coming, so we
505: put out the title */
506: if (*strptr == '1' ||
507: !strncmp(strptr, "501", 3) || !strncmp(strptr, "502", 3)) {
2.21 frystyk 508:
509: /* Put up new stream */
510: target = HTML_new(request, NULL, WWW_HTML,
511: request->output_format,
512: request->output_stream);
2.20 frystyk 513: START(HTML_H1);
514: PUTS("CSO Search Results");
515: END(HTML_H1);
516:
517: /* Output the header line of the list */
518: START(HTML_PRE); /* To make it look as the other headers */
519: if (!icon_blank) icon_blank = icon_unknown;
520: if (HTDirShowMask & HT_DIR_SHOW_ICON && icon_blank) {
521: HTMLPutImg(target, icon_blank->icon_url,
522: HTIcon_alt_string(icon_blank->icon_alt, NO),
523: NULL);
524: }
525: PUTC(' ');
526: PUTS("Record");
527: PUTC('\n');
528: START(HTML_HR);
529: PUTC('\n');
530: END(HTML_PRE);
531: }
2.7 secret 532:
2.20 frystyk 533: /* Break on line that begins with a 2. It's the end of data. */
534: if (*strptr == '2') {
535: status = HT_LOADED;
536: break;
537: }
538:
539: /* Lines beginning with 5 are errors, generate msg and quit */
540: if (*strptr == '5') {
541: char *msgptr = strchr(chunk->data, ':');
542: if (!msgptr)
543: msgptr = chunk->data;
544: else
545: ++msgptr;
546: if (!strncmp(strptr, "501", 3)) /* No entries */
547: status = HT_LOADED;
548: else if (!strncmp(strptr, "502", 3)) { /* Too many */
549: status = HT_LOADED;
550: PUTS(msgptr);
551: } else {
552: HTErrorAdd(request, ERR_FATAL, NO, HTERR_CSO_SERVER,
553: (void *) msgptr,
554: strlen(msgptr), "parse_cso");
555: }
556: break;
557: }
558:
559: if(*strptr == '-') {
560: /* data lines look like -200:#:
561: * where # is the search result number and can be
562: * multiple digits (infinate?)
563: * find the second colon and check the digit to the
564: * left of it to see if they are diferent
565: * if they are then a different person is starting.
566: * make this line an <h2>
2.7 secret 567: */
2.20 frystyk 568: char *code; /* format: -200:code:field:value */
569: char *field;
570: char *value;
571: if ((code = strchr(strptr, ':')) != NULL &&
572: (field = strchr(++code, ':')) != NULL) {
573: *field++ = '\0';
574:
575: /* Let's do a strcmp instead of numbers */
576: if (!records) { /* Header of first record */
577: records++;
578: START(HTML_H2);
579: PUTS("Record 1");
580: END(HTML_H2);
581: START(HTML_DL);
582: } else if (cur_code && strcmp(code, cur_code)) {
583: char recstr[20];
584: records++;
585: END(HTML_DL);
586: START(HTML_H3);
587: PUTS("Record ");
2.23 frystyk 588: sprintf(recstr, "%u", records);
2.20 frystyk 589: PUTS(recstr);
590: END(HTML_H3);
591: START(HTML_DL);
592: } else
593: START(HTML_DT);
594:
595: /* I'm not sure whether the name field comes in any
596: * special order or if its even required in a
597: * record, so for now the first line is the header
598: * no matter what it is (it's almost always the
599: * alias)
2.7 secret 600: */
2.20 frystyk 601: if ((value = strchr(field, ':')) == NULL)
602: value = "Empty?";
603: else
604: *value++ = '\0';
605: {
606: char *strip = HTStrip(field);
607: PUTS(strip);
608: START(HTML_DD);
609: strip = HTStrip(value);
610: PUTS(strip);
611: }
2.7 secret 612:
2.20 frystyk 613: /* save the code for comparison on the next pass */
614: StrAllocCopy(cur_code, code);
615: }
616: } /* end data line */
617: HTChunkClear(chunk);
618: } /* end new line */
619: } else
620: HTChunkPutc(chunk, ch);
621: }
622: if (ch < 0)
623: status = ch;
624:
625: /* Put out the bottom line */
626: if (status != HT_INTERRUPTED) {
2.21 frystyk 627: if (target) {
628: char *outstr;
629: if ((outstr = (char *) malloc(100)) == NULL)
630: outofmem(__FILE__, "parse_menu");
631: if (!records)
632: sprintf(outstr, "No records");
633: else if (records == 1)
634: sprintf(outstr, "1 record");
635: else
636: sprintf(outstr, "%u records", records);
637: START(HTML_PRE);
638: START(HTML_HR);
639: PUTS(outstr);
640: END(HTML_PRE);
641: free(outstr);
642: FREE_TARGET;
643: } else {
2.28 ! frystyk 644: if (PROT_TRACE)
! 645: fprintf(stderr, "HTGopher.... Interrupted before any stream was put up.\n");
2.21 frystyk 646: }
2.20 frystyk 647: }
648:
649: /* Clean up */
2.26 frystyk 650: HTInputSocket_free(gopher->isoc);
2.20 frystyk 651: HTChunkFree(chunk);
652: FREE(cur_code);
653: return status;
654: }
2.7 secret 655:
1.1 timbl 656:
657: /* Display a Gopher Index document
2.20 frystyk 658: ** -------------------------------
659: */
660: PRIVATE void display_index ARGS2(HTRequest *, request,
661: CONST char *, url)
1.1 timbl 662: {
2.20 frystyk 663: HTStructured *target = HTML_new(request, NULL, WWW_HTML,
664: request->output_format,
665: request->output_stream);
2.18 luotonen 666:
1.2 timbl 667: START(HTML_H1);
2.20 frystyk 668: PUTS("Searchable Gopher Index");
1.2 timbl 669: END(HTML_H1);
2.7 secret 670: START(HTML_ISINDEX);
2.20 frystyk 671: if (!HTAnchor_title(request->anchor))
672: HTAnchor_setTitle(request->anchor, url);
2.7 secret 673: FREE_TARGET;
674: return;
675: }
676:
677:
678: /* Display a CSO index document
679: ** -------------------------------
680: */
2.20 frystyk 681: PRIVATE void display_cso ARGS2(HTRequest *, request,
682: CONST char *, url)
2.7 secret 683: {
2.20 frystyk 684: HTStructured *target = HTML_new(request, NULL, WWW_HTML,
685: request->output_format,
686: request->output_stream);
2.7 secret 687: START(HTML_H1);
2.20 frystyk 688: PUTS("Searchable Index of a CSO Name Server");
2.7 secret 689: END(HTML_H1);
690: START(HTML_ISINDEX);
2.20 frystyk 691: if (!HTAnchor_title(request->anchor))
692: HTAnchor_setTitle(request->anchor, url);
1.2 timbl 693: FREE_TARGET;
1.1 timbl 694: return;
695: }
696:
697:
2.20 frystyk 698:
699: /* HTGopher_send_cmd
1.1 timbl 700: **
2.20 frystyk 701: ** This function creates a socket and writes the gopher command to it.
702: ** The command must be terminated with <CRLF>
703: **
704: ** Returns 0 on OK, else <0 but does NOT close the connection
1.1 timbl 705: */
2.26 frystyk 706: PRIVATE int HTGopher_send_cmd ARGS3(gopher_info *, gopher,
2.20 frystyk 707: char *, url,
2.26 frystyk 708: char *, command)
1.1 timbl 709: {
2.20 frystyk 710: int status = 0;
2.26 frystyk 711: if (!gopher || !command) {
2.28 ! frystyk 712: if (PROT_TRACE)
2.20 frystyk 713: fprintf(stderr, "Gopher Tx... Bad argument!\n");
714: return -1;
715: }
2.26 frystyk 716: if ((status = HTDoConnect((HTNetInfo *) gopher, url, GOPHER_PORT,
2.28 ! frystyk 717: NULL, NO)) < 0) {
! 718: if (PROT_TRACE)
2.20 frystyk 719: fprintf(stderr, "HTLoadGopher Connection not established!\n");
720: return status;
721: }
2.28 ! frystyk 722: if (PROT_TRACE)
2.26 frystyk 723: fprintf(stderr, "Gopher...... Connected, socket %d\n", gopher->sockfd);
2.20 frystyk 724:
725: /* Write the command to the socket */
726: #ifdef NOT_ASCII
727: {
728: char * p;
729: for(p = command; *p; p++) {
730: *p = TOASCII(*p);
1.1 timbl 731: }
732: }
2.20 frystyk 733: #endif
2.28 ! frystyk 734: if (PROT_TRACE)
2.26 frystyk 735: fprintf(stderr, "Gopher Tx... %s", command);
736: if ((status = NETWRITE(gopher->sockfd, command,
737: (int) strlen(command))) < 0) {
2.28 ! frystyk 738: if (PROT_TRACE)
! 739: fprintf(stderr, "Gopher...... Error sending command: %s\n",
! 740: command);
2.26 frystyk 741: HTErrorSysAdd(gopher->request, ERR_FATAL, NO, "NETWRITE");
2.20 frystyk 742: } else
743: status = 0;
744: return status;
1.1 timbl 745: }
746:
747:
748: /* Load by name HTLoadGopher
749: ** ============
750: **
2.24 frystyk 751: ** Given a hypertext address, this routine loads a gopher document
752: **
753: ** On entry,
754: ** request This is the request structure
755: ** On exit,
756: ** returns <0 Error has occured
757: ** HT_LOADED OK
1.1 timbl 758: **
759: */
2.13 timbl 760: PUBLIC int HTLoadGopher ARGS1(HTRequest *, request)
1.1 timbl 761: {
2.22 frystyk 762: char *url;
2.20 frystyk 763: int status = -1;
2.26 frystyk 764: char *command = NULL;
2.20 frystyk 765: gopher_info *gopher;
766:
2.22 frystyk 767: if (!request || !request->anchor) {
2.28 ! frystyk 768: if (PROT_TRACE) fprintf(stderr, "HTLoadGopher Bad argument\n");
2.20 frystyk 769: return -1;
770: }
2.22 frystyk 771: url = HTAnchor_physical(request->anchor);
2.28 ! frystyk 772: if (PROT_TRACE) fprintf(stderr, "HTGopher.... Looking for `%s\'\n", url);
2.20 frystyk 773:
2.26 frystyk 774: /* Initiate a new gopher structure and bind to resuest structure */
2.20 frystyk 775: if ((gopher = (gopher_info *) calloc(1, sizeof(gopher_info))) == NULL)
776: outofmem(__FILE__, "HTLoadGopher");
2.26 frystyk 777: gopher->sockfd = -1;
778: gopher->request = request;
779: request->net_info = (HTNetInfo *) gopher;
2.20 frystyk 780: gopher->type = GOPHER_MENU;
1.1 timbl 781:
2.20 frystyk 782: /* Get entity type, and selector string and generate command */
1.1 timbl 783: {
2.20 frystyk 784: char *path = HTParse(url, "", PARSE_PATH);
785: char *selector = path;
786: char *query = NULL;
787: char *separator = NULL;
788: if (*selector)
789: gopher->type = *selector++; /* Pick up gtype */
790: if (gopher->type == GOPHER_INDEX) {
791: HTAnchor_setIndex(request->anchor); /* Search is allowed */
792: query = strchr(selector, '?'); /* Look for search string */
793:
794: /* Display local "cover page" only if no search requested */
795: if (!query || !*(query+1)) { /* No search required */
796: display_index(request, url);
797: status = HT_LOADED; /* Local function only */
798: } else {
799: *query++ = 0; /* Skip '?' */
800: separator = "\t";
1.1 timbl 801: }
2.20 frystyk 802: } else if (gopher->type == GOPHER_CSO) {
803: HTAnchor_setIndex(request->anchor); /* Search is allowed */
804: query = strchr(selector, '?'); /* Look for search string */
805:
806: /* Display local "cover page" only if no search requested */
807: if (!query || !*(query+1)) { /* No search required */
808: display_cso(request, url);
809: status = HT_LOADED; /* Local function only */
810: } else {
811: *query++ = 0; /* Skip '?' */
812: separator = "query ";
1.1 timbl 813: }
814: }
815:
2.20 frystyk 816: /* Now generate the final command */
817: if (status != HT_LOADED) {
2.24 frystyk 818: char crlf[3];
2.26 frystyk 819: StrAllocCopy(command, selector);
2.20 frystyk 820: if (query) {
821: char *p;
822: for (p=query; *p; p++) /* Remove plus signs 921006 */
823: if (*p == '+') *p = ' ';
2.26 frystyk 824: StrAllocCat(command, separator);
825: StrAllocCat(command, query);
2.20 frystyk 826: }
2.26 frystyk 827: HTUnEscape(command);
828: HTCleanTelnetString(command); /* Prevent security holes */
2.24 frystyk 829: *crlf = CR; /* Telnet termination */
830: *(crlf+1) = LF;
831: *(crlf+2) = '\0';
2.26 frystyk 832: StrAllocCat(command, crlf);
2.20 frystyk 833: }
834: free(path);
1.1 timbl 835: }
836:
2.20 frystyk 837: /* Now we must ask the server for real data :-( */
838: if (status != HT_LOADED) {
2.26 frystyk 839: if ((status = HTGopher_send_cmd(gopher, url, command)) == 0) {
2.20 frystyk 840:
841: /* Now read the data from the socket: */
842: switch (gopher->type) {
843: case GOPHER_HTML:
2.26 frystyk 844: status = HTParseSocket(WWW_HTML, gopher->sockfd, request);
2.20 frystyk 845: break;
846:
847: case GOPHER_GIF:
848: case GOPHER_IMAGE:
849: case GOPHER_PLUS_IMAGE:
2.26 frystyk 850: status = HTParseSocket(HTAtom_for("image/gif"), gopher->sockfd,
2.20 frystyk 851: request);
852: break;
853: case GOPHER_MENU:
854: case GOPHER_INDEX:
855: status = parse_menu(request, gopher, url);
856: break;
857:
858: case GOPHER_CSO:
859: status = parse_cso(request, gopher, url);
860: break;
861:
862: case GOPHER_MACBINHEX:
863: case GOPHER_PCBINHEX:
864: case GOPHER_UUENCODED:
865: case GOPHER_BINARY:
2.26 frystyk 866: {
2.20 frystyk 867: /* Do our own filetyping -- maybe we get lucky */
868: HTFormat format;
869: format = HTFileFormat(url, &request->content_encoding,
870: &request->content_language);
871: if (format) {
2.28 ! frystyk 872: if (PROT_TRACE)
! 873: fprintf(stderr, "Gopher...... Figured out content-type myself: %s\n", HTAtom_name(format));
2.26 frystyk 874: status = HTParseSocket(format, gopher->sockfd,
2.20 frystyk 875: request);
876: }
877: else {
2.28 ! frystyk 878: if (PROT_TRACE)
! 879: fprintf(stderr,"Gopher...... using www/unknown\n");
2.20 frystyk 880: /* Specifying WWW_UNKNOWN forces dump to local disk */
2.26 frystyk 881: HTParseSocket(WWW_UNKNOWN, gopher->sockfd, request);
2.20 frystyk 882: }
883: }
884: break;
885:
886: case GOPHER_SOUND:
887: case GOPHER_PLUS_SOUND:
2.26 frystyk 888: status = HTParseSocket(WWW_AUDIO, gopher->sockfd, request);
2.20 frystyk 889: break;
890:
891: case GOPHER_PLUS_MOVIE:
2.26 frystyk 892: status = HTParseSocket(WWW_VIDEO, gopher->sockfd, request);
2.20 frystyk 893: break;
2.26 frystyk 894:
895: /* Try and look at the suffix - maybe it is a PostScript file
896: so that we should start an externam viewer. */
2.20 frystyk 897: case GOPHER_TEXT:
2.26 frystyk 898: default:
899: {
900: HTFormat format;
901: format = HTFileFormat(url, &request->content_encoding,
902: &request->content_language);
903: if (format) {
2.28 ! frystyk 904: if (PROT_TRACE)
! 905: fprintf(stderr, "Gopher...... Figured out content-type myself: %s\n", HTAtom_name(format));
2.26 frystyk 906: status = HTParseSocket(format, gopher->sockfd,
907: request);
908: }
909: else {
910: status = HTParseSocket(WWW_PLAINTEXT, gopher->sockfd,
911: request);
912: }
913: }
2.20 frystyk 914: break;
2.16 luotonen 915: }
916: }
1.2 timbl 917:
2.20 frystyk 918: /* Close the connection */
2.26 frystyk 919: if (gopher->sockfd >= 0) {
2.28 ! frystyk 920: if (PROT_TRACE) fprintf(stderr, "Gopher...... Closing socket %d\n",
! 921: gopher->sockfd);
2.26 frystyk 922: if (NETCLOSE(gopher->sockfd) < 0)
923: status = HTErrorSysAdd(request, ERR_FATAL, NO, "NETCLOSE");
2.25 frystyk 924: }
2.20 frystyk 925: }
926: if (status == HT_INTERRUPTED) {
927: HTErrorAdd(request, ERR_WARNING, NO, HTERR_INTERRUPTED, NULL, 0,
928: "HTLoadGopher");
929: }
2.26 frystyk 930: FREE(command);
2.20 frystyk 931: free(gopher);
932:
933: if (status < 0 && status != HT_INTERRUPTED) {
2.21 frystyk 934: char *unescaped = NULL;
935: StrAllocCopy(unescaped, url);
936: HTUnEscape(unescaped);
937: HTErrorAdd(request, ERR_FATAL, NO, HTERR_INTERNAL, (void *) unescaped,
938: (int) strlen(unescaped), "HTLoadGopher");
2.20 frystyk 939: HTAnchor_clearIndex(request->anchor);
2.21 frystyk 940: free(unescaped);
2.20 frystyk 941: }
942: return status;
1.1 timbl 943: }
1.2 timbl 944:
2.10 timbl 945: GLOBALDEF PUBLIC HTProtocol HTGopher = { "gopher", HTLoadGopher, NULL, NULL };
1.1 timbl 946:
Webmaster