Annotation of XML/nanohttp.c, revision 1.1
1.1 ! daniel 1: /*
! 2: * nanohttp.c: minimalist HTTP implementation to fetch external subsets.
! 3: *
! 4: * See Copyright for the status of this software.
! 5: *
! 6: * Daniel.Veillard@w3.org
! 7: */
! 8:
! 9: #include <stdio.h>
! 10: #include <string.h>
! 11: #include <stdlib.h>
! 12: #include <unistd.h>
! 13: #include <sys/socket.h>
! 14: #include <netinet/in.h>
! 15: #include <arpa/inet.h>
! 16: #include <netdb.h>
! 17: #include <fcntl.h>
! 18: #include <errno.h>
! 19: #include <sys/time.h>
! 20: #include <sys/select.h>
! 21:
! 22: #define XML_NANO_HTTP_MAX_REDIR 10
! 23:
! 24: #define XML_NANO_HTTP_CHUNK 4096
! 25:
! 26: #define XML_NANO_HTTP_CLOSED 0
! 27: #define XML_NANO_HTTP_WRITE 1
! 28: #define XML_NANO_HTTP_READ 2
! 29: #define XML_NANO_HTTP_NONE 4
! 30:
! 31: typedef struct xmlNanoHTTPCtxt {
! 32: char *protocol; /* the protocol name */
! 33: char *hostname; /* the host name */
! 34: int port; /* the port */
! 35: char *path; /* the path within the URL */
! 36: int fd; /* the file descriptor for the socket */
! 37: int state; /* WRITE / READ / CLOSED */
! 38: char *out; /* buffer sent (zero terminated) */
! 39: char *outptr; /* index within the buffer sent */
! 40: char *in; /* the receiving buffer */
! 41: char *content; /* the start of the content */
! 42: char *inptr; /* the next byte to read from network */
! 43: char *inrptr; /* the next byte to give back to the client */
! 44: int inlen; /* len of the input buffer */
! 45: int last; /* return code for last operation */
! 46: int returnValue; /* the protocol return value */
! 47: char *contentType; /* the MIME type for the input */
! 48: char *location; /* the new URL in case of redirect */
! 49: } xmlNanoHTTPCtxt, *xmlNanoHTTPCtxtPtr;
! 50:
! 51: static void xmlNanoHTTPScanURL(xmlNanoHTTPCtxtPtr ctxt, const char *URL) {
! 52: const char *cur = URL;
! 53: char buf[4096];
! 54: int index = 0;
! 55: int port = 0;
! 56:
! 57: if (ctxt->protocol != NULL) {
! 58: free(ctxt->protocol);
! 59: ctxt->protocol = NULL;
! 60: }
! 61: if (ctxt->hostname != NULL) {
! 62: free(ctxt->hostname);
! 63: ctxt->hostname = NULL;
! 64: }
! 65: if (ctxt->path != NULL) {
! 66: free(ctxt->path);
! 67: ctxt->path = NULL;
! 68: }
! 69: buf[index] = 0;
! 70: while (*cur != 0) {
! 71: if ((cur[0] == ':') && (cur[1] == '/') && (cur[2] == '/')) {
! 72: buf[index] = 0;
! 73: ctxt->protocol = strdup(buf);
! 74: index = 0;
! 75: cur += 3;
! 76: break;
! 77: }
! 78: buf[index++] = *cur++;
! 79: }
! 80: if (*cur == 0) return;
! 81:
! 82: buf[index] = 0;
! 83: while (1) {
! 84: if (cur[0] == ':') {
! 85: buf[index] = 0;
! 86: ctxt->hostname = strdup(buf);
! 87: index = 0;
! 88: cur += 1;
! 89: while ((*cur >= '0') && (*cur <= '9')) {
! 90: port *= 10;
! 91: port += *cur - '0';
! 92: cur++;
! 93: }
! 94: if (port != 0) ctxt->port = port;
! 95: while ((cur[0] != '/') && (*cur != 0))
! 96: cur++;
! 97: break;
! 98: }
! 99: if ((*cur == '/') || (*cur == 0)) {
! 100: buf[index] = 0;
! 101: ctxt->hostname = strdup(buf);
! 102: index = 0;
! 103: break;
! 104: }
! 105: buf[index++] = *cur++;
! 106: }
! 107: if (*cur == 0)
! 108: ctxt->path = strdup("/");
! 109: else
! 110: ctxt->path = strdup(cur);
! 111: }
! 112:
! 113: static xmlNanoHTTPCtxtPtr xmlNanoHTTPNewCtxt(const char *URL) {
! 114: xmlNanoHTTPCtxtPtr ret;
! 115:
! 116: ret = (xmlNanoHTTPCtxtPtr) malloc(sizeof(xmlNanoHTTPCtxt));
! 117: if (ret == NULL) return(NULL);
! 118:
! 119: memset(ret, 0, sizeof(xmlNanoHTTPCtxt));
! 120: ret->port = 80;
! 121: ret->returnValue = 0;
! 122:
! 123: xmlNanoHTTPScanURL(ret, URL);
! 124:
! 125: return(ret);
! 126: }
! 127:
! 128: static void xmlNanoHTTPFreeCtxt(xmlNanoHTTPCtxtPtr ctxt) {
! 129: if (ctxt->hostname != NULL) free(ctxt->hostname);
! 130: if (ctxt->protocol != NULL) free(ctxt->protocol);
! 131: if (ctxt->path != NULL) free(ctxt->path);
! 132: if (ctxt->out != NULL) free(ctxt->out);
! 133: if (ctxt->in != NULL) free(ctxt->in);
! 134: if (ctxt->contentType != NULL) free(ctxt->contentType);
! 135: if (ctxt->location != NULL) free(ctxt->location);
! 136: ctxt->state = XML_NANO_HTTP_NONE;
! 137: if (ctxt->fd >= 0) close(ctxt->fd);
! 138: ctxt->fd = -1;
! 139: free(ctxt);
! 140: }
! 141:
! 142: static void xmlNanoHTTPSend(xmlNanoHTTPCtxtPtr ctxt) {
! 143: if (ctxt->state & XML_NANO_HTTP_WRITE)
! 144: ctxt->last = write(ctxt->fd, ctxt->outptr, strlen(ctxt->outptr));
! 145: }
! 146:
! 147: static int xmlNanoHTTPRecv(xmlNanoHTTPCtxtPtr ctxt) {
! 148: fd_set rfd;
! 149: struct timeval tv;
! 150:
! 151:
! 152: while (ctxt->state & XML_NANO_HTTP_READ) {
! 153: if (ctxt->in == NULL) {
! 154: ctxt->in = (char *) malloc(65000 * sizeof(char));
! 155: if (ctxt->in == NULL) {
! 156: ctxt->last = -1;
! 157: return(-1);
! 158: }
! 159: ctxt->inlen = 65000;
! 160: ctxt->inptr = ctxt->content = ctxt->inrptr = ctxt->in;
! 161: }
! 162: if (ctxt->inrptr > ctxt->in + XML_NANO_HTTP_CHUNK) {
! 163: int delta = ctxt->inrptr - ctxt->in;
! 164: int len = ctxt->inptr - ctxt->inrptr;
! 165:
! 166: memmove(ctxt->in, ctxt->inrptr, len);
! 167: ctxt->inrptr -= delta;
! 168: ctxt->content -= delta;
! 169: ctxt->inptr -= delta;
! 170: }
! 171: if ((ctxt->in + ctxt->inlen) < (ctxt->inptr + XML_NANO_HTTP_CHUNK)) {
! 172: int d_inptr = ctxt->inptr - ctxt->in;
! 173: int d_content = ctxt->content - ctxt->in;
! 174: int d_inrptr = ctxt->inrptr - ctxt->in;
! 175:
! 176: ctxt->inlen *= 2;
! 177: ctxt->in = (char *) realloc(ctxt->in, ctxt->inlen);
! 178: if (ctxt->in == NULL) {
! 179: ctxt->last = -1;
! 180: return(-1);
! 181: }
! 182: ctxt->inptr = ctxt->in + d_inptr;
! 183: ctxt->content = ctxt->in + d_content;
! 184: ctxt->inrptr = ctxt->in + d_inrptr;
! 185: }
! 186: ctxt->last = read(ctxt->fd, ctxt->inptr, XML_NANO_HTTP_CHUNK);
! 187: if (ctxt->last > 0) {
! 188: ctxt->inptr += ctxt->last;
! 189: return(ctxt->last);
! 190: }
! 191: if (ctxt->last == 0) {
! 192: return(0);
! 193: }
! 194: #ifdef EWOULDBLOCK
! 195: if ((ctxt->last == -1) && (errno != EWOULDBLOCK)) {
! 196: return 0;
! 197: }
! 198: #endif
! 199: tv.tv_sec=10;
! 200: tv.tv_usec=0;
! 201: FD_ZERO(&rfd);
! 202: FD_SET(ctxt->fd, &rfd);
! 203:
! 204: if(select(ctxt->fd+1, &rfd, NULL, NULL, &tv)<1)
! 205: return 0;
! 206: }
! 207: return(0);
! 208: }
! 209:
! 210: char *xmlNanoHTTPReadLine(xmlNanoHTTPCtxtPtr ctxt) {
! 211: static char buf[4096];
! 212: char *bp=buf;
! 213:
! 214: while(bp - buf < 4095) {
! 215: if(ctxt->inrptr == ctxt->inptr) {
! 216: if (xmlNanoHTTPRecv(ctxt) == 0) {
! 217: if (bp == buf)
! 218: return NULL;
! 219: else
! 220: *bp = 0;
! 221: return buf;
! 222: }
! 223: }
! 224: *bp = *ctxt->inrptr++;
! 225: if(*bp == '\n') {
! 226: *bp = 0;
! 227: return buf;
! 228: }
! 229: if(*bp != '\r')
! 230: bp++;
! 231: }
! 232: buf[4095] = 0;
! 233: return(buf);
! 234: }
! 235:
! 236:
! 237: static void xmlNanoHTTPScanAnswer(xmlNanoHTTPCtxtPtr ctxt, const char *line) {
! 238: const char *cur = line;
! 239:
! 240: if (line == NULL) return;
! 241:
! 242: if (!strncmp(line, "HTTP/", 5)) {
! 243: int version = 0;
! 244: int ret = 0;
! 245:
! 246: cur += 5;
! 247: while ((*cur >= '0') && (*cur <= '9')) {
! 248: version *= 10;
! 249: version += *cur - '0';
! 250: cur++;
! 251: }
! 252: if (*cur == '.') {
! 253: cur++;
! 254: if ((*cur >= '0') && (*cur <= '9')) {
! 255: version *= 10;
! 256: version += *cur - '0';
! 257: cur++;
! 258: }
! 259: while ((*cur >= '0') && (*cur <= '9'))
! 260: cur++;
! 261: } else
! 262: version *= 10;
! 263: if ((*cur != ' ') && (*cur != '\t')) return;
! 264: while ((*cur == ' ') || (*cur == '\t')) cur++;
! 265: if ((*cur < '0') || (*cur > '9')) return;
! 266: while ((*cur >= '0') && (*cur <= '9')) {
! 267: ret *= 10;
! 268: ret += *cur - '0';
! 269: cur++;
! 270: }
! 271: if ((*cur != 0) && (*cur != ' ') && (*cur != '\t')) return;
! 272: ctxt->returnValue = ret;
! 273: } else if (!strncmp(line, "Content-Type:", 13)) {
! 274: cur += 13;
! 275: while ((*cur == ' ') || (*cur == '\t')) cur++;
! 276: if (ctxt->contentType != NULL)
! 277: free(ctxt->contentType);
! 278: ctxt->contentType = strdup(cur);
! 279: } else if (!strncmp(line, "ContentType:", 12)) {
! 280: cur += 12;
! 281: if (ctxt->contentType != NULL) return;
! 282: while ((*cur == ' ') || (*cur == '\t')) cur++;
! 283: ctxt->contentType = strdup(cur);
! 284: } else if (!strncmp(line, "content-type:", 13)) {
! 285: cur += 13;
! 286: if (ctxt->contentType != NULL) return;
! 287: while ((*cur == ' ') || (*cur == '\t')) cur++;
! 288: ctxt->contentType = strdup(cur);
! 289: } else if (!strncmp(line, "contenttype:", 12)) {
! 290: cur += 12;
! 291: if (ctxt->contentType != NULL) return;
! 292: while ((*cur == ' ') || (*cur == '\t')) cur++;
! 293: ctxt->contentType = strdup(cur);
! 294: } else if (!strncmp(line, "Location:", 9)) {
! 295: cur += 9;
! 296: while ((*cur == ' ') || (*cur == '\t')) cur++;
! 297: if (ctxt->location != NULL)
! 298: free(ctxt->location);
! 299: ctxt->location = strdup(cur);
! 300: } else if (!strncmp(line, "location:", 9)) {
! 301: cur += 9;
! 302: if (ctxt->location != NULL) return;
! 303: while ((*cur == ' ') || (*cur == '\t')) cur++;
! 304: ctxt->location = strdup(cur);
! 305: }
! 306: }
! 307:
! 308: static int xmlNanoHTTPConnectAttempt(struct in_addr ia, int port)
! 309: {
! 310: int s=socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
! 311: struct sockaddr_in sin;
! 312: fd_set wfd;
! 313: struct timeval tv;
! 314:
! 315: if(s==-1) {
! 316: perror("socket");
! 317: return(-1);
! 318: }
! 319:
! 320: if(fcntl(s, F_SETFL, FNDELAY)==-1) {
! 321: perror("nonblocking");
! 322: close(s);
! 323: return(-1);
! 324: }
! 325:
! 326: sin.sin_family = AF_INET;
! 327: sin.sin_addr = ia;
! 328: sin.sin_port = htons(port);
! 329:
! 330: if((connect(s, (struct sockaddr *)&sin, sizeof(sin))==-1) &&
! 331: (errno != EINPROGRESS)) {
! 332: perror("connect");
! 333: close(s);
! 334: return(-1);
! 335: }
! 336:
! 337: tv.tv_sec = 60; /* We use 60 second timeouts for now */
! 338: tv.tv_usec = 0;
! 339:
! 340: FD_ZERO(&wfd);
! 341: FD_SET(s, &wfd);
! 342:
! 343: switch(select(s+1, NULL, &wfd, NULL, &tv))
! 344: {
! 345: case 0:
! 346: /* Time out */
! 347: close(s);
! 348: return(-1);
! 349: case -1:
! 350: /* Ermm.. ?? */
! 351: perror("select");
! 352: close(s);
! 353: return(-1);
! 354: }
! 355:
! 356: return s;
! 357: }
! 358:
! 359: int xmlNanoHTTPConnectHost(const char *host, int port)
! 360: {
! 361: struct hostent *h;
! 362: int i;
! 363: int s;
! 364:
! 365: h=gethostbyname(host);
! 366: if(h==NULL)
! 367: {
! 368: fprintf(stderr,"unable to resolve '%s'.\n", host);
! 369: return(-1);
! 370: }
! 371:
! 372:
! 373: for(i=0; h->h_addr_list[i]; i++)
! 374: {
! 375: struct in_addr ia;
! 376: memcpy(&ia, h->h_addr_list[i],4);
! 377: s = xmlNanoHTTPConnectAttempt(ia, port);
! 378: if(s != -1)
! 379: return s;
! 380: }
! 381: fprintf(stderr, "unable to connect to '%s'.\n", host);
! 382: return(-1);
! 383: }
! 384:
! 385: int xmlNanoHTTPOldFetch(const char *URL, const char *filename,
! 386: char **contentType) {
! 387: xmlNanoHTTPCtxtPtr ctxt;
! 388: char buf[4096];
! 389: int ret;
! 390: int fd;
! 391: char *p;
! 392: int head;
! 393: int nbRedirects = 0;
! 394: char *redirURL = NULL;
! 395:
! 396: retry:
! 397: if (redirURL == NULL)
! 398: ctxt = xmlNanoHTTPNewCtxt(URL);
! 399: else
! 400: ctxt = xmlNanoHTTPNewCtxt(redirURL);
! 401:
! 402: if ((ctxt->protocol == NULL) || (strcmp(ctxt->protocol, "http"))) {
! 403: xmlNanoHTTPFreeCtxt(ctxt);
! 404: if (redirURL != NULL) free(redirURL);
! 405: return(-1);
! 406: }
! 407: if (ctxt->hostname == NULL) {
! 408: xmlNanoHTTPFreeCtxt(ctxt);
! 409: if (redirURL != NULL) free(redirURL);
! 410: return(-1);
! 411: }
! 412: ret = xmlNanoHTTPConnectHost(ctxt->hostname, ctxt->port);
! 413: if (ret < 0) {
! 414: xmlNanoHTTPFreeCtxt(ctxt);
! 415: if (redirURL != NULL) free(redirURL);
! 416: return(-1);
! 417: }
! 418: ctxt->fd = ret;
! 419: snprintf(buf, sizeof(buf),"GET %s HTTP/1.0\r\nhost: %s\r\n\r\n",
! 420: ctxt->path, ctxt->hostname);
! 421: ctxt->outptr = ctxt->out = strdup(buf);
! 422: ctxt->state = XML_NANO_HTTP_WRITE;
! 423: xmlNanoHTTPSend(ctxt);
! 424: ctxt->state = XML_NANO_HTTP_READ;
! 425: head = 1;
! 426:
! 427: while ((p = xmlNanoHTTPReadLine(ctxt)) != NULL) {
! 428: if (head && (*p == 0)) {
! 429: head = 0;
! 430: ctxt->content = ctxt->inrptr;
! 431: break;
! 432: }
! 433: xmlNanoHTTPScanAnswer(ctxt, p);
! 434: if (p != NULL) printf("%s\n", p);
! 435: }
! 436: while (xmlNanoHTTPRecv(ctxt)) ;
! 437:
! 438: if (!strcmp(filename, "-"))
! 439: fd = 0;
! 440: else {
! 441: fd = open(filename, O_CREAT | O_WRONLY);
! 442: if (fd < 0) {
! 443: xmlNanoHTTPFreeCtxt(ctxt);
! 444: if (redirURL != NULL) free(redirURL);
! 445: return(-1);
! 446: }
! 447: }
! 448:
! 449: printf("Code %d, content-type '%s'\n\n",
! 450: ctxt->returnValue, ctxt->contentType);
! 451: if ((ctxt->location != NULL) && (ctxt->returnValue >= 300) &&
! 452: (ctxt->returnValue < 400)) {
! 453: printf("Redirect to: %s\n", ctxt->location);
! 454: if (nbRedirects < XML_NANO_HTTP_MAX_REDIR) {
! 455: nbRedirects++;
! 456: if (redirURL != NULL) free(redirURL);
! 457: redirURL = strdup(ctxt->location);
! 458: xmlNanoHTTPFreeCtxt(ctxt);
! 459: goto retry;
! 460: }
! 461: }
! 462:
! 463: write(fd, ctxt->content, ctxt->inptr - ctxt->content);
! 464: xmlNanoHTTPFreeCtxt(ctxt);
! 465: if (redirURL != NULL) free(redirURL);
! 466: return(0);
! 467: }
! 468:
! 469: void *
! 470: xmlNanoHTTPOpen(const char *URL, char **contentType) {
! 471: xmlNanoHTTPCtxtPtr ctxt;
! 472: char buf[4096];
! 473: int ret;
! 474: char *p;
! 475: int head;
! 476: int nbRedirects = 0;
! 477: char *redirURL = NULL;
! 478:
! 479: retry:
! 480: if (redirURL == NULL)
! 481: ctxt = xmlNanoHTTPNewCtxt(URL);
! 482: else {
! 483: ctxt = xmlNanoHTTPNewCtxt(redirURL);
! 484: free(redirURL);
! 485: redirURL = NULL;
! 486: }
! 487:
! 488: if ((ctxt->protocol == NULL) || (strcmp(ctxt->protocol, "http"))) {
! 489: xmlNanoHTTPFreeCtxt(ctxt);
! 490: if (redirURL != NULL) free(redirURL);
! 491: return(NULL);
! 492: }
! 493: if (ctxt->hostname == NULL) {
! 494: xmlNanoHTTPFreeCtxt(ctxt);
! 495: return(NULL);
! 496: }
! 497: ret = xmlNanoHTTPConnectHost(ctxt->hostname, ctxt->port);
! 498: if (ret < 0) {
! 499: xmlNanoHTTPFreeCtxt(ctxt);
! 500: return(NULL);
! 501: }
! 502: ctxt->fd = ret;
! 503: snprintf(buf, sizeof(buf),"GET %s HTTP/1.0\r\nhost: %s\r\n\r\n",
! 504: ctxt->path, ctxt->hostname);
! 505: ctxt->outptr = ctxt->out = strdup(buf);
! 506: ctxt->state = XML_NANO_HTTP_WRITE;
! 507: xmlNanoHTTPSend(ctxt);
! 508: ctxt->state = XML_NANO_HTTP_READ;
! 509: head = 1;
! 510:
! 511: while ((p = xmlNanoHTTPReadLine(ctxt)) != NULL) {
! 512: if (head && (*p == 0)) {
! 513: head = 0;
! 514: ctxt->content = ctxt->inrptr;
! 515: break;
! 516: }
! 517: xmlNanoHTTPScanAnswer(ctxt, p);
! 518:
! 519: if (p != NULL) printf("%s\n", p);
! 520: }
! 521:
! 522: if ((ctxt->location != NULL) && (ctxt->returnValue >= 300) &&
! 523: (ctxt->returnValue < 400)) {
! 524: printf("Redirect to: %s\n", ctxt->location);
! 525: while (xmlNanoHTTPRecv(ctxt)) ;
! 526: if (nbRedirects < XML_NANO_HTTP_MAX_REDIR) {
! 527: nbRedirects++;
! 528: redirURL = strdup(ctxt->location);
! 529: xmlNanoHTTPFreeCtxt(ctxt);
! 530: goto retry;
! 531: }
! 532: xmlNanoHTTPFreeCtxt(ctxt);
! 533: return(NULL);
! 534:
! 535: }
! 536:
! 537: printf("Code %d, content-type '%s'\n\n",
! 538: ctxt->returnValue, ctxt->contentType);
! 539:
! 540: return((void *) ctxt);
! 541: }
! 542:
! 543: int
! 544: xmlNanoHTTPRead(void *ctx, void *dest, int len) {
! 545: xmlNanoHTTPCtxtPtr ctxt = (xmlNanoHTTPCtxtPtr) ctx;
! 546:
! 547: if (ctx == NULL) return(-1);
! 548: if (dest == NULL) return(-1);
! 549: if (len <= 0) return(0);
! 550:
! 551: while (ctxt->inptr - ctxt->inrptr < len) {
! 552: if (xmlNanoHTTPRecv(ctxt) == 0) break;
! 553: }
! 554: if (ctxt->inptr - ctxt->inrptr < len)
! 555: len = ctxt->inptr - ctxt->inrptr;
! 556: memcpy(dest, ctxt->inrptr, len);
! 557: ctxt->inrptr += len;
! 558: return(len);
! 559: }
! 560:
! 561: void
! 562: xmlNanoHTTPClose(void *ctx) {
! 563: xmlNanoHTTPCtxtPtr ctxt = (xmlNanoHTTPCtxtPtr) ctx;
! 564:
! 565: if (ctx == NULL) return;
! 566:
! 567: xmlNanoHTTPFreeCtxt(ctxt);
! 568: }
! 569:
! 570: int xmlNanoHTTPFetch(const char *URL, const char *filename,
! 571: char **contentType) {
! 572: void *ctxt;
! 573: char buf[4096];
! 574: int fd;
! 575: int len;
! 576:
! 577: ctxt = xmlNanoHTTPOpen(URL, contentType);
! 578: if (ctxt == NULL) return(-1);
! 579:
! 580: if (!strcmp(filename, "-"))
! 581: fd = 0;
! 582: else {
! 583: fd = open(filename, O_CREAT | O_WRONLY);
! 584: if (fd < 0) {
! 585: xmlNanoHTTPClose(ctxt);
! 586: return(-1);
! 587: }
! 588: }
! 589:
! 590: while ((len = xmlNanoHTTPRead(ctxt, buf, sizeof(buf))) > 0) {
! 591: write(fd, buf, len);
! 592: }
! 593:
! 594: xmlNanoHTTPClose(ctxt);
! 595: return(0);
! 596: }
! 597:
! 598: #ifdef STANDALONE
! 599: int main(int argc, char **argv) {
! 600: char *contentType = NULL;
! 601:
! 602: if (argv[1] != NULL) {
! 603: if (argv[2] != NULL)
! 604: xmlNanoHTTPFetch(argv[1], argv[2], &contentType);
! 605: else
! 606: xmlNanoHTTPFetch(argv[1], "-", &contentType);
! 607: } else {
! 608: printf("%s: minimal HTTP GET implementation\n", argv[0]);
! 609: printf("\tusage %s [ URL [ filename ] ]\n", argv[0]);
! 610: }
! 611: return(0);
! 612: }
! 613: #endif /* STANDALONE */
Webmaster