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