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