Home | History | Annotate | Download | only in libvncserver
      1 /*
      2  * httpd.c - a simple HTTP server
      3  */
      4 
      5 /*
      6  *  Copyright (C) 2011-2012 Christian Beier <dontmind (at) freeshell.org>
      7  *  Copyright (C) 2002 RealVNC Ltd.
      8  *  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
      9  *
     10  *  This is free software; you can redistribute it and/or modify
     11  *  it under the terms of the GNU General Public License as published by
     12  *  the Free Software Foundation; either version 2 of the License, or
     13  *  (at your option) any later version.
     14  *
     15  *  This software is distributed in the hope that it will be useful,
     16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     18  *  GNU General Public License for more details.
     19  *
     20  *  You should have received a copy of the GNU General Public License
     21  *  along with this software; if not, write to the Free Software
     22  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
     23  *  USA.
     24  */
     25 
     26 #ifdef __STRICT_ANSI__
     27 #define _BSD_SOURCE
     28 #define _POSIX_SOURCE
     29 #endif
     30 
     31 #include <rfb/rfb.h>
     32 
     33 #include <ctype.h>
     34 #ifdef LIBVNCSERVER_HAVE_UNISTD_H
     35 #include <unistd.h>
     36 #endif
     37 #ifdef LIBVNCSERVER_HAVE_SYS_TYPES_H
     38 #include <sys/types.h>
     39 #endif
     40 #ifdef LIBVNCSERVER_HAVE_FCNTL_H
     41 #include <fcntl.h>
     42 #endif
     43 #include <errno.h>
     44 
     45 #ifdef WIN32
     46 #include <io.h>
     47 #include <winsock2.h>
     48 #include <ws2tcpip.h>
     49 #define close closesocket
     50 #if defined(_MSC_VER)
     51 #include <BaseTsd.h> /* For the missing ssize_t */
     52 #define ssize_t SSIZE_T
     53 #define read _read /* Prevent POSIX deprecation warnings */
     54 #endif
     55 #else
     56 #ifdef LIBVNCSERVER_HAVE_SYS_TIME_H
     57 #include <sys/time.h>
     58 #endif
     59 #ifdef LIBVNCSERVER_HAVE_SYS_SOCKET_H
     60 #include <sys/socket.h>
     61 #endif
     62 #ifdef LIBVNCSERVER_HAVE_NETINET_IN_H
     63 #include <netinet/in.h>
     64 #include <netinet/tcp.h>
     65 #include <netdb.h>
     66 #include <arpa/inet.h>
     67 #endif
     68 #include <pwd.h>
     69 #endif
     70 
     71 #ifdef USE_LIBWRAP
     72 #include <tcpd.h>
     73 #endif
     74 
     75 
     76 #define NOT_FOUND_STR "HTTP/1.0 404 Not found\r\nConnection: close\r\n\r\n" \
     77     "<HEAD><TITLE>File Not Found</TITLE></HEAD>\n" \
     78     "<BODY><H1>File Not Found</H1></BODY>\n"
     79 
     80 #define INVALID_REQUEST_STR "HTTP/1.0 400 Invalid Request\r\nConnection: close\r\n\r\n" \
     81     "<HEAD><TITLE>Invalid Request</TITLE></HEAD>\n" \
     82     "<BODY><H1>Invalid request</H1></BODY>\n"
     83 
     84 #define OK_STR "HTTP/1.0 200 OK\r\nConnection: close\r\n\r\n"
     85 #define OK_STR_HTML "HTTP/1.0 200 OK\r\nConnection: close\r\nContent-Type: text/html\r\n\r\n"
     86 
     87 
     88 
     89 static void httpProcessInput(rfbScreenInfoPtr screen);
     90 static rfbBool compareAndSkip(char **ptr, const char *str);
     91 static rfbBool parseParams(const char *request, char *result, int max_bytes);
     92 static rfbBool validateString(char *str);
     93 
     94 #define BUF_SIZE 32768
     95 
     96 static char buf[BUF_SIZE];
     97 static size_t buf_filled=0;
     98 
     99 /*
    100  * httpInitSockets sets up the TCP socket to listen for HTTP connections.
    101  */
    102 
    103 void
    104 rfbHttpInitSockets(rfbScreenInfoPtr rfbScreen)
    105 {
    106     if (rfbScreen->httpInitDone)
    107 	return;
    108 
    109     rfbScreen->httpInitDone = TRUE;
    110 
    111     if (!rfbScreen->httpDir)
    112 	return;
    113 
    114     if (rfbScreen->httpPort == 0) {
    115 	rfbScreen->httpPort = rfbScreen->port-100;
    116     }
    117 
    118     if ((rfbScreen->httpListenSock =
    119       rfbListenOnTCPPort(rfbScreen->httpPort, rfbScreen->listenInterface)) < 0) {
    120 	rfbLogPerror("ListenOnTCPPort");
    121 	return;
    122     }
    123     rfbLog("Listening for HTTP connections on TCP port %d\n", rfbScreen->httpPort);
    124     rfbLog("  URL http://%s:%d\n",rfbScreen->thisHost,rfbScreen->httpPort);
    125 
    126 #ifdef LIBVNCSERVER_IPv6
    127     if (rfbScreen->http6Port == 0) {
    128 	rfbScreen->http6Port = rfbScreen->ipv6port-100;
    129     }
    130 
    131     if ((rfbScreen->httpListen6Sock
    132 	 = rfbListenOnTCP6Port(rfbScreen->http6Port, rfbScreen->listen6Interface)) < 0) {
    133       /* ListenOnTCP6Port has its own detailed error printout */
    134       return;
    135     }
    136     rfbLog("Listening for HTTP connections on TCP6 port %d\n", rfbScreen->http6Port);
    137     rfbLog("  URL http://%s:%d\n",rfbScreen->thisHost,rfbScreen->http6Port);
    138 #endif
    139 }
    140 
    141 void rfbHttpShutdownSockets(rfbScreenInfoPtr rfbScreen) {
    142     if(rfbScreen->httpSock>-1) {
    143 	close(rfbScreen->httpSock);
    144 	FD_CLR(rfbScreen->httpSock,&rfbScreen->allFds);
    145 	rfbScreen->httpSock=-1;
    146     }
    147 
    148     if(rfbScreen->httpListenSock>-1) {
    149 	close(rfbScreen->httpListenSock);
    150 	FD_CLR(rfbScreen->httpListenSock,&rfbScreen->allFds);
    151 	rfbScreen->httpListenSock=-1;
    152     }
    153 
    154     if(rfbScreen->httpListen6Sock>-1) {
    155 	close(rfbScreen->httpListen6Sock);
    156 	FD_CLR(rfbScreen->httpListen6Sock,&rfbScreen->allFds);
    157 	rfbScreen->httpListen6Sock=-1;
    158     }
    159 }
    160 
    161 /*
    162  * httpCheckFds is called from ProcessInputEvents to check for input on the
    163  * HTTP socket(s).  If there is input to process, httpProcessInput is called.
    164  */
    165 
    166 void
    167 rfbHttpCheckFds(rfbScreenInfoPtr rfbScreen)
    168 {
    169     int nfds;
    170     fd_set fds;
    171     struct timeval tv;
    172 #ifdef LIBVNCSERVER_IPv6
    173     struct sockaddr_storage addr;
    174 #else
    175     struct sockaddr_in addr;
    176 #endif
    177     socklen_t addrlen = sizeof(addr);
    178 
    179     if (!rfbScreen->httpDir)
    180 	return;
    181 
    182     if (rfbScreen->httpListenSock < 0)
    183 	return;
    184 
    185     FD_ZERO(&fds);
    186     FD_SET(rfbScreen->httpListenSock, &fds);
    187     if (rfbScreen->httpListen6Sock >= 0) {
    188 	FD_SET(rfbScreen->httpListen6Sock, &fds);
    189     }
    190     if (rfbScreen->httpSock >= 0) {
    191 	FD_SET(rfbScreen->httpSock, &fds);
    192     }
    193     tv.tv_sec = 0;
    194     tv.tv_usec = 0;
    195     nfds = select(max(rfbScreen->httpListen6Sock, max(rfbScreen->httpSock,rfbScreen->httpListenSock)) + 1, &fds, NULL, NULL, &tv);
    196     if (nfds == 0) {
    197 	return;
    198     }
    199     if (nfds < 0) {
    200 #ifdef WIN32
    201 		errno = WSAGetLastError();
    202 #endif
    203 	if (errno != EINTR)
    204 		rfbLogPerror("httpCheckFds: select");
    205 	return;
    206     }
    207 
    208     if ((rfbScreen->httpSock >= 0) && FD_ISSET(rfbScreen->httpSock, &fds)) {
    209 	httpProcessInput(rfbScreen);
    210     }
    211 
    212     if (FD_ISSET(rfbScreen->httpListenSock, &fds) || FD_ISSET(rfbScreen->httpListen6Sock, &fds)) {
    213 	if (rfbScreen->httpSock >= 0) close(rfbScreen->httpSock);
    214 
    215 	if(FD_ISSET(rfbScreen->httpListenSock, &fds)) {
    216 	    if ((rfbScreen->httpSock = accept(rfbScreen->httpListenSock, (struct sockaddr *)&addr, &addrlen)) < 0) {
    217 	      rfbLogPerror("httpCheckFds: accept");
    218 	      return;
    219 	    }
    220 	}
    221 	else if(FD_ISSET(rfbScreen->httpListen6Sock, &fds)) {
    222 	    if ((rfbScreen->httpSock = accept(rfbScreen->httpListen6Sock, (struct sockaddr *)&addr, &addrlen)) < 0) {
    223 	      rfbLogPerror("httpCheckFds: accept");
    224 	      return;
    225 	    }
    226 	}
    227 
    228 #ifdef USE_LIBWRAP
    229 	char host[1024];
    230 #ifdef LIBVNCSERVER_IPv6
    231 	if(getnameinfo((struct sockaddr*)&addr, addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST) != 0) {
    232 	  rfbLogPerror("httpCheckFds: error in getnameinfo");
    233 	  host[0] = '\0';
    234 	}
    235 #else
    236 	memcpy(host, inet_ntoa(addr.sin_addr), sizeof(host));
    237 #endif
    238 	if(!hosts_ctl("vnc",STRING_UNKNOWN, host,
    239 		      STRING_UNKNOWN)) {
    240 	  rfbLog("Rejected HTTP connection from client %s\n",
    241 		 host);
    242 	  close(rfbScreen->httpSock);
    243 	  rfbScreen->httpSock=-1;
    244 	  return;
    245 	}
    246 #endif
    247         if(!rfbSetNonBlocking(rfbScreen->httpSock)) {
    248 	    close(rfbScreen->httpSock);
    249 	    rfbScreen->httpSock=-1;
    250 	    return;
    251 	}
    252 	/*AddEnabledDevice(httpSock);*/
    253     }
    254 }
    255 
    256 
    257 static void
    258 httpCloseSock(rfbScreenInfoPtr rfbScreen)
    259 {
    260     close(rfbScreen->httpSock);
    261     rfbScreen->httpSock = -1;
    262     buf_filled = 0;
    263 }
    264 
    265 static rfbClientRec cl;
    266 
    267 /*
    268  * httpProcessInput is called when input is received on the HTTP socket.
    269  */
    270 
    271 static void
    272 httpProcessInput(rfbScreenInfoPtr rfbScreen)
    273 {
    274 #ifdef LIBVNCSERVER_IPv6
    275     struct sockaddr_storage addr;
    276 #else
    277     struct sockaddr_in addr;
    278 #endif
    279     socklen_t addrlen = sizeof(addr);
    280     char fullFname[512];
    281     char params[1024];
    282     char *ptr;
    283     char *fname;
    284     unsigned int maxFnameLen;
    285     FILE* fd;
    286     rfbBool performSubstitutions = FALSE;
    287     char str[256+32];
    288 #ifndef WIN32
    289     char* user=getenv("USER");
    290 #endif
    291 
    292     cl.sock=rfbScreen->httpSock;
    293 
    294     if (strlen(rfbScreen->httpDir) > 255) {
    295 	rfbErr("-httpd directory too long\n");
    296 	httpCloseSock(rfbScreen);
    297 	return;
    298     }
    299     strcpy(fullFname, rfbScreen->httpDir);
    300     fname = &fullFname[strlen(fullFname)];
    301     maxFnameLen = 511 - strlen(fullFname);
    302 
    303     buf_filled=0;
    304 
    305     /* Read data from the HTTP client until we get a complete request. */
    306     while (1) {
    307 	ssize_t got;
    308 
    309         if (buf_filled > sizeof (buf)) {
    310 	    rfbErr("httpProcessInput: HTTP request is too long\n");
    311 	    httpCloseSock(rfbScreen);
    312 	    return;
    313 	}
    314 
    315 	got = read (rfbScreen->httpSock, buf + buf_filled,
    316 			    sizeof (buf) - buf_filled - 1);
    317 
    318 	if (got <= 0) {
    319 	    if (got == 0) {
    320 		rfbErr("httpd: premature connection close\n");
    321 	    } else {
    322 #ifdef WIN32
    323 	        errno=WSAGetLastError();
    324 #endif
    325 		if (errno == EAGAIN) {
    326 		    return;
    327 		}
    328 		rfbLogPerror("httpProcessInput: read");
    329 	    }
    330 	    httpCloseSock(rfbScreen);
    331 	    return;
    332 	}
    333 
    334 	buf_filled += got;
    335 	buf[buf_filled] = '\0';
    336 
    337 	/* Is it complete yet (is there a blank line)? */
    338 	if (strstr (buf, "\r\r") || strstr (buf, "\n\n") ||
    339 	    strstr (buf, "\r\n\r\n") || strstr (buf, "\n\r\n\r"))
    340 	    break;
    341     }
    342 
    343 
    344     /* Process the request. */
    345     if(rfbScreen->httpEnableProxyConnect) {
    346 	const static char* PROXY_OK_STR = "HTTP/1.0 200 OK\r\nContent-Type: octet-stream\r\nPragma: no-cache\r\n\r\n";
    347 	if(!strncmp(buf, "CONNECT ", 8)) {
    348 	    if(atoi(strchr(buf, ':')+1)!=rfbScreen->port) {
    349 		rfbErr("httpd: CONNECT format invalid.\n");
    350 		rfbWriteExact(&cl,INVALID_REQUEST_STR, strlen(INVALID_REQUEST_STR));
    351 		httpCloseSock(rfbScreen);
    352 		return;
    353 	    }
    354 	    /* proxy connection */
    355 	    rfbLog("httpd: client asked for CONNECT\n");
    356 	    rfbWriteExact(&cl,PROXY_OK_STR,strlen(PROXY_OK_STR));
    357 	    rfbNewClientConnection(rfbScreen,rfbScreen->httpSock);
    358 	    rfbScreen->httpSock = -1;
    359 	    return;
    360 	}
    361 	if (!strncmp(buf, "GET ",4) && !strncmp(strchr(buf,'/'),"/proxied.connection HTTP/1.", 27)) {
    362 	    /* proxy connection */
    363 	    rfbLog("httpd: client asked for /proxied.connection\n");
    364 	    rfbWriteExact(&cl,PROXY_OK_STR,strlen(PROXY_OK_STR));
    365 	    rfbNewClientConnection(rfbScreen,rfbScreen->httpSock);
    366 	    rfbScreen->httpSock = -1;
    367 	    return;
    368 	}
    369     }
    370 
    371     if (strncmp(buf, "GET ", 4)) {
    372 	rfbErr("httpd: no GET line\n");
    373 	httpCloseSock(rfbScreen);
    374 	return;
    375     } else {
    376 	/* Only use the first line. */
    377 	buf[strcspn(buf, "\n\r")] = '\0';
    378     }
    379 
    380     if (strlen(buf) > maxFnameLen) {
    381 	rfbErr("httpd: GET line too long\n");
    382 	httpCloseSock(rfbScreen);
    383 	return;
    384     }
    385 
    386     if (sscanf(buf, "GET %s HTTP/1.", fname) != 1) {
    387 	rfbErr("httpd: couldn't parse GET line\n");
    388 	httpCloseSock(rfbScreen);
    389 	return;
    390     }
    391 
    392     if (fname[0] != '/') {
    393 	rfbErr("httpd: filename didn't begin with '/'\n");
    394 	rfbWriteExact(&cl, NOT_FOUND_STR, strlen(NOT_FOUND_STR));
    395 	httpCloseSock(rfbScreen);
    396 	return;
    397     }
    398 
    399 
    400     getpeername(rfbScreen->httpSock, (struct sockaddr *)&addr, &addrlen);
    401 #ifdef LIBVNCSERVER_IPv6
    402     {
    403         char host[1024];
    404         if(getnameinfo((struct sockaddr*)&addr, addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST) != 0) {
    405             rfbLogPerror("httpProcessInput: error in getnameinfo");
    406         }
    407         rfbLog("httpd: get '%s' for %s\n", fname+1, host);
    408     }
    409 #else
    410     rfbLog("httpd: get '%s' for %s\n", fname+1,
    411 	   inet_ntoa(addr.sin_addr));
    412 #endif
    413 
    414     /* Extract parameters from the URL string if necessary */
    415 
    416     params[0] = '\0';
    417     ptr = strchr(fname, '?');
    418     if (ptr != NULL) {
    419        *ptr = '\0';
    420        if (!parseParams(&ptr[1], params, 1024)) {
    421            params[0] = '\0';
    422            rfbErr("httpd: bad parameters in the URL\n");
    423        }
    424     }
    425 
    426 
    427     /* If we were asked for '/', actually read the file index.vnc */
    428 
    429     if (strcmp(fname, "/") == 0) {
    430 	strcpy(fname, "/index.vnc");
    431 	rfbLog("httpd: defaulting to '%s'\n", fname+1);
    432     }
    433 
    434     /* Substitutions are performed on files ending .vnc */
    435 
    436     if (strlen(fname) >= 4 && strcmp(&fname[strlen(fname)-4], ".vnc") == 0) {
    437 	performSubstitutions = TRUE;
    438     }
    439 
    440     /* Open the file */
    441 
    442     if ((fd = fopen(fullFname, "r")) == 0) {
    443         rfbLogPerror("httpProcessInput: open");
    444         rfbWriteExact(&cl, NOT_FOUND_STR, strlen(NOT_FOUND_STR));
    445         httpCloseSock(rfbScreen);
    446         return;
    447     }
    448 
    449     if(performSubstitutions) /* is the 'index.vnc' file */
    450       rfbWriteExact(&cl, OK_STR_HTML, strlen(OK_STR_HTML));
    451     else
    452       rfbWriteExact(&cl, OK_STR, strlen(OK_STR));
    453 
    454     while (1) {
    455 	int n = fread(buf, 1, BUF_SIZE-1, fd);
    456 	if (n < 0) {
    457 	    rfbLogPerror("httpProcessInput: read");
    458 	    fclose(fd);
    459 	    httpCloseSock(rfbScreen);
    460 	    return;
    461 	}
    462 
    463 	if (n == 0)
    464 	    break;
    465 
    466 	if (performSubstitutions) {
    467 
    468 	    /* Substitute $WIDTH, $HEIGHT, etc with the appropriate values.
    469 	       This won't quite work properly if the .vnc file is longer than
    470 	       BUF_SIZE, but it's reasonable to assume that .vnc files will
    471 	       always be short. */
    472 
    473 	    char *ptr = buf;
    474 	    char *dollar;
    475 	    buf[n] = 0; /* make sure it's null-terminated */
    476 
    477 	    while ((dollar = strchr(ptr, '$'))!=NULL) {
    478 		rfbWriteExact(&cl, ptr, (dollar - ptr));
    479 
    480 		ptr = dollar;
    481 
    482 		if (compareAndSkip(&ptr, "$WIDTH")) {
    483 
    484 		    sprintf(str, "%d", rfbScreen->width);
    485 		    rfbWriteExact(&cl, str, strlen(str));
    486 
    487 		} else if (compareAndSkip(&ptr, "$HEIGHT")) {
    488 
    489 		    sprintf(str, "%d", rfbScreen->height);
    490 		    rfbWriteExact(&cl, str, strlen(str));
    491 
    492 		} else if (compareAndSkip(&ptr, "$APPLETWIDTH")) {
    493 
    494 		    sprintf(str, "%d", rfbScreen->width);
    495 		    rfbWriteExact(&cl, str, strlen(str));
    496 
    497 		} else if (compareAndSkip(&ptr, "$APPLETHEIGHT")) {
    498 
    499 		    sprintf(str, "%d", rfbScreen->height + 32);
    500 		    rfbWriteExact(&cl, str, strlen(str));
    501 
    502 		} else if (compareAndSkip(&ptr, "$PORT")) {
    503 
    504 		    sprintf(str, "%d", rfbScreen->port);
    505 		    rfbWriteExact(&cl, str, strlen(str));
    506 
    507 		} else if (compareAndSkip(&ptr, "$DESKTOP")) {
    508 
    509 		    rfbWriteExact(&cl, rfbScreen->desktopName, strlen(rfbScreen->desktopName));
    510 
    511 		} else if (compareAndSkip(&ptr, "$DISPLAY")) {
    512 
    513 		    sprintf(str, "%s:%d", rfbScreen->thisHost, rfbScreen->port-5900);
    514 		    rfbWriteExact(&cl, str, strlen(str));
    515 
    516 		} else if (compareAndSkip(&ptr, "$USER")) {
    517 #ifndef WIN32
    518 		    if (user) {
    519 			rfbWriteExact(&cl, user,
    520 				   strlen(user));
    521 		    } else
    522 #endif
    523 			rfbWriteExact(&cl, "?", 1);
    524 		} else if (compareAndSkip(&ptr, "$PARAMS")) {
    525 		    if (params[0] != '\0')
    526 			rfbWriteExact(&cl, params, strlen(params));
    527 		} else {
    528 		    if (!compareAndSkip(&ptr, "$$"))
    529 			ptr++;
    530 
    531 		    if (rfbWriteExact(&cl, "$", 1) < 0) {
    532 			fclose(fd);
    533 			httpCloseSock(rfbScreen);
    534 			return;
    535 		    }
    536 		}
    537 	    }
    538 	    if (rfbWriteExact(&cl, ptr, (&buf[n] - ptr)) < 0)
    539 		break;
    540 
    541 	} else {
    542 
    543 	    /* For files not ending .vnc, just write out the buffer */
    544 
    545 	    if (rfbWriteExact(&cl, buf, n) < 0)
    546 		break;
    547 	}
    548     }
    549 
    550     fclose(fd);
    551     httpCloseSock(rfbScreen);
    552 }
    553 
    554 
    555 static rfbBool
    556 compareAndSkip(char **ptr, const char *str)
    557 {
    558     if (strncmp(*ptr, str, strlen(str)) == 0) {
    559 	*ptr += strlen(str);
    560 	return TRUE;
    561     }
    562 
    563     return FALSE;
    564 }
    565 
    566 /*
    567  * Parse the request tail after the '?' character, and format a sequence
    568  * of <param> tags for inclusion into an HTML page with embedded applet.
    569  */
    570 
    571 static rfbBool
    572 parseParams(const char *request, char *result, int max_bytes)
    573 {
    574     char param_request[128];
    575     char param_formatted[196];
    576     const char *tail;
    577     char *delim_ptr;
    578     char *value_str;
    579     int cur_bytes, len;
    580 
    581     result[0] = '\0';
    582     cur_bytes = 0;
    583 
    584     tail = request;
    585     for (;;) {
    586 	/* Copy individual "name=value" string into a buffer */
    587 	delim_ptr = strchr((char *)tail, '&');
    588 	if (delim_ptr == NULL) {
    589 	    if (strlen(tail) >= sizeof(param_request)) {
    590 		return FALSE;
    591 	    }
    592 	    strcpy(param_request, tail);
    593 	} else {
    594 	    len = delim_ptr - tail;
    595 	    if (len >= sizeof(param_request)) {
    596 		return FALSE;
    597 	    }
    598 	    memcpy(param_request, tail, len);
    599 	    param_request[len] = '\0';
    600 	}
    601 
    602 	/* Split the request into parameter name and value */
    603 	value_str = strchr(&param_request[1], '=');
    604 	if (value_str == NULL) {
    605 	    return FALSE;
    606 	}
    607 	*value_str++ = '\0';
    608 	if (strlen(value_str) == 0) {
    609 	    return FALSE;
    610 	}
    611 
    612 	/* Validate both parameter name and value */
    613 	if (!validateString(param_request) || !validateString(value_str)) {
    614 	    return FALSE;
    615 	}
    616 
    617 	/* Prepare HTML-formatted representation of the name=value pair */
    618 	len = sprintf(param_formatted,
    619 		      "<PARAM NAME=\"%s\" VALUE=\"%s\">\n",
    620 		      param_request, value_str);
    621 	if (cur_bytes + len + 1 > max_bytes) {
    622 	    return FALSE;
    623 	}
    624 	strcat(result, param_formatted);
    625 	cur_bytes += len;
    626 
    627 	/* Go to the next parameter */
    628 	if (delim_ptr == NULL) {
    629 	    break;
    630 	}
    631 	tail = delim_ptr + 1;
    632     }
    633     return TRUE;
    634 }
    635 
    636 /*
    637  * Check if the string consists only of alphanumeric characters, '+'
    638  * signs, underscores, dots, colons and square brackets.
    639  * Replace all '+' signs with spaces.
    640  */
    641 
    642 static rfbBool
    643 validateString(char *str)
    644 {
    645     char *ptr;
    646 
    647     for (ptr = str; *ptr != '\0'; ptr++) {
    648 	if (!isalnum(*ptr) && *ptr != '_' && *ptr != '.'
    649 	    && *ptr != ':' && *ptr != '[' && *ptr != ']' ) {
    650 	    if (*ptr == '+') {
    651 		*ptr = ' ';
    652 	    } else {
    653 		return FALSE;
    654 	    }
    655 	}
    656     }
    657     return TRUE;
    658 }
    659 
    660