Annotation of libwww/Library/src/HTGopher.c, revision 2.32.2.3
2.32 frystyk 1: /* HTGopher.c
2: ** GOPHER ACCESS
3: **
4: ** (c) COPYRIGHT CERN 1994.
5: ** Please first read the full copyright statement in the file COPYRIGH.
1.1 timbl 6: **
7: ** History:
8: ** 26 Sep 90 Adapted from other accesses (News, HTTP) TBL
9: ** 29 Nov 91 Downgraded to C, for portable implementation.
2.17 frystyk 10: ** 28 Apr 94 target no more global and icons implemented
11: ** HF, frystyk@dxcern.cern.ch
2.19 luotonen 12: ** 2 May 94 Fixed possible security hole when the URL contains
13: ** a newline, that could cause multiple commands to be
14: ** sent to a Gopher server. AL, luotonen@www.cern.ch
2.20 frystyk 15: ** 12 May 94 Checked and made ready for multi-threads, Frystyk
2.27 duns 16: ** 8 Jul 94 FM Insulate free() from _free structure element.
2.21 frystyk 17: **
18: ** NOTE:
19: ** When parsing a gopher menu, we can't use the default HTParseSocket
20: ** but we will hav eto take care of the stram ourselves :-(
21: **
1.1 timbl 22: */
23:
2.20 frystyk 24: /* Library include files */
2.32.2.3! frystyk 25: #include "tcp.h"
2.20 frystyk 26: #include "HTUtils.h"
2.32.2.3! frystyk 27: #include "HTString.h"
! 28: #include "HTParse.h"
2.20 frystyk 29: #include "HTTCP.h"
2.17 frystyk 30: #include "HTIcons.h"
2.20 frystyk 31: #include "HTAccess.h"
1.1 timbl 32: #include "HTFormat.h"
2.20 frystyk 33: #include "HTError.h"
34: #include "HTFile.h"
1.2 timbl 35: #include "HTML.h"
2.20 frystyk 36: #include "HTMLPDTD.h"
37: #include "HTDirBrw.h"
38: #include "HTGopher.h" /* Implemented here */
39:
40: /* Macros and other defines */
41: #ifndef GOPHER_PORT
42: #define GOPHER_PORT 70 /* See protocol spec */
43: #endif
1.2 timbl 44:
2.20 frystyk 45: /* Hypertext object building machinery */
2.17 frystyk 46: #define PUTC(c) (*target->isa->put_character)(target, c)
47: #define PUTS(s) (*target->isa->put_string)(target, s)
48: #define START(e) (*target->isa->start_element)(target, e, 0, 0)
49: #define END(e) (*target->isa->end_element)(target, e)
2.27 duns 50: #define FREE_TARGET (*target->isa->_free)(target)
2.20 frystyk 51:
52: /* Type definitions and global variables etc. local to this module */
53: typedef enum _HTGopherType {
54: GOPHER_TEXT = '0',
55: GOPHER_MENU = '1',
56: GOPHER_CSO = '2',
57: GOPHER_ERROR = '3',
58: GOPHER_MACBINHEX = '4',
59: GOPHER_PCBINHEX = '5',
60: GOPHER_UUENCODED = '6',
61: GOPHER_INDEX = '7',
62: GOPHER_TELNET = '8',
63: GOPHER_BINARY = '9',
64: GOPHER_GIF = 'g',
65: GOPHER_HTML = 'h', /* HTML */
2.28 frystyk 66: GOPHER_INFO = 'i',
2.20 frystyk 67: GOPHER_SOUND = 's',
68: GOPHER_WWW = 'w', /* W3 address */
69: GOPHER_IMAGE = 'I',
70: GOPHER_TN3270 = 'T',
71: GOPHER_DUPLICATE = '+',
72: GOPHER_PLUS_IMAGE = ':', /* Addition from Gopher Plus */
73: GOPHER_PLUS_MOVIE = ';',
74: GOPHER_PLUS_SOUND = '<'
75: } HTGopherType;
76:
1.2 timbl 77: struct _HTStructured {
78: CONST HTStructuredClass * isa;
79: /* ... */
80: };
81:
2.26 frystyk 82: /* This is the local definition of HTRequest->net_info */
2.20 frystyk 83: typedef struct _gopher_info {
2.32.2.3! frystyk 84: SOCKFD sockfd; /* Socket descripter */
2.30 frystyk 85: SockA sock_addr; /* SockA is defined in tcp.h */
86: HTInputSocket * isoc; /* Input buffer */
87: HTStream * target; /* Output stream */
88: HTChunk * transmit; /* Line to be send */
89: int addressCount; /* Attempts if multi-homed host */
90: time_t connecttime; /* Used on multihomed hosts */
91: struct _HTRequest * request; /* Link back to request structure */
2.28 frystyk 92:
2.30 frystyk 93: HTGopherType type; /* Gopher item type */
2.20 frystyk 94: } gopher_info;
1.1 timbl 95:
2.20 frystyk 96: /* ------------------------------------------------------------------------- */
1.1 timbl 97:
2.20 frystyk 98: /* get_gopher_icon
1.1 timbl 99: **
2.20 frystyk 100: ** This function finds an appopriate icon for the item in the gopher
101: ** list. Actually it is only a shell build upon HTGetIcon().
2.17 frystyk 102: **
103: */
104: PRIVATE HTIconNode *get_gopher_icon ARGS2(CONST char *, filename,
105: int, gopher_type)
106: {
107: HTFormat content_type = NULL;
108: HTAtom *content_encoding = NULL;
109:
110: if (gopher_type == GOPHER_MENU)
111: return icon_dir ? icon_dir : icon_unknown;
112:
113: switch(gopher_type) {
114: case GOPHER_TEXT:
115: content_type = HTAtom_for("text/void");
116: break;
2.20 frystyk 117: case GOPHER_IMAGE:
118: case GOPHER_PLUS_IMAGE:
2.17 frystyk 119: case GOPHER_GIF:
120: content_type = HTAtom_for("image/void");
121: break;
2.20 frystyk 122: case GOPHER_WWW:
2.17 frystyk 123: case GOPHER_HTML:
124: content_type = HTAtom_for("text/void");
125: break;
126: case GOPHER_SOUND:
2.20 frystyk 127: case GOPHER_PLUS_SOUND:
2.17 frystyk 128: content_type = HTAtom_for("audio/void");
129: break;
2.20 frystyk 130: case GOPHER_PLUS_MOVIE:
131: content_type = HTAtom_for("video/void");
2.17 frystyk 132: break;
133: case GOPHER_INDEX:
134: content_type = HTAtom_for("application/x-gopher-index");
135: break;
136: case GOPHER_CSO:
137: content_type = HTAtom_for("application/x-gopher-cso");
138: break;
139: case GOPHER_TELNET:
140: content_type = HTAtom_for("application/x-gopher-telnet");
141: break;
142: case GOPHER_TN3270:
143: content_type = HTAtom_for("application/x-gopher-tn3270");
144: break;
145: case GOPHER_DUPLICATE:
146: content_type = HTAtom_for("application/x-gopher-duplicate");
147: break;
148: case GOPHER_ERROR:
149: content_type = HTAtom_for("www/unknown");
150: break;
151: case GOPHER_MACBINHEX:
152: case GOPHER_PCBINHEX:
153: case GOPHER_UUENCODED:
154: case GOPHER_BINARY:
155: { /* Do our own filetyping -- maybe we get lucky */
156: HTAtom *language;
157: content_type = HTFileFormat(filename, &content_encoding,
158: &language);
159: }
160: default:
161: content_type = HTAtom_for("www/unknown");
162: break;
163: }
164: return HTGetIcon(S_IFMT & S_IFREG, content_type, content_encoding);
165: }
166:
167:
2.20 frystyk 168: /* parse_menu
169: **
170: ** This function parses a gopher menu and puts it into a iconized
171: ** list.
172: **
173: ** Returns HT_LOADED on succed, HT_INTERRUPTED if interrupted and -1
174: ** if other error.
1.1 timbl 175: **
176: */
2.20 frystyk 177: PRIVATE int parse_menu ARGS3(HTRequest *, request,
178: gopher_info *, gopher,
179: CONST char *, url)
180: #define TAB '\t'
181: #define HEX_ESCAPE '%'
1.1 timbl 182: {
2.20 frystyk 183: int status = -1;
2.17 frystyk 184: unsigned int files = 0;
185: int ch;
2.20 frystyk 186: HTChunk *chunk = HTChunkCreate(128);
187: char *message = NULL; /* For a gopher message */
2.28 frystyk 188: char *info = NULL; /* For gopher information send as `i' type */
189:
2.21 frystyk 190: HTStructured *target = NULL;
2.26 frystyk 191:
192: gopher->isoc = HTInputSocket_new(gopher->sockfd);
2.20 frystyk 193:
194: /* Output the list */
2.26 frystyk 195: while ((ch = HTInputSocket_getCharacter(gopher->isoc)) >= 0) {
2.20 frystyk 196: if (ch == CR || ch == LF) {
197: if (chunk->size) { /* Got some text */
198: char *name = NULL; /* Gopher menu fields */
199: char *selector = NULL;
200: char *host = NULL;
201: char *port = NULL;
202: char *strptr;
203: char *errptr;
204: char gtype;
205: HTChunkTerminate(chunk);
206: strptr = chunk->data; /* Scan it to parse it */
2.28 frystyk 207: if (PROT_TRACE)
2.32.2.3! frystyk 208: fprintf(TDEST, "HTGopher.... Menu item: `%s\'\n",
2.20 frystyk 209: chunk->data);
210: gtype = *strptr++;
211:
212: if (gtype == GOPHER_ERROR) {
2.28 frystyk 213: StrAllocCat(message, chunk->data+1);
2.31 frystyk 214: continue;
2.20 frystyk 215: }
2.28 frystyk 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.32.2.3! frystyk 404: fprintf(TDEST, "HTGopher.... Bad menu item, `%s\'\n",
2.20 frystyk 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.32.2.3! frystyk 459: fprintf(TDEST, "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.32.2.1 frystyk 498: char *keyword;
499: if ((keyword = strchr(url, '?')) != NULL)
500: keyword++;
2.26 frystyk 501:
502: gopher->isoc = HTInputSocket_new(gopher->sockfd);
2.20 frystyk 503:
504: /* Start grabbing chars from the network */
2.26 frystyk 505: while ((ch = HTInputSocket_getCharacter(gopher->isoc)) >= 0) {
2.20 frystyk 506: if (ch == CR || ch == LF) {
507: if (chunk->size) {
508: /* OK we now have a line in 'p' lets parse it and print it */
509: char *strptr;
510: HTChunkTerminate(chunk);
511: strptr = chunk->data;
512:
513: /* If line begins with a 1, then more data is coming, so we
514: put out the title */
515: if (*strptr == '1' ||
516: !strncmp(strptr, "501", 3) || !strncmp(strptr, "502", 3)) {
2.21 frystyk 517:
518: /* Put up new stream */
519: target = HTML_new(request, NULL, WWW_HTML,
520: request->output_format,
521: request->output_stream);
2.32.2.1 frystyk 522: START(HTML_HTML);
523: START(HTML_HEAD);
524: START(HTML_TITLE);
525: PUTS("CSO Search: ");
526: if (keyword)
527: PUTS(keyword);
528: else
529: PUTS("empty");
530: END(HTML_TITLE);
531: END(HTML_HEAD);
532: START(HTML_BODY);
533:
2.20 frystyk 534: START(HTML_H1);
2.32.2.1 frystyk 535: PUTS("CSO Search: ");
536: if (keyword)
537: PUTS(keyword);
538: else
539: PUTS("empty");
2.20 frystyk 540: END(HTML_H1);
541:
2.32.2.1 frystyk 542: #if 0
2.20 frystyk 543: /* Output the header line of the list */
544: START(HTML_PRE); /* To make it look as the other headers */
545: if (!icon_blank) icon_blank = icon_unknown;
546: if (HTDirShowMask & HT_DIR_SHOW_ICON && icon_blank) {
547: HTMLPutImg(target, icon_blank->icon_url,
548: HTIcon_alt_string(icon_blank->icon_alt, NO),
549: NULL);
550: }
551: PUTC(' ');
552: PUTS("Record");
553: PUTC('\n');
554: START(HTML_HR);
555: PUTC('\n');
556: END(HTML_PRE);
2.32.2.1 frystyk 557: #endif
2.20 frystyk 558: }
2.7 secret 559:
2.20 frystyk 560: /* Break on line that begins with a 2. It's the end of data. */
561: if (*strptr == '2') {
562: status = HT_LOADED;
563: break;
564: }
565:
566: /* Lines beginning with 5 are errors, generate msg and quit */
567: if (*strptr == '5') {
568: char *msgptr = strchr(chunk->data, ':');
569: if (!msgptr)
570: msgptr = chunk->data;
571: else
572: ++msgptr;
573: if (!strncmp(strptr, "501", 3)) /* No entries */
574: status = HT_LOADED;
575: else if (!strncmp(strptr, "502", 3)) { /* Too many */
576: status = HT_LOADED;
577: PUTS(msgptr);
578: } else {
579: HTErrorAdd(request, ERR_FATAL, NO, HTERR_CSO_SERVER,
580: (void *) msgptr,
581: strlen(msgptr), "parse_cso");
582: }
583: break;
584: }
585:
586: if(*strptr == '-') {
587: /* data lines look like -200:#:
588: * where # is the search result number and can be
589: * multiple digits (infinate?)
590: * find the second colon and check the digit to the
591: * left of it to see if they are diferent
592: * if they are then a different person is starting.
593: * make this line an <h2>
2.7 secret 594: */
2.20 frystyk 595: char *code; /* format: -200:code:field:value */
596: char *field;
597: char *value;
598: if ((code = strchr(strptr, ':')) != NULL &&
599: (field = strchr(++code, ':')) != NULL) {
600: *field++ = '\0';
601:
602: /* Let's do a strcmp instead of numbers */
603: if (!records) { /* Header of first record */
2.32.2.1 frystyk 604: #if 0
2.20 frystyk 605: START(HTML_H2);
606: PUTS("Record 1");
607: END(HTML_H2);
2.32.2.1 frystyk 608: #endif
2.20 frystyk 609: START(HTML_DL);
610: START(HTML_DT);
2.32.2.1 frystyk 611: } else {
612: if (cur_code && strcmp(code, cur_code))
613: START(HTML_P);
614: START(HTML_DT);
615: }
2.20 frystyk 616:
617: /* I'm not sure whether the name field comes in any
618: * special order or if its even required in a
619: * record, so for now the first line is the header
620: * no matter what it is (it's almost always the
621: * alias)
2.7 secret 622: */
2.20 frystyk 623: if ((value = strchr(field, ':')) == NULL)
624: value = "Empty?";
625: else
626: *value++ = '\0';
627: {
628: char *strip = HTStrip(field);
629: PUTS(strip);
630: START(HTML_DD);
631: strip = HTStrip(value);
2.32.2.1 frystyk 632: if (!records ||
633: (cur_code && strcmp(code, cur_code))) {
634: START(HTML_B);
635: PUTS(strip);
636: END(HTML_B);
637: } else
638: PUTS(strip);
2.20 frystyk 639: }
2.32.2.1 frystyk 640: records++;
2.7 secret 641:
2.20 frystyk 642: /* save the code for comparison on the next pass */
643: StrAllocCopy(cur_code, code);
644: }
645: } /* end data line */
646: HTChunkClear(chunk);
647: } /* end new line */
648: } else
649: HTChunkPutc(chunk, ch);
650: }
651: if (ch < 0)
652: status = ch;
653:
654: /* Put out the bottom line */
2.32.2.1 frystyk 655: #if 0
2.20 frystyk 656: if (status != HT_INTERRUPTED) {
2.21 frystyk 657: if (target) {
658: char *outstr;
659: if ((outstr = (char *) malloc(100)) == NULL)
660: outofmem(__FILE__, "parse_menu");
661: if (!records)
662: sprintf(outstr, "No records");
663: else if (records == 1)
664: sprintf(outstr, "1 record");
665: else
666: sprintf(outstr, "%u records", records);
667: START(HTML_PRE);
668: START(HTML_HR);
669: PUTS(outstr);
670: END(HTML_PRE);
671: free(outstr);
672: FREE_TARGET;
673: } else {
2.28 frystyk 674: if (PROT_TRACE)
2.32.2.3! frystyk 675: fprintf(TDEST, "HTGopher.... Interrupted before any stream was put up.\n");
2.21 frystyk 676: }
2.20 frystyk 677: }
2.32.2.1 frystyk 678: #endif
2.20 frystyk 679:
2.32.2.1 frystyk 680: if (target) {
681: if (records)
682: END(HTML_DL);
683: else
684: PUTS("Nothing matched you query");
685: END(HTML_BODY);
686: END(HTML_HTML);
687: FREE_TARGET;
688: }
2.20 frystyk 689: /* Clean up */
2.26 frystyk 690: HTInputSocket_free(gopher->isoc);
2.20 frystyk 691: HTChunkFree(chunk);
692: FREE(cur_code);
693: return status;
694: }
2.7 secret 695:
1.1 timbl 696:
697: /* Display a Gopher Index document
2.20 frystyk 698: ** -------------------------------
699: */
700: PRIVATE void display_index ARGS2(HTRequest *, request,
701: CONST char *, url)
1.1 timbl 702: {
2.20 frystyk 703: HTStructured *target = HTML_new(request, NULL, WWW_HTML,
704: request->output_format,
705: request->output_stream);
2.18 luotonen 706:
2.32.2.1 frystyk 707: START(HTML_HTML);
708: START(HTML_HEAD);
709: START(HTML_TITLE);
710: PUTS("Searchable Gopher Index");
711: END(HTML_TITLE);
712: END(HTML_HEAD);
713: START(HTML_BODY);
714:
1.2 timbl 715: START(HTML_H1);
2.20 frystyk 716: PUTS("Searchable Gopher Index");
1.2 timbl 717: END(HTML_H1);
2.7 secret 718: START(HTML_ISINDEX);
2.20 frystyk 719: if (!HTAnchor_title(request->anchor))
720: HTAnchor_setTitle(request->anchor, url);
2.32.2.1 frystyk 721: END(HTML_BODY);
722: END(HTML_HTML);
2.7 secret 723: FREE_TARGET;
724: return;
725: }
726:
727:
728: /* Display a CSO index document
729: ** -------------------------------
730: */
2.20 frystyk 731: PRIVATE void display_cso ARGS2(HTRequest *, request,
732: CONST char *, url)
2.7 secret 733: {
2.20 frystyk 734: HTStructured *target = HTML_new(request, NULL, WWW_HTML,
735: request->output_format,
736: request->output_stream);
2.32.2.1 frystyk 737: START(HTML_HTML);
738: START(HTML_HEAD);
739: START(HTML_TITLE);
740: PUTS("Searchable Index of a CSO Name Server");
741: END(HTML_TITLE);
742: END(HTML_HEAD);
743: START(HTML_BODY);
744:
2.7 secret 745: START(HTML_H1);
2.20 frystyk 746: PUTS("Searchable Index of a CSO Name Server");
2.7 secret 747: END(HTML_H1);
2.32.2.1 frystyk 748: PUTS("A CSO Name Server usually provides directory information about people.");
2.7 secret 749: START(HTML_ISINDEX);
2.20 frystyk 750: if (!HTAnchor_title(request->anchor))
751: HTAnchor_setTitle(request->anchor, url);
2.32.2.1 frystyk 752: END(HTML_BODY);
753: END(HTML_HTML);
1.2 timbl 754: FREE_TARGET;
1.1 timbl 755: return;
756: }
757:
758:
2.20 frystyk 759:
760: /* HTGopher_send_cmd
1.1 timbl 761: **
2.20 frystyk 762: ** This function creates a socket and writes the gopher command to it.
763: ** The command must be terminated with <CRLF>
764: **
765: ** Returns 0 on OK, else <0 but does NOT close the connection
1.1 timbl 766: */
2.26 frystyk 767: PRIVATE int HTGopher_send_cmd ARGS3(gopher_info *, gopher,
2.20 frystyk 768: char *, url,
2.26 frystyk 769: char *, command)
1.1 timbl 770: {
2.20 frystyk 771: int status = 0;
2.26 frystyk 772: if (!gopher || !command) {
2.28 frystyk 773: if (PROT_TRACE)
2.32.2.3! frystyk 774: fprintf(TDEST, "Gopher Tx... Bad argument!\n");
2.20 frystyk 775: return -1;
776: }
2.26 frystyk 777: if ((status = HTDoConnect((HTNetInfo *) gopher, url, GOPHER_PORT,
2.28 frystyk 778: NULL, NO)) < 0) {
779: if (PROT_TRACE)
2.32.2.3! frystyk 780: fprintf(TDEST, "HTLoadGopher Connection not established!\n");
2.20 frystyk 781: return status;
782: }
2.28 frystyk 783: if (PROT_TRACE)
2.32.2.3! frystyk 784: fprintf(TDEST, "Gopher...... Connected, socket %d\n", gopher->sockfd);
2.20 frystyk 785:
786: /* Write the command to the socket */
787: #ifdef NOT_ASCII
788: {
789: char * p;
790: for(p = command; *p; p++) {
791: *p = TOASCII(*p);
1.1 timbl 792: }
793: }
2.20 frystyk 794: #endif
2.28 frystyk 795: if (PROT_TRACE)
2.32.2.3! frystyk 796: fprintf(TDEST, "Gopher Tx... %s", command);
2.26 frystyk 797: if ((status = NETWRITE(gopher->sockfd, command,
798: (int) strlen(command))) < 0) {
2.28 frystyk 799: if (PROT_TRACE)
2.32.2.3! frystyk 800: fprintf(TDEST, "Gopher...... Error sending command: %s\n",
2.28 frystyk 801: command);
2.32.2.3! frystyk 802: HTErrorSysAdd(gopher->request, ERR_FATAL, socerrno, NO, "NETWRITE");
2.20 frystyk 803: } else
804: status = 0;
805: return status;
1.1 timbl 806: }
807:
808:
809: /* Load by name HTLoadGopher
810: ** ============
811: **
2.24 frystyk 812: ** Given a hypertext address, this routine loads a gopher document
813: **
814: ** On entry,
815: ** request This is the request structure
816: ** On exit,
817: ** returns <0 Error has occured
818: ** HT_LOADED OK
1.1 timbl 819: **
820: */
2.13 timbl 821: PUBLIC int HTLoadGopher ARGS1(HTRequest *, request)
1.1 timbl 822: {
2.22 frystyk 823: char *url;
2.20 frystyk 824: int status = -1;
2.26 frystyk 825: char *command = NULL;
2.20 frystyk 826: gopher_info *gopher;
827:
2.22 frystyk 828: if (!request || !request->anchor) {
2.32.2.3! frystyk 829: if (PROT_TRACE) fprintf(TDEST, "HTLoadGopher Bad argument\n");
2.20 frystyk 830: return -1;
831: }
2.22 frystyk 832: url = HTAnchor_physical(request->anchor);
2.32.2.3! frystyk 833: if (PROT_TRACE) fprintf(TDEST, "HTGopher.... Looking for `%s\'\n", url);
2.20 frystyk 834:
2.26 frystyk 835: /* Initiate a new gopher structure and bind to resuest structure */
2.20 frystyk 836: if ((gopher = (gopher_info *) calloc(1, sizeof(gopher_info))) == NULL)
837: outofmem(__FILE__, "HTLoadGopher");
2.32.2.3! frystyk 838: gopher->sockfd = INVSOC;
2.26 frystyk 839: gopher->request = request;
840: request->net_info = (HTNetInfo *) gopher;
2.20 frystyk 841: gopher->type = GOPHER_MENU;
1.1 timbl 842:
2.20 frystyk 843: /* Get entity type, and selector string and generate command */
1.1 timbl 844: {
2.20 frystyk 845: char *path = HTParse(url, "", PARSE_PATH);
846: char *selector = path;
847: char *query = NULL;
848: char *separator = NULL;
849: if (*selector)
2.29 frystyk 850: gopher->type = (HTGopherType) *selector++; /* Pick up gtype */
2.20 frystyk 851: if (gopher->type == GOPHER_INDEX) {
852: HTAnchor_setIndex(request->anchor); /* Search is allowed */
853: query = strchr(selector, '?'); /* Look for search string */
854:
855: /* Display local "cover page" only if no search requested */
856: if (!query || !*(query+1)) { /* No search required */
857: display_index(request, url);
858: status = HT_LOADED; /* Local function only */
859: } else {
860: *query++ = 0; /* Skip '?' */
861: separator = "\t";
1.1 timbl 862: }
2.20 frystyk 863: } else if (gopher->type == GOPHER_CSO) {
864: HTAnchor_setIndex(request->anchor); /* Search is allowed */
865: query = strchr(selector, '?'); /* Look for search string */
866:
867: /* Display local "cover page" only if no search requested */
868: if (!query || !*(query+1)) { /* No search required */
869: display_cso(request, url);
870: status = HT_LOADED; /* Local function only */
871: } else {
872: *query++ = 0; /* Skip '?' */
2.32.2.1 frystyk 873: *selector = 0;
2.20 frystyk 874: separator = "query ";
1.1 timbl 875: }
876: }
877:
2.20 frystyk 878: /* Now generate the final command */
879: if (status != HT_LOADED) {
2.24 frystyk 880: char crlf[3];
2.26 frystyk 881: StrAllocCopy(command, selector);
2.20 frystyk 882: if (query) {
883: char *p;
884: for (p=query; *p; p++) /* Remove plus signs 921006 */
885: if (*p == '+') *p = ' ';
2.26 frystyk 886: StrAllocCat(command, separator);
887: StrAllocCat(command, query);
2.20 frystyk 888: }
2.26 frystyk 889: HTUnEscape(command);
890: HTCleanTelnetString(command); /* Prevent security holes */
2.24 frystyk 891: *crlf = CR; /* Telnet termination */
892: *(crlf+1) = LF;
893: *(crlf+2) = '\0';
2.26 frystyk 894: StrAllocCat(command, crlf);
2.20 frystyk 895: }
896: free(path);
1.1 timbl 897: }
898:
2.20 frystyk 899: /* Now we must ask the server for real data :-( */
900: if (status != HT_LOADED) {
2.26 frystyk 901: if ((status = HTGopher_send_cmd(gopher, url, command)) == 0) {
2.20 frystyk 902:
903: /* Now read the data from the socket: */
904: switch (gopher->type) {
905: case GOPHER_HTML:
2.26 frystyk 906: status = HTParseSocket(WWW_HTML, gopher->sockfd, request);
2.20 frystyk 907: break;
908:
909: case GOPHER_GIF:
910: case GOPHER_IMAGE:
911: case GOPHER_PLUS_IMAGE:
2.26 frystyk 912: status = HTParseSocket(HTAtom_for("image/gif"), gopher->sockfd,
2.20 frystyk 913: request);
914: break;
915: case GOPHER_MENU:
916: case GOPHER_INDEX:
917: status = parse_menu(request, gopher, url);
918: break;
919:
920: case GOPHER_CSO:
921: status = parse_cso(request, gopher, url);
922: break;
923:
924: case GOPHER_MACBINHEX:
925: case GOPHER_PCBINHEX:
926: case GOPHER_UUENCODED:
927: case GOPHER_BINARY:
2.29 frystyk 928: { /* Do our own filetyping -- maybe we get lucky */
929: char *filename = HTParse(url, "",
930: PARSE_PATH+PARSE_PUNCTUATION);
2.32.2.2 frystyk 931: request->content_type = HTFileFormat(filename,
2.29 frystyk 932: &request->content_encoding,
933: &request->content_language);
2.32.2.2 frystyk 934: if (request->content_type) {
2.28 frystyk 935: if (PROT_TRACE)
2.32.2.3! frystyk 936: fprintf(TDEST, "Gopher...... Figured out content-type myself: %s\n", HTAtom_name(request->content_type));
2.32.2.2 frystyk 937: status = HTParseSocket(request->content_type,
938: gopher->sockfd, request);
2.20 frystyk 939: }
940: else {
2.28 frystyk 941: if (PROT_TRACE)
2.32.2.3! frystyk 942: fprintf(TDEST,"Gopher...... using www/unknown\n");
2.20 frystyk 943: /* Specifying WWW_UNKNOWN forces dump to local disk */
2.26 frystyk 944: HTParseSocket(WWW_UNKNOWN, gopher->sockfd, request);
2.20 frystyk 945: }
2.29 frystyk 946: free(filename);
2.20 frystyk 947: }
948: break;
949:
950: case GOPHER_SOUND:
951: case GOPHER_PLUS_SOUND:
2.26 frystyk 952: status = HTParseSocket(WWW_AUDIO, gopher->sockfd, request);
2.20 frystyk 953: break;
954:
955: case GOPHER_PLUS_MOVIE:
2.26 frystyk 956: status = HTParseSocket(WWW_VIDEO, gopher->sockfd, request);
2.20 frystyk 957: break;
2.26 frystyk 958:
959: /* Try and look at the suffix - maybe it is a PostScript file
2.29 frystyk 960: so that we should start an external viewer. */
2.20 frystyk 961: case GOPHER_TEXT:
2.26 frystyk 962: default:
2.29 frystyk 963: { /* Do our own filetyping -- maybe we get lucky */
964: char *filename = HTParse(url, "",
965: PARSE_PATH+PARSE_PUNCTUATION);
2.32.2.2 frystyk 966: request->content_type = HTFileFormat(filename,
2.29 frystyk 967: &request->content_encoding,
968: &request->content_language);
2.32.2.2 frystyk 969: if (request->content_type) {
2.28 frystyk 970: if (PROT_TRACE)
2.32.2.3! frystyk 971: fprintf(TDEST, "Gopher...... Figured out content-type myself: %s\n", HTAtom_name(request->content_type));
2.32.2.2 frystyk 972: status = HTParseSocket(request->content_type,
973: gopher->sockfd, request);
2.26 frystyk 974: }
975: else {
976: status = HTParseSocket(WWW_PLAINTEXT, gopher->sockfd,
977: request);
978: }
2.29 frystyk 979: free(filename);
2.26 frystyk 980: }
2.20 frystyk 981: break;
2.16 luotonen 982: }
983: }
1.2 timbl 984:
2.20 frystyk 985: /* Close the connection */
2.32.2.3! frystyk 986: if (gopher->sockfd != INVSOC) {
! 987: if (PROT_TRACE) fprintf(TDEST, "Gopher...... Closing socket %d\n",
2.28 frystyk 988: gopher->sockfd);
2.26 frystyk 989: if (NETCLOSE(gopher->sockfd) < 0)
2.32.2.3! frystyk 990: status = HTErrorSysAdd(request, ERR_FATAL, socerrno, NO,
! 991: "NETCLOSE");
2.25 frystyk 992: }
2.20 frystyk 993: }
994: if (status == HT_INTERRUPTED) {
2.32.2.3! frystyk 995: HTErrorAdd(request, ERR_WARN, NO, HTERR_INTERRUPTED, NULL, 0,
2.20 frystyk 996: "HTLoadGopher");
997: }
2.26 frystyk 998: FREE(command);
2.30 frystyk 999: gopher->request->net_info = NULL;
2.20 frystyk 1000: free(gopher);
1001:
1002: if (status < 0 && status != HT_INTERRUPTED) {
2.21 frystyk 1003: char *unescaped = NULL;
1004: StrAllocCopy(unescaped, url);
1005: HTUnEscape(unescaped);
1006: HTErrorAdd(request, ERR_FATAL, NO, HTERR_INTERNAL, (void *) unescaped,
1007: (int) strlen(unescaped), "HTLoadGopher");
2.20 frystyk 1008: HTAnchor_clearIndex(request->anchor);
2.21 frystyk 1009: free(unescaped);
2.20 frystyk 1010: }
1011: return status;
1.1 timbl 1012: }
1.2 timbl 1013:
2.30 frystyk 1014: GLOBALDEF PUBLIC HTProtocol HTGopher = {
1015: "gopher", SOC_BLOCK, HTLoadGopher, NULL, NULL
1016: };
1.1 timbl 1017:
Webmaster