Annotation of libwww/Library/src/HTGopher.c, revision 2.22

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

Webmaster