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