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