Annotation of libwww/Library/src/HTGopher.c, revision 2.42
2.32 frystyk 1: /* HTGopher.c
2: ** GOPHER ACCESS
3: **
2.37 frystyk 4: ** (c) COPYRIGHT MIT 1995.
2.32 frystyk 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
2.39 frystyk 11: ** HF, frystyk@w3.org
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.42 ! frystyk 17: ** Sep 95 HFN Made non-blocking and state stream oriented
1.1 timbl 18: */
19:
2.20 frystyk 20: /* Library include files */
2.34 frystyk 21: #include "tcp.h"
22: #include "HTUtils.h"
23: #include "HTString.h"
2.20 frystyk 24: #include "HTParse.h"
25: #include "HTTCP.h"
2.17 frystyk 26: #include "HTIcons.h"
2.42 ! frystyk 27: #include "HTReqMan.h"
2.38 frystyk 28: #include "HTSocket.h"
1.1 timbl 29: #include "HTFormat.h"
2.42 ! frystyk 30: #include "HTGuess.h"
2.20 frystyk 31: #include "HTError.h"
2.42 ! frystyk 32: #include "HTWriter.h"
! 33: #include "HTNet.h"
2.36 frystyk 34: #include "HTBind.h"
2.40 frystyk 35: #include "HTMLGen.h"
2.20 frystyk 36: #include "HTDirBrw.h"
37: #include "HTGopher.h" /* Implemented here */
38:
39: /* Macros and other defines */
40: #ifndef GOPHER_PORT
41: #define GOPHER_PORT 70 /* See protocol spec */
42: #endif
1.2 timbl 43:
2.20 frystyk 44: /* Hypertext object building machinery */
2.17 frystyk 45: #define PUTC(c) (*target->isa->put_character)(target, c)
46: #define PUTS(s) (*target->isa->put_string)(target, s)
47: #define START(e) (*target->isa->start_element)(target, e, 0, 0)
48: #define END(e) (*target->isa->end_element)(target, e)
2.20 frystyk 49:
50: /* Type definitions and global variables etc. local to this module */
51: typedef enum _HTGopherType {
2.42 ! frystyk 52: GT_TEXT = '0',
! 53: GT_MENU = '1',
! 54: GT_CSO = '2',
! 55: GT_ERROR = '3',
! 56: GT_MACBINHEX = '4',
! 57: GT_PCBINHEX = '5',
! 58: GT_UUENCODED = '6',
! 59: GT_INDEX = '7',
! 60: GT_TELNET = '8',
! 61: GT_BINARY = '9',
! 62: GT_GIF = 'g',
! 63: GT_HTML = 'h', /* HTML */
! 64: GT_INFO = 'i',
! 65: GT_SOUND = 's',
! 66: GT_WWW = 'w', /* W3 address */
! 67: GT_IMAGE = 'I',
! 68: GT_TN3270 = 'T',
! 69: GT_DUPLICATE = '+',
! 70: GT_PLUS_IMAGE = ':', /* Addition from Gopher Plus */
! 71: GT_PLUS_MOVIE = ';',
! 72: GT_PLUS_SOUND = '<',
! 73: GT_EOF = '.'
2.20 frystyk 74: } HTGopherType;
75:
2.42 ! frystyk 76: /* Final states have negative value */
! 77: typedef enum _GopherState {
! 78: GOPHER_ERROR = -3,
! 79: GOPHER_NO_DATA = -2,
! 80: GOPHER_GOT_DATA = -1,
! 81: GOPHER_BEGIN = 0,
! 82: GOPHER_NEED_CONNECTION,
! 83: GOPHER_NEED_REQUEST,
! 84: GOPHER_NEED_RESPONSE
! 85: } GopherState;
1.2 timbl 86:
2.42 ! frystyk 87: /* This is the context structure for the this module */
2.20 frystyk 88: typedef struct _gopher_info {
2.30 frystyk 89: HTGopherType type; /* Gopher item type */
2.42 ! frystyk 90: GopherState state;
! 91: char * cmd;
2.20 frystyk 92: } gopher_info;
1.1 timbl 93:
2.42 ! frystyk 94: #define MAX_GOPHER_LINE 256
! 95:
! 96: struct _HTStructured {
! 97: CONST HTStructuredClass * isa;
! 98: };
! 99:
! 100: struct _HTStream {
! 101: CONST HTStreamClass * isa;
! 102: HTStructured * target;
! 103: HTRequest * request;
! 104: HTSocketEOL state;
! 105: char * url;
! 106: BOOL pre; /* Preformatted mode? */
! 107: BOOL junk; /* For too long lines */
! 108: BOOL CSO;
! 109: char cso_rec[10]; /* CSO record number */
! 110: char buffer[MAX_GOPHER_LINE+1];
! 111: int buflen;
! 112: };
! 113:
2.20 frystyk 114: /* ------------------------------------------------------------------------- */
1.1 timbl 115:
2.42 ! frystyk 116: /* GopherIcon
! 117: ** ----------
2.20 frystyk 118: ** This function finds an appopriate icon for the item in the gopher
119: ** list. Actually it is only a shell build upon HTGetIcon().
2.17 frystyk 120: */
2.42 ! frystyk 121: PRIVATE HTIconNode *GopherIcon (HTGopherType type)
2.17 frystyk 122: {
2.36 frystyk 123: HTFormat content_type = NULL;
124: HTEncoding content_encoding = NULL;
2.42 ! frystyk 125: if (type == GT_MENU)
2.17 frystyk 126: return icon_dir ? icon_dir : icon_unknown;
2.42 ! frystyk 127: switch(type) {
! 128: case GT_TEXT:
2.17 frystyk 129: content_type = HTAtom_for("text/void");
130: break;
2.42 ! frystyk 131: case GT_IMAGE:
! 132: case GT_PLUS_IMAGE:
! 133: case GT_GIF:
2.17 frystyk 134: content_type = HTAtom_for("image/void");
135: break;
2.42 ! frystyk 136: case GT_WWW:
! 137: case GT_HTML:
2.17 frystyk 138: content_type = HTAtom_for("text/void");
139: break;
2.42 ! frystyk 140: case GT_SOUND:
! 141: case GT_PLUS_SOUND:
2.17 frystyk 142: content_type = HTAtom_for("audio/void");
143: break;
2.42 ! frystyk 144: case GT_PLUS_MOVIE:
2.20 frystyk 145: content_type = HTAtom_for("video/void");
2.17 frystyk 146: break;
2.42 ! frystyk 147: case GT_INDEX:
2.17 frystyk 148: content_type = HTAtom_for("application/x-gopher-index");
149: break;
2.42 ! frystyk 150: case GT_CSO:
2.17 frystyk 151: content_type = HTAtom_for("application/x-gopher-cso");
152: break;
2.42 ! frystyk 153: case GT_TELNET:
2.17 frystyk 154: content_type = HTAtom_for("application/x-gopher-telnet");
155: break;
2.42 ! frystyk 156: case GT_TN3270:
2.17 frystyk 157: content_type = HTAtom_for("application/x-gopher-tn3270");
158: break;
2.42 ! frystyk 159: case GT_DUPLICATE:
2.17 frystyk 160: content_type = HTAtom_for("application/x-gopher-duplicate");
161: break;
2.42 ! frystyk 162: case GT_ERROR:
2.17 frystyk 163: content_type = HTAtom_for("www/unknown");
164: break;
2.42 ! frystyk 165: case GT_MACBINHEX:
! 166: case GT_PCBINHEX:
! 167: case GT_UUENCODED:
2.36 frystyk 168: content_type = WWW_BINARY;
169: content_encoding = WWW_ENC_BASE64;
170: break;
2.42 ! frystyk 171: case GT_BINARY:
2.36 frystyk 172: content_type = WWW_BINARY;
173: break;
2.17 frystyk 174: default:
175: content_type = HTAtom_for("www/unknown");
176: break;
177: }
178: return HTGetIcon(S_IFMT & S_IFREG, content_type, content_encoding);
179: }
180:
2.42 ! frystyk 181: /* ------------------------------------------------------------------------- */
! 182: /* STREAMS */
! 183: /* ------------------------------------------------------------------------- */
2.17 frystyk 184:
2.42 ! frystyk 185: /* GopherTitle
! 186: ** -----------
! 187: ** Create the top part of the page
1.1 timbl 188: */
2.42 ! frystyk 189: PRIVATE BOOL GopherTitle (HTStream *me)
! 190: {
! 191: HTStructured *target = me->target;
! 192: char *str = NULL;
! 193: StrAllocCopy(str, me->CSO ? "CSO Search " : "GopherMenu");
2.28 frystyk 194:
2.42 ! frystyk 195: START(HTML_HTML);
! 196: START(HTML_HEAD);
! 197: START(HTML_TITLE);
! 198: if (me->CSO) {
! 199: char *keyword = strchr(me->url, '?');
! 200: if (keyword) {
! 201: StrAllocCat(str, "for ");
! 202: StrAllocCat(str, ++keyword);
! 203: }
! 204: }
! 205: PUTS(str);
! 206: END(HTML_TITLE);
! 207: END(HTML_HEAD);
2.26 frystyk 208:
2.42 ! frystyk 209: START(HTML_BODY);
! 210: START(HTML_H1);
! 211: PUTS(str);
! 212: END(HTML_H1);
! 213: free(str);
! 214: return YES;
! 215: }
1.1 timbl 216:
2.42 ! frystyk 217: /* GopherBottom
! 218: ** ------------
! 219: ** Create the bottom part of the page
! 220: */
! 221: PRIVATE BOOL GopherBottom (HTStream *me)
! 222: {
! 223: HTStructured *target = me->target;
! 224: if (me->pre)
! 225: END(HTML_PRE);
! 226: END(HTML_BODY);
! 227: END(HTML_HTML);
! 228: return YES;
! 229: }
2.17 frystyk 230:
2.42 ! frystyk 231: /* GopherMenuLine
! 232: ** --------------
! 233: ** Parses a Gopher Menu Line
! 234: ** Return YES if more data else NO
! 235: */
! 236: PRIVATE BOOL GopherMenuLine (HTStream *me, char *line)
! 237: {
! 238: HTStructured *target = me->target;
! 239: HTGopherType gtype = (HTGopherType) *line++;
! 240: if (PROT_TRACE)
! 241: fprintf(TDEST, "HTGopher.... Menu line: `%s\'\n", line);
! 242: if (gtype == GT_INFO) {
! 243: char *stop = strchr(line, '\t');
! 244: if (stop) *stop = '\0';
! 245: PUTS(line);
! 246: } else if (gtype == GT_ERROR) {
! 247: char *stop = strchr(line, '\t');
! 248: if (stop) *stop = '\0';
! 249: PUTS(line);
! 250: } else if ((strstr(line, "error.host") || strstr(line, "errorhost"))) {
! 251: char *stop = strchr(line, '\t'); /* Chop off error.host */
! 252: if (stop) *stop = '\0';
! 253: PUTS(line);
! 254: } else if (gtype == GT_EOF) {
! 255: return NO;
! 256: } else { /* Parse normal gopher menu line */
! 257: char *name = line; /* Get link information */
! 258: char *selector = strchr(name, '\t');
! 259: char *host = NULL;
! 260: char *port = NULL;
! 261: if (selector) {
! 262: *selector++ = '\0';
! 263: if ((host = strchr(selector, '\t'))) {
! 264: *host++ = '\0';
! 265: if ((port = strchr(host, '\t'))) {
! 266: char *junk;
! 267: *port = ':'; /* delimit host a la W3 */
! 268: if ((junk = strchr(port, '\t')) != NULL)
! 269: *junk = '\0'; /* Chop port */
! 270: if (*(port+1) == '0' && !*(port+2))
! 271: *port = '\0';
2.21 frystyk 272: }
2.42 ! frystyk 273: }
! 274: }
! 275: if (!me->pre) { /* For now we use preformatted listing */
! 276: START(HTML_PRE);
! 277: me->pre = YES;
! 278: }
! 279: if (HTDirShowMask & HT_DIR_SHOW_ICON) { /* Put out the icon */
! 280: HTIconNode *icon = GopherIcon(gtype);
! 281: if (icon && icon->icon_url) {
! 282: HTMLPutImg(target, icon->icon_url,
! 283: HTIcon_alt_string(icon->icon_alt, YES), NULL);
! 284: PUTC(' ');
! 285: }
! 286: }
! 287: if (gtype == GT_WWW) { /* Gopher pointer to W3 */
! 288: char *escaped = NULL;
! 289: escaped = HTEscape(selector, URL_PATH);
! 290: HTStartAnchor(target, NULL, escaped);
! 291: PUTS(name);
! 292: END(HTML_A);
! 293: free(escaped);
! 294: } else if (port) { /* Other types need port */
! 295: char *escaped = NULL;
! 296: char *address = NULL;
! 297: int addr_len;
! 298:
! 299: /* Calculate the length of the WWW-address */
! 300: if (selector && *selector) {
! 301: escaped = HTEscape(selector, URL_PATH);
! 302: addr_len = 15 + strlen(escaped) + strlen(host) + 1;
! 303: } else {
! 304: addr_len = 15 + strlen(host) + 1;
! 305: }
! 306: if ((address = (char *) malloc(addr_len)) == NULL)
! 307: outofmem(__FILE__, "GopherMenuLine");
! 308: *address = '\0';
! 309:
! 310: if (gtype == GT_TELNET) {
! 311: if (escaped)
! 312: sprintf(address, "telnet://%s@%s/", escaped, host);
! 313: else
! 314: sprintf(address, "telnet://%s/", host);
! 315: } else if (gtype == GT_TN3270) {
! 316: if (escaped)
! 317: sprintf(address, "tn3270://%s@%s/", escaped, host);
! 318: else
! 319: sprintf(address, "tn3270://%s/", host);
! 320: } else {
! 321: if (escaped)
! 322: sprintf(address, "//%s/%c%s", host, gtype, escaped);
! 323: else
! 324: sprintf(address, "//%s/%c", host, gtype);
! 325: }
! 326:
! 327: HTStartAnchor(target, NULL, address);
! 328: PUTS(name);
! 329: END(HTML_A);
! 330: FREE(address);
! 331: FREE(escaped);
! 332: PUTC('\n');
! 333: } else { /* If parse error */
! 334: if (PROT_TRACE)
! 335: fprintf(TDEST, "HTGopher.... Bad menu item, `%s\'\n", line);
! 336: }
! 337: }
! 338: return YES;
! 339: }
2.21 frystyk 340:
2.42 ! frystyk 341: /* GopherCSOLine
! 342: ** --------------
! 343: ** Parses a Gopher Menu Line
! 344: ** Return YES if more data else NO
! 345: */
! 346: PRIVATE BOOL GopherCSOLine (HTStream *me, char *line)
! 347: {
! 348: HTStructured *target = me->target;
! 349: if (*line == '1') { /* Information line */
! 350: char *start = strchr(line, ':');
! 351: start = start ? ++start : line;
! 352: PUTS(start);
! 353: } else if (*line == '2') { /* Transfer complete */
! 354: return NO;
! 355: } else if (*line == '5') { /* Error */
! 356: char *start = strchr(line, ':');
! 357: start = start ? ++start : line;
! 358: PUTS(start);
! 359: } else if (*line == '-') { /* data */
! 360: /* data lines look like '-200:code:field:value'
! 361: * where code is the search result number and can be
! 362: * multiple digits (infinte?)
! 363: * find the second colon and check the digit to the
! 364: * left of it to see if they are diferent
! 365: * if they are then a different person is starting.
! 366: */
! 367: char *code;
! 368: char *field;
! 369: if ((code = strchr(line, ':')) && (field = strchr(++code, ':'))) {
! 370: BOOL newrec = YES;
! 371: *field++ = '\0';
! 372: if (!*me->cso_rec) { /* Header of first record */
! 373: START(HTML_DL);
! 374: } else if (strcmp(me->cso_rec, code)) { /* Another new record */
! 375: START(HTML_B);
! 376: } else
! 377: newrec = NO;
! 378: START(HTML_DT);
! 379:
! 380: /* I'm not sure whether the name field comes in any
! 381: * special order or if its even required in a
! 382: * record, so for now the first line is the header
! 383: * no matter what it is (it's almost always the
! 384: * alias)
! 385: */
! 386: {
! 387: char *value = strchr(field, ':');
! 388: if (!value)
! 389: value = "Empty value";
! 390: else
! 391: *value++ = '\0';
! 392: {
! 393: char *strip = HTStrip(field);
! 394: PUTS(strip);
! 395: START(HTML_DD);
! 396: strip = HTStrip(value);
! 397: if (newrec) {
! 398: PUTS(strip);
! 399: END(HTML_B);
2.20 frystyk 400: } else
2.42 ! frystyk 401: PUTS(strip);
! 402: }
2.20 frystyk 403:
2.42 ! frystyk 404: /* Save the code for comparison on the next pass */
! 405: strcpy(me->cso_rec, code);
2.20 frystyk 406: }
2.42 ! frystyk 407: }
! 408: } else { /* Unknown line */
! 409: char *start = strchr(line, ':');
! 410: start = start ? ++start : line;
! 411: PUTS(start);
2.20 frystyk 412: }
2.42 ! frystyk 413: return YES;
! 414: }
1.2 timbl 415:
2.42 ! frystyk 416: /*
! 417: ** Searches for Gopher line until buffer fills up or a CRLF or LF is found
! 418: */
! 419: PRIVATE int GopherMenu_put_block ARGS3(HTStream *, me, CONST char*, b, int, l)
! 420: {
! 421: while (l-- > 0) {
! 422: if (me->state == EOL_FCR) {
! 423: if (*b == LF && me->buflen) {
! 424: if (!me->junk) {
! 425: BOOL cont;
! 426: *(me->buffer+me->buflen) = '\0';
! 427: cont = me->CSO ? GopherCSOLine(me, me->buffer) :
! 428: GopherMenuLine(me, me->buffer);
! 429: if (cont == NO) return HT_LOADED;
! 430: } else
! 431: me->junk = NO; /* back to normal */
! 432: me->buflen = 0;
! 433: me->state = EOL_BEGIN;
2.20 frystyk 434: }
2.42 ! frystyk 435: } else if (*b == CR) {
! 436: me->state = EOL_FCR;
! 437: } else if (*b == LF && me->buflen) {
! 438: if (!me->junk) {
! 439: BOOL cont;
! 440: *(me->buffer+me->buflen) = '\0';
! 441: cont = me->CSO ? GopherCSOLine(me, me->buffer) :
! 442: GopherMenuLine(me, me->buffer);
! 443: if (cont == NO) return HT_LOADED;
! 444: } else
! 445: me->junk = NO; /* back to normal */
! 446: me->buflen = 0;
! 447: me->state = EOL_BEGIN;
! 448: } else {
! 449: *(me->buffer+me->buflen++) = *b;
! 450: if (me->buflen >= MAX_GOPHER_LINE) {
! 451: if (PROT_TRACE)
! 452: fprintf(TDEST, "Gopher...... Line too long - ignored\n");
! 453: me->buflen = 0;
! 454: me->junk = YES;
2.20 frystyk 455: }
456: }
2.42 ! frystyk 457: b++;
2.17 frystyk 458: }
2.42 ! frystyk 459: return HT_OK;
! 460: }
2.17 frystyk 461:
2.42 ! frystyk 462: PRIVATE int GopherMenu_put_string ARGS2(HTStream *, me, CONST char*, s)
! 463: {
! 464: return GopherMenu_put_block(me, s, (int) strlen(s));
1.1 timbl 465: }
2.11 timbl 466:
2.42 ! frystyk 467: PRIVATE int GopherMenu_put_character ARGS2(HTStream *, me, char, c)
! 468: {
! 469: return GopherMenu_put_block(me, &c, 1);
! 470: }
2.11 timbl 471:
2.42 ! frystyk 472: PRIVATE int GopherMenu_flush ARGS1(HTStream *, me)
! 473: {
! 474: return (*me->target->isa->flush)(me->target);
! 475: }
! 476:
! 477: PRIVATE int GopherMenu_free ARGS1(HTStream *, me)
! 478: {
! 479: int status = HT_OK;
! 480: GopherBottom(me);
! 481: if ((status = (*me->target->isa->_free)(me->target)) == HT_WOULD_BLOCK)
! 482: return HT_WOULD_BLOCK;
! 483: free(me);
! 484: return HT_OK;
! 485: }
! 486:
! 487: PRIVATE int GopherMenu_abort ARGS2(HTStream *, me, HTError, e)
! 488: {
! 489: (*me->target->isa->abort)(me->target, e);
! 490: free(me);
! 491: if (PROT_TRACE)
! 492: fprintf(TDEST, "Gopher...... ABORTING...\n");
! 493: return HT_ERROR;
! 494: }
! 495:
! 496: /* Goper Menu Stream
! 497: ** -----------------
2.20 frystyk 498: */
2.42 ! frystyk 499: PRIVATE CONST HTStreamClass GopherMenuClass =
! 500: {
! 501: "GopherMenu",
! 502: GopherMenu_flush,
! 503: GopherMenu_free,
! 504: GopherMenu_abort,
! 505: GopherMenu_put_character,
! 506: GopherMenu_put_string,
! 507: GopherMenu_put_block
! 508: };
2.26 frystyk 509:
2.42 ! frystyk 510: /*
! 511: ** Stream for creating a HTML object out of a Gopher Menu object
! 512: */
! 513: PRIVATE HTStream * GopherMenu_new (HTRequest * request, char *url, BOOL CSO)
! 514: {
! 515: HTStream * me = (HTStream *) calloc(1, sizeof(HTStream));
! 516: if (!me) outofmem(__FILE__, "GopherMenu_new");
! 517: me->isa = &GopherMenuClass;
! 518: me->target = HTMLGenerator(request, NULL, WWW_HTML,
! 519: request->output_format, request->output_stream);
! 520: HTAnchor_setFormat(request->anchor, WWW_HTML);
! 521: me->request = request;
! 522: me->state = EOL_BEGIN;
! 523: me->url = url;
! 524: me->CSO = CSO;
! 525: GopherTitle(me);
! 526: return me;
! 527: }
2.20 frystyk 528:
2.42 ! frystyk 529: /* ------------------------------------------------------------------------- */
! 530: /* GOPHER STATE MACHINE */
! 531: /* ------------------------------------------------------------------------- */
2.7 secret 532:
2.42 ! frystyk 533: /* GopherCleanup
! 534: ** -------------
! 535: ** This function closes the connection and frees memory.
! 536: ** Returns YES if OK, else NO
! 537: */
! 538: PRIVATE BOOL GopherCleanup (HTRequest *req, int status)
! 539: {
! 540: HTNet *net = req->net;
! 541: gopher_info *gopher = (gopher_info *) net->context;
2.20 frystyk 542:
2.42 ! frystyk 543: /* Free stream with data TO network */
! 544: if (req->input_stream) {
! 545: if (status == HT_INTERRUPTED)
! 546: (*req->input_stream->isa->abort)(req->input_stream, NULL);
! 547: else
! 548: (*req->input_stream->isa->_free)(req->input_stream);
2.20 frystyk 549: }
550:
2.42 ! frystyk 551: /* Remove the request object and our own context structure for gopher */
! 552: HTNet_delete(net, status);
! 553: if (gopher) {
! 554: FREE(gopher->cmd);
! 555: free(gopher);
2.34 frystyk 556: }
2.42 ! frystyk 557: return YES;
2.20 frystyk 558: }
2.7 secret 559:
1.1 timbl 560: /* Display a Gopher Index document
2.20 frystyk 561: ** -------------------------------
2.42 ! frystyk 562: ** We create a small HTML object as we have no network activity
2.20 frystyk 563: */
2.42 ! frystyk 564: PRIVATE void display_index (HTRequest * request)
1.1 timbl 565: {
2.40 frystyk 566: HTStructured *target = HTMLGenerator(request, NULL, WWW_HTML,
2.42 ! frystyk 567: request->output_format,
! 568: request->output_stream);
! 569: /* Update anchor information */
2.36 frystyk 570: HTAnchor_setFormat(request->anchor, WWW_HTML);
2.42 ! frystyk 571: HTAnchor_setTitle(request->anchor, "Searchable Gopher Index");
! 572: /* @@@ We don't set Content-Length */
! 573:
2.34 frystyk 574: START(HTML_HTML);
575: START(HTML_HEAD);
576: START(HTML_TITLE);
577: PUTS("Searchable Gopher Index");
578: END(HTML_TITLE);
579: END(HTML_HEAD);
580: START(HTML_BODY);
581:
1.2 timbl 582: START(HTML_H1);
2.20 frystyk 583: PUTS("Searchable Gopher Index");
1.2 timbl 584: END(HTML_H1);
2.7 secret 585: START(HTML_ISINDEX);
2.34 frystyk 586: END(HTML_BODY);
587: END(HTML_HTML);
2.42 ! frystyk 588: (*target->isa->_free)(target);
2.7 secret 589: }
590:
591:
592: /* Display a CSO index document
593: ** -------------------------------
2.42 ! frystyk 594: ** We create a small HTML object as we have no network activity
2.7 secret 595: */
2.42 ! frystyk 596: PRIVATE void display_cso (HTRequest * request)
2.7 secret 597: {
2.40 frystyk 598: HTStructured *target = HTMLGenerator(request, NULL, WWW_HTML,
2.42 ! frystyk 599: request->output_format,
! 600: request->output_stream);
! 601: /* Update anchor information */
2.36 frystyk 602: HTAnchor_setFormat(request->anchor, WWW_HTML);
2.42 ! frystyk 603: HTAnchor_setTitle(request->anchor, "Searchable SCO Index");
! 604: /* @@@ We don't set Content-Length */
! 605:
2.34 frystyk 606: START(HTML_HTML);
607: START(HTML_HEAD);
608: START(HTML_TITLE);
609: PUTS("Searchable Index of a CSO Name Server");
610: END(HTML_TITLE);
611: END(HTML_HEAD);
612: START(HTML_BODY);
613:
2.7 secret 614: START(HTML_H1);
2.20 frystyk 615: PUTS("Searchable Index of a CSO Name Server");
2.7 secret 616: END(HTML_H1);
2.34 frystyk 617: PUTS("A CSO Name Server usually provides directory information about people.");
2.7 secret 618: START(HTML_ISINDEX);
2.34 frystyk 619: END(HTML_BODY);
620: END(HTML_HTML);
2.42 ! frystyk 621: (*target->isa->_free)(target);
1.1 timbl 622: }
623:
2.42 ! frystyk 624: /* HTLoadGopher
! 625: ** ------------
2.24 frystyk 626: ** Given a hypertext address, this routine loads a gopher document
627: **
628: ** On entry,
2.42 ! frystyk 629: ** request This is the request structure
! 630: ** On Exit
! 631: ** returns HT_ERROR Error has occured in call back
! 632: ** HT_OK Call back was OK
1.1 timbl 633: */
2.42 ! frystyk 634: PUBLIC int HTLoadGopher (SOCKET soc, HTRequest * request, SockOps ops)
1.1 timbl 635: {
2.42 ! frystyk 636: int status = HT_ERROR;
! 637: HTNet *net = request->net;
2.20 frystyk 638: gopher_info *gopher;
2.42 ! frystyk 639: char *url = HTAnchor_physical(request->anchor);
2.20 frystyk 640:
2.42 ! frystyk 641: /*
! 642: ** Initiate a new gopher structure and bind to request structure
! 643: ** This is actually state GOPHER_BEGIN, but it can't be in the state
! 644: ** machine as we need the structure first.
! 645: */
! 646: if (ops == FD_NONE) {
! 647: if (PROT_TRACE) fprintf(TDEST, "Gopher...... Looking for `%s\'\n",url);
! 648: if ((gopher = (gopher_info *) calloc(1, sizeof(gopher_info))) == NULL)
! 649: outofmem(__FILE__, "HTLoadGopher");
! 650: gopher->type = GT_MENU;
! 651: gopher->state = GOPHER_BEGIN;
! 652: net->context = gopher;
! 653: } if (ops == FD_CLOSE) { /* Interrupted */
! 654: GopherCleanup(request, HT_INTERRUPTED);
! 655: return HT_OK;
! 656: } else
! 657: gopher = (gopher_info *) net->context; /* Get existing copy */
! 658:
! 659: /* Now jump into the machine. We know the state from the previous run */
! 660: while (1) {
! 661: switch (gopher->state) {
! 662:
! 663: case GOPHER_BEGIN: /* Get entity type, and selector string */
! 664: {
! 665: char *path = HTParse(url, "", PARSE_PATH);
! 666: char *selector = path;
! 667: char *query = NULL;
! 668: char *separator = NULL;
! 669: if (*selector) gopher->type = (HTGopherType) *selector++;
! 670: if (gopher->type == GT_INDEX) {
! 671: HTAnchor_setIndex(request->anchor); /* Is index */
! 672: query = strchr(selector, '?');
! 673:
! 674: /* Display local cover page only if no search requested */
! 675: if (!query || !*(query+1)) { /* No search required */
! 676: display_index(request);
! 677: gopher->state = GOPHER_GOT_DATA;
! 678: break;
! 679: } else {
! 680: *query++ = 0; /* Skip '?' */
! 681: separator = "\t";
! 682: }
! 683: } else if (gopher->type == GT_CSO) {
! 684: HTAnchor_setIndex(request->anchor); /* Search is allowed */
! 685: query = strchr(selector, '?'); /* Look for search string */
! 686:
! 687: /* Display local cover page only if no search requested */
! 688: if (!query || !*(query+1)) { /* No search required */
! 689: display_cso(request);
! 690: gopher->state = GOPHER_GOT_DATA;
! 691: break;
! 692: } else {
! 693: *query++ = 0; /* Skip '?' */
! 694: *selector = 0;
! 695: separator = "query ";
! 696: }
! 697: }
2.20 frystyk 698:
2.42 ! frystyk 699: /* Now generate the final command */
! 700: {
! 701: char crlf[3];
! 702: StrAllocCopy(gopher->cmd, selector);
! 703: if (query) {
! 704: char *p;
! 705: for (p=query; *p; p++) /* Remove plus signs 921006 */
! 706: if (*p == '+') *p = ' ';
! 707: StrAllocCat(gopher->cmd, separator);
! 708: StrAllocCat(gopher->cmd, query);
! 709: }
! 710: HTUnEscape(gopher->cmd);
! 711: HTCleanTelnetString(gopher->cmd); /* Prevent sec holes */
! 712: *crlf = CR; /* Telnet termination */
! 713: *(crlf+1) = LF;
! 714: *(crlf+2) = '\0';
! 715: StrAllocCat(gopher->cmd, crlf);
! 716: }
! 717: free(path);
! 718: gopher->state = GOPHER_NEED_CONNECTION;
1.1 timbl 719: }
2.42 ! frystyk 720: break;
! 721:
! 722: case GOPHER_NEED_CONNECTION:
! 723: status = HTDoConnect(net, url, GOPHER_PORT, NULL, NO);
! 724: if (status == HT_OK) {
! 725: if (PROT_TRACE)
! 726: fprintf(TDEST, "Gopher...... Connected, socket %d\n",
! 727: net->sockfd);
! 728:
! 729: /* Set up stream TO network */
! 730: request->input_stream = HTWriter_new(net, YES);
! 731:
! 732: /* Set up stream FROM network and corresponding read buffer */
! 733: net->isoc = HTInputSocket_new(net->sockfd);
! 734: if (gopher->type == GT_MENU) {
! 735: if (!HTImProxy && request->output_format == WWW_SOURCE)
! 736: net->target = request->output_stream;
! 737: else
! 738: net->target = GopherMenu_new(request, url, NO);
! 739: } else if (gopher->type == GT_CSO) {
! 740: if (!HTImProxy && request->output_format == WWW_SOURCE)
! 741: net->target = request->output_stream;
! 742: else
! 743: net->target = GopherMenu_new(request, url, YES);
! 744: } else {
! 745: /* This is probably better than the gopher types */
! 746: net->target = HTGuess_new(request, NULL,WWW_UNKNOWN,
! 747: request->output_format,
! 748: request->output_stream);
! 749: }
! 750: gopher->state = GOPHER_NEED_REQUEST;
! 751: } else if (status == HT_WOULD_BLOCK)
! 752: return HT_OK;
! 753: else
! 754: gopher->state = GOPHER_ERROR;
! 755: break;
! 756:
! 757: case GOPHER_NEED_REQUEST:
! 758: if (PROT_TRACE) fprintf(TDEST, "Gopher Tx... `%s\'", gopher->cmd);
! 759: status = (*request->input_stream->isa->put_block)
! 760: (request->input_stream, gopher->cmd, strlen(gopher->cmd));
! 761: if (status == HT_WOULD_BLOCK)
! 762: return HT_OK;
! 763: else if (status == HT_ERROR)
! 764: gopher->state = GOPHER_ERROR;
! 765: else
! 766: gopher->state = GOPHER_NEED_RESPONSE;
! 767: break;
1.1 timbl 768:
2.42 ! frystyk 769: case GOPHER_NEED_RESPONSE:
! 770: status = HTSocketRead(request, net->target);
! 771: if (status == HT_WOULD_BLOCK)
! 772: return HT_OK;
! 773: else if (status == HT_LOADED)
! 774: gopher->state = GOPHER_GOT_DATA;
! 775: else
! 776: gopher->state = GOPHER_ERROR;
! 777: break;
1.2 timbl 778:
2.42 ! frystyk 779: case GOPHER_GOT_DATA:
! 780: GopherCleanup(request, HT_LOADED);
! 781: return HT_OK;
! 782: break;
! 783:
! 784: case GOPHER_NO_DATA:
! 785: GopherCleanup(request, HT_NO_DATA);
! 786: return HT_OK;
! 787: break;
! 788:
! 789: case GOPHER_ERROR:
! 790: GopherCleanup(request, HT_ERROR);
! 791: return HT_OK;
! 792: break;
2.25 frystyk 793: }
2.42 ! frystyk 794: } /* while(1) */
1.1 timbl 795: }
Webmaster