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