Overview
SHA1 Hash: | 737e76a69fbc2e8327195198d14a9287d6863880 |
---|---|
Date: | 2009-03-30 00:31:56 |
User: | drh |
Comment: | Refactor the HTTP client logic to make it much easier to add support for "file:" and "https:" URLs on push, pull, sync, and clone. |
Timelines: | ancestors | descendants | both | trunk |
Other Links: | files | ZIP archive | manifest |
Tags And Properties
- branch=trunk inherited from [a28c83647d]
- sym-trunk inherited from [a28c83647d]
Changes
[hide diffs]Modified src/http.c from [099ac83ebe] to [de0b4f29e0].
@@ -23,388 +23,208 @@ ** ** This file contains code that implements the client-side HTTP protocol */ #include "config.h" #include "http.h" -#ifdef __MINGW32__ -# include <windows.h> -# include <winsock2.h> -#else -# include <arpa/inet.h> -# include <sys/socket.h> -# include <netdb.h> -# include <netinet/in.h> -#endif -#include <assert.h> -#include <sys/types.h> -#include <signal.h> +#include <assert.h> /* -** Persistent information about the HTTP connection. -*/ - -#ifdef __MINGW32__ -static WSADATA ws_info; -static int pSocket = 0; /* The socket on which we talk to the server on */ -#else -static FILE *pSocket = 0; /* The socket filehandle on which we talk to the server */ -#endif - -/* -** Winsock must be initialize before use. This helper method allows us to -** always call ws_init in our code regardless of platform but only actually -** initialize winsock on the windows platform. +** Construct the "login" card with the client credentials. +** +** login LOGIN NONCE SIGNATURE +** +** The LOGIN is the user id of the client. NONCE is the sha1 checksum +** of all payload that follows the login card. SIGNATURE is the sha1 +** checksum of the nonce followed by the user password. +** +** Write the constructed login card into pLogin. pLogin is initialized +** by this routine. */ -static void ws_init(){ -#ifdef __MINGW32__ - if (WSAStartup(MAKEWORD(2,0), &ws_info) != 0){ - fossil_panic("can't initialize winsock"); +static void http_build_login_card(Blob *pPayload, Blob *pLogin){ + Blob nonce; /* The nonce */ + Blob pw; /* The user password */ + Blob sig; /* The signature field */ + + blob_zero(&nonce); + blob_zero(&pw); + sha1sum_blob(pPayload, &nonce); + blob_copy(&pw, &nonce); + blob_zero(pLogin); + if( g.urlUser==0 ){ + user_select(); + db_blob(&pw, "SELECT pw FROM user WHERE uid=%d", g.userUid); + sha1sum_blob(&pw, &sig); + blob_appendf(pLogin, "login %s %b %b\n", g.zLogin, &nonce, &sig); + }else{ + if( g.urlPasswd==0 ){ + if( strcmp(g.urlUser,"anonymous")!=0 ){ + char *zPrompt = mprintf("password for %s: ", g.urlUser); + Blob x; + prompt_for_password(zPrompt, &x, 0); + free(zPrompt); + g.urlPasswd = blob_str(&x); + }else{ + g.urlPasswd = ""; + } + } + blob_append(&pw, g.urlPasswd, -1); + sha1sum_blob(&pw, &sig); + blob_appendf(pLogin, "login %s %b %b\n", g.urlUser, &nonce, &sig); } -#endif -} - -/* -** Like ws_init, winsock must also be cleaned up after. -*/ -static void ws_cleanup(){ -#ifdef __MINGW32__ - WSACleanup(); -#endif + blob_reset(&nonce); + blob_reset(&pw); + blob_reset(&sig); } /* -** Open a socket connection to the server. Return 0 on success and -** non-zero if an error occurs. +** Construct an appropriate HTTP request header. Write the header +** into pHdr. This routine initializes the pHdr blob. pPayload is +** the complete payload (including the login card) already compressed. */ -static int http_open_socket(void){ - static struct sockaddr_in addr; /* The server address */ - static int addrIsInit = 0; /* True once addr is initialized */ - int s; +static void http_build_header(Blob *pPayload, Blob *pHdr){ + int i; + const char *zSep; - if( g.urlIsHttps ){ - fossil_fatal("SSL/TLS is not yet implemented."); + blob_zero(pHdr); + i = strlen(g.urlPath); + if( i>0 && g.urlPath[i-1]=='/' ){ + zSep = ""; + }else{ + zSep = "/"; } - ws_init(); - if( !addrIsInit ){ - addr.sin_family = AF_INET; - addr.sin_port = htons(g.urlPort); - *(int*)&addr.sin_addr = inet_addr(g.urlName); - if( -1 == *(int*)&addr.sin_addr ){ -#ifndef FOSSIL_STATIC_LINK - struct hostent *pHost; - pHost = gethostbyname(g.urlName); - if( pHost!=0 ){ - memcpy(&addr.sin_addr,pHost->h_addr_list[0],pHost->h_length); - }else -#endif - { - fossil_panic("can't resolve host name: %s\n", g.urlName); - } - } - addrIsInit = 1; - - /* Set the Global.zIpAddr variable to the server we are talking to. - ** This is used to populate the ipaddr column of the rcvfrom table, - ** if any files are received from the server. - */ - g.zIpAddr = mprintf("%s", inet_ntoa(addr.sin_addr)); + blob_appendf(pHdr, "POST %s%sxfer HTTP/1.1\r\n", g.urlPath, zSep); + blob_appendf(pHdr, "Host: %s\r\n", g.urlHostname); + blob_appendf(pHdr, "User-Agent: Fossil/" MANIFEST_VERSION "\r\n"); + if( g.fHttpTrace ){ + blob_appendf(pHdr, "Content-Type: application/x-fossil-debug\r\n"); + }else{ + blob_appendf(pHdr, "Content-Type: application/x-fossil\r\n"); } - s = socket(AF_INET,SOCK_STREAM,0); - if( s<0 ){ - fossil_panic("cannot create a socket"); - } - if( connect(s,(struct sockaddr*)&addr,sizeof(addr))<0 ){ - fossil_panic("cannot connect to host %s:%d", g.urlName, g.urlPort); - } -#ifdef __MINGW32__ - pSocket = s; -#else - pSocket = fdopen(s,"r+"); - signal(SIGPIPE, SIG_IGN); -#endif - return 0; + blob_appendf(pHdr, "Content-Length: %d\r\n\r\n", blob_size(pPayload)); } -#ifdef __MINGW32__ /* -** Read the socket until a newline '\n' is found. Return the number -** of characters read. pSockId contains the socket handel. pOut -** contains a pointer to the buffer to write to. pOutSize contains -** the maximum size of the line that pOut can handle. +** Sign the content in pSend, compress it, and send it to the server +** via HTTP or HTTPS. Get a reply, uncompress the reply, and store the reply +** in pRecv. pRecv is assumed to be uninitialized when +** this routine is called - this routine will initialize it. +** +** The server address is contain in the "g" global structure. The +** url_parse() routine should have been called prior to this routine +** in order to fill this structure appropriately. */ -static int socket_recv_line(int pSockId, char* pOut, int pOutSize){ - int received=0; - char letter; - memset(pOut,0,pOutSize); - for(; received<pOutSize-1;received++){ - if( recv(pSockId,(char*)&letter,1,0)>0 ){ - pOut[received]=letter; - if( letter=='\n' ){ - break; - } - }else{ - break; - } +void http_exchange(Blob *pSend, Blob *pReply){ + Blob login; /* The login card */ + Blob payload; /* The complete payload including login card */ + Blob hdr; /* The HTTP request header */ + int closeConnection; /* True to close the connection when done */ + int iLength; /* Length of the reply payload */ + int rc; /* Result code */ + int iHttpVersion; /* Which version of HTTP protocol server uses */ + char *zLine; /* A single line of the reply header */ + int i; /* Loop counter */ + + if( transport_open() ){ + fossil_fatal(transport_errmsg()); + } + + /* Construct the login card and prepare the complete payload */ + http_build_login_card(pSend, &login); + if( g.fHttpTrace ){ + payload = login; + blob_append(&payload, blob_buffer(pSend), blob_size(pSend)); + }else{ + blob_compress2(&login, pSend, &payload); + blob_reset(&login); } - return received; -} + + /* Construct the HTTP request header */ + http_build_header(&payload, &hdr); -/* -** Initialize a blob to the data on an input socket. return -** the number of bytes read into the blob. Any prior content -** of the blob is discarded, not freed. -** -** The function was placed here in http.c due to it's socket -** nature and we did not want to introduce socket headers into -** the socket neutral blob.c file. -*/ -int socket_read_blob(Blob *pBlob, int pSockId, int nToRead){ - int i=0,read=0; - char rbuf[50]; - blob_zero(pBlob); - while ( i<nToRead ){ - read = recv(pSockId, rbuf, 50, 0); - i += read; - if( read<=0 ){ - return 0; - } - blob_append(pBlob, rbuf, read); + /* When tracing, write the transmitted HTTP message both to standard + ** output and into a file. The file can then be used to drive the + ** server-side like this: + ** + ** ./fossil http <http-trace-1.txt + */ + if( g.fHttpTrace ){ + static int traceCnt = 0; + char *zOutFile; + FILE *out; + traceCnt++; + zOutFile = mprintf("http-trace-%d.txt", traceCnt); + printf("HTTP SEND: (%s)\n%s%s=======================\n", + zOutFile, blob_str(&hdr), blob_str(&payload)); + out = fopen(zOutFile, "w"); + if( out ){ + fwrite(blob_buffer(&hdr), 1, blob_size(&hdr), out); + fwrite(blob_buffer(&payload), 1, blob_size(&payload), out); + fclose(out); + } } - return blob_size(pBlob); -} -#endif -/* -** Make a single attempt to talk to the server. Return TRUE on success -** and FALSE on a failure. -** -** pHeader contains the HTTP header. pPayload contains the content. -** The content of the reply is written into pReply. pReply is assumed -** to be uninitialized prior to this call. -** -** If an error occurs, this routine return false, resets pReply and -** closes the persistent connection, if any. -*/ -static int http_send_recv(Blob *pHeader, Blob *pPayload, Blob *pReply){ - int closeConnection=1; /* default to closing the connection */ - int rc; - int iLength; - int iHttpVersion; - int i; - int nRead; - char zLine[2000]; + /* + ** Send the request to the server. + */ + transport_send(&hdr); + transport_send(&payload); + blob_reset(&hdr); + blob_reset(&payload); - if( pSocket==0 && http_open_socket() ){ - return 0; - } + /* + ** Read and interpret the server reply + */ + closeConnection = 1; iLength = -1; -#ifdef __MINGW32__ - /* - ** Use recv/send on the windows platform as winsock does not allow - ** sockets to be used as FILE handles, thus fdopen, fwrite, fgets - ** does not function on windows for sockets. - */ - rc = send(pSocket, blob_buffer(pHeader), blob_size(pHeader), 0); - if( rc!=blob_size(pHeader) ) goto write_err; - rc = send(pSocket, blob_buffer(pPayload), blob_size(pPayload), 0); - if( rc!=blob_size(pPayload) ) goto write_err; - - /* Read the response */ - while( socket_recv_line(pSocket, zLine, 2000) ){ - for( i=0; zLine[i] && zLine[i]!='\n' && zLine[i]!='\r'; i++ ){} - if( i==0 ) break; - zLine[i] = 0; + while( (zLine = transport_receive_line())!=0 && zLine[0]!=0 ){ if( strncasecmp(zLine, "http/1.", 7)==0 ){ if( sscanf(zLine, "HTTP/1.%d %d", &iHttpVersion, &rc)!=2 ) goto write_err; if( rc!=200 ) goto write_err; if( iHttpVersion==0 ){ closeConnection = 1; }else{ closeConnection = 0; } } else if( strncasecmp(zLine, "content-length:", 15)==0 ){ - iLength = atoi(&zLine[16]); + for(i=15; isspace(zLine[i]); i++){} + iLength = atoi(&zLine[i]); }else if( strncasecmp(zLine, "connection:", 11)==0 ){ - for(i=12; isspace(zLine[i]); i++){} - if( zLine[i]=='c' || zLine[i]=='C' ){ + char c; + for(i=11; isspace(zLine[i]); i++){} + c = zLine[i]; + if( c=='c' || c=='C' ){ closeConnection = 1; - }else if( zLine[i]=='k' || zLine[i]=='K' ){ + }else if( c=='k' || c=='K' ){ closeConnection = 0; } } } + + /* + ** Extract the reply payload that follows the header + */ if( iLength<0 ) goto write_err; - nRead = socket_read_blob(pReply, pSocket, iLength); -#else - rc = fwrite(blob_buffer(pHeader), 1, blob_size(pHeader), pSocket); - if( rc!=blob_size(pHeader) ) goto write_err; - rc = fwrite(blob_buffer(pPayload), 1, blob_size(pPayload), pSocket); - if( rc!=blob_size(pPayload) ) goto write_err; - if( fflush(pSocket) ) goto write_err; - if( fgets(zLine, sizeof(zLine), pSocket)==0 ) goto write_err; - if( sscanf(zLine, "HTTP/1.%d %d", &iHttpVersion, &rc)!=2 ) goto write_err; - if( rc!=200 ) goto write_err; - if( iHttpVersion==0 ){ - closeConnection = 1; /* Connection: close */ + blob_zero(pReply); + blob_resize(pReply, iLength); + iLength = transport_receive(blob_buffer(pReply), iLength); + blob_resize(pReply, iLength); + if( g.fHttpTrace ){ + printf("HTTP RECEIVE:\n%s\n=======================\n", blob_str(pReply)); }else{ - closeConnection = 0; /* Connection: keep-alive */ + blob_uncompress(pReply, pReply); } - while( fgets(zLine, sizeof(zLine), pSocket) ){ - for(i=0; zLine[i] && zLine[i]!='\n' && zLine[i]!='\r'; i++){} - if( i==0 ) break; - zLine[i] = 0; - if( strncasecmp(zLine,"content-length:",15)==0 ){ - iLength = atoi(&zLine[16]); - }else if( strncasecmp(zLine, "connection:", 11)==0 ){ - for(i=12; isspace(zLine[i]); i++){} - if( zLine[i]=='c' || zLine[i]=='C' ){ - closeConnection = 1; /* Connection: close */ - }else if( zLine[i]=='k' || zLine[i]=='K' ){ - closeConnection = 0; /* Connection: keep-alive */ - } - } + + /* + ** Close the connection to the server if appropriate. + */ + if( closeConnection ){ + transport_close(); } - if( iLength<0 ) goto write_err; - nRead = blob_read_from_channel(pReply, pSocket, iLength); -#endif - if( nRead!=iLength ){ - blob_reset(pReply); - goto write_err; - } - if( closeConnection ){ - http_close(); - } - return 1; + return; + /* + ** Jump to here if an error is seen. + */ write_err: - http_close(); - return 0; -} - -/* -** Sign the content in pSend, compress it, and send it to the server -** via HTTP. Get a reply, uncompress the reply, and store the reply -** in pRecv. pRecv is assumed to be uninitialized when -** this routine is called - this routine will initialize it. -** -** The server address is contain in the "g" global structure. The -** url_parse() routine should have been called prior to this routine -** in order to fill this structure appropriately. -*/ -void http_exchange(Blob *pSend, Blob *pRecv){ - Blob login, nonce, sig, pw, payload, hdr; - const char *zSep; - int i; - int cnt = 0; - - blob_zero(&nonce); - blob_zero(&pw); - sha1sum_blob(pSend, &nonce); - blob_copy(&pw, &nonce); - blob_zero(&login); - if( g.urlUser==0 ){ - user_select(); - db_blob(&pw, "SELECT pw FROM user WHERE uid=%d", g.userUid); - sha1sum_blob(&pw, &sig); - blob_appendf(&login, "login %s %b %b\n", g.zLogin, &nonce, &sig); - }else{ - if( g.urlPasswd==0 ){ - if( strcmp(g.urlUser,"anonymous")!=0 ){ - char *zPrompt = mprintf("password for %s: ", g.urlUser); - Blob x; - prompt_for_password(zPrompt, &x, 0); - free(zPrompt); - g.urlPasswd = blob_str(&x); - }else{ - g.urlPasswd = ""; - } - } - blob_append(&pw, g.urlPasswd, -1); - /* printf("presig=[%s]\n", blob_str(&pw)); */ - sha1sum_blob(&pw, &sig); - blob_appendf(&login, "login %s %b %b\n", g.urlUser, &nonce, &sig); - } - blob_reset(&nonce); - blob_reset(&pw); - blob_reset(&sig); - if( g.fHttpTrace ){ - payload = login; - blob_append(&payload, blob_buffer(pSend), blob_size(pSend)); - }else{ - blob_compress2(&login, pSend, &payload); - blob_reset(&login); - } - blob_zero(&hdr); - i = strlen(g.urlPath); - if( i>0 && g.urlPath[i-1]=='/' ){ - zSep = ""; - }else{ - zSep = "/"; - } - blob_appendf(&hdr, "POST %s%sxfer HTTP/1.1\r\n", g.urlPath, zSep); - blob_appendf(&hdr, "Host: %s\r\n", g.urlHostname); - blob_appendf(&hdr, "User-Agent: Fossil/" MANIFEST_VERSION "\r\n"); - if( g.fHttpTrace ){ - blob_appendf(&hdr, "Content-Type: application/x-fossil-debug\r\n"); - }else{ - blob_appendf(&hdr, "Content-Type: application/x-fossil\r\n"); - } - blob_appendf(&hdr, "Content-Length: %d\r\n\r\n", blob_size(&payload)); - - if( g.fHttpTrace ){ - /* When tracing, write the transmitted HTTP message both to standard - ** output and into a file. The file can then be used to drive the - ** server-side like this: - ** - ** ./fossil http <http-trace-1.txt - */ - static int traceCnt = 0; - char *zOutFile; - FILE *out; - traceCnt++; - zOutFile = mprintf("http-trace-%d.txt", traceCnt); - printf("HTTP SEND: (%s)\n%s%s=======================\n", - zOutFile, blob_str(&hdr), blob_str(&payload)); - out = fopen(zOutFile, "w"); - if( out ){ - fwrite(blob_buffer(&hdr), 1, blob_size(&hdr), out); - fwrite(blob_buffer(&payload), 1, blob_size(&payload), out); - fclose(out); - } - } - for(cnt=0; cnt<2; cnt++){ - if( http_send_recv(&hdr, &payload, pRecv) ) break; - } - if( cnt>=2 ){ - fossil_fatal("connection to server failed"); - } - blob_reset(&hdr); - blob_reset(&payload); - if( g.fHttpTrace ){ - printf("HTTP RECEIVE:\n%s\n=======================\n", blob_str(pRecv)); - }else{ - blob_uncompress(pRecv, pRecv); - } -} - - -/* -** Make sure the socket to the HTTP server is closed -*/ -void http_close(void){ - if( pSocket ){ -#ifdef __MINGW32__ - closesocket(pSocket); -#else - fclose(pSocket); -#endif - pSocket = 0; - } - /* - ** This is counter productive. Each time we open a connection we initialize - ** winsock and then when closing we cleanup. It would be better to - ** initialize winsock once at application start when we know we are going to - ** use the socket interface and then cleanup once at application exit when - ** we are all done with all socket operations. - */ - ws_cleanup(); + transport_close(); + return; }
Added src/http_socket.c version [b09f0278f6]
@@ -1,1 +1,218 @@ +/* +** Copyright (c) 2009 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public +** License version 2 as published by the Free Software Foundation. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** General Public License for more details. +** +** You should have received a copy of the GNU General Public +** License along with this library; if not, write to the +** Free Software Foundation, Inc., 59 Temple Place - Suite 330, +** Boston, MA 02111-1307, USA. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file manages low-level client socket communications. The socket +** might be for a simple HTTP request or for an encrypted HTTPS request. +** +** This file implements a singleton. A single client socket may be active +** at a time. State information is stored in static variables. The identity +** of the server is held in global variables that are set by url_parse(). +** +** Low-level sockets are abstracted out into this module because they +** are handled different on Unix and windows. +*/ + +#include "config.h" +#include "http_socket.h" +#ifdef __MINGW32__ +# include <windows.h> +# include <winsock2.h> +#else +# include <arpa/inet.h> +# include <sys/socket.h> +# include <netdb.h> +# include <netinet/in.h> +#endif +#include <assert.h> +#include <sys/types.h> +#include <signal.h> + +/* +** There can only be a single socket connection open at a time. +** State information about that socket is stored in the following +** local variables: +*/ +static int socketIsInit = 0; /* True after global initialization */ +#ifdef __MINGW32__ +static WSADATA socketInfo; /* Windows socket initialize data */ +#endif +static int iSocket = -1; /* The socket on which we talk to the server */ +static char *socketErrMsg = 0; /* Text of most recent socket error */ + + +/* +** Clear the socket error message +*/ +static void socket_clear_errmsg(void){ + free(socketErrMsg); + socketErrMsg = 0; +} + +/* +** Set the socket error message. +*/ +void socket_set_errmsg(char *zFormat, ...){ + va_list ap; + socket_clear_errmsg(); + va_start(ap, zFormat); + socketErrMsg = vmprintf(zFormat, ap); + va_end(ap); +} + +/* +** Return the current socket error message +*/ +const char *socket_errmsg(void){ + return socketErrMsg; +} + +/* +** Call this routine once before any other use of the socket interface. +** This routine does initial configuration of the socket module. +*/ +void socket_global_init(void){ + if( socketIsInit==0 ){ +#ifdef __MINGW32__ + if( WSAStartup(MAKEWORD(2,0), &socketInfo)!=0 ){ + fossil_panic("can't initialize winsock"); + } +#endif + socketIsInit = 1; + } +} + +/* +** Call this routine to shutdown the socket module prior to program +** exit. +*/ +void socket_global_shutdown(void){ + if( socketIsInit ){ +#ifdef __MINGW32__ + WSACleanup(); +#endif + socket_clear_errmsg(); + socketIsInit = 0; + } +} + +/* +** Close the currently open socket. If no socket is open, this routine +** is a no-op. +*/ +void socket_close(void){ + if( iSocket>=0 ){ +#ifdef __MINGW32__ + closesocket(iSocket); +#else + close(iSocket); +#endif + iSocket = -1; + } +} + +/* +** Open a socket connection. The identify of the server is determined +** by global varibles that are set using url_parse(): +** +** g.urlName Name of the server. Ex: www.fossil-scm.org +** g.urlPort TCP/IP port to use. Ex: 80 +** +** Return the number of errors. +*/ +int socket_open(void){ + static struct sockaddr_in addr; /* The server address */ + static int addrIsInit = 0; /* True once addr is initialized */ + + socket_global_init(); + if( !addrIsInit ){ + addr.sin_family = AF_INET; + addr.sin_port = htons(g.urlPort); + *(int*)&addr.sin_addr = inet_addr(g.urlName); + if( -1 == *(int*)&addr.sin_addr ){ +#ifndef FOSSIL_STATIC_LINK + struct hostent *pHost; + pHost = gethostbyname(g.urlName); + if( pHost!=0 ){ + memcpy(&addr.sin_addr,pHost->h_addr_list[0],pHost->h_length); + }else +#endif + { + socket_set_errmsg("can't resolve host name: %s\n", g.urlName); + return 1; + } + } + addrIsInit = 1; + + /* Set the Global.zIpAddr variable to the server we are talking to. + ** This is used to populate the ipaddr column of the rcvfrom table, + ** if any files are received from the server. + */ + g.zIpAddr = mprintf("%s", inet_ntoa(addr.sin_addr)); + } + iSocket = socket(AF_INET,SOCK_STREAM,0); + if( iSocket<0 ){ + socket_set_errmsg("cannot create a socket"); + return 1; + } + if( connect(iSocket,(struct sockaddr*)&addr,sizeof(addr))<0 ){ + socket_set_errmsg("cannot connect to host %s:%d", g.urlName, g.urlPort); + socket_close(); + return 1; + } +#ifndef __MINGW32__ + signal(SIGPIPE, SIG_IGN); +#endif + return 0; +} + +/* +** Send content out over the open socket connection. +*/ +size_t socket_send(void *NotUsed, void *pContent, size_t N){ + size_t sent; + size_t total = 0; + while( N>0 ){ + sent = send(iSocket, pContent, N, 0); + if( sent<=0 ) break; + total += sent; + N -= sent; + pContent = (void*)&((char*)pContent)[sent]; + } + return total; +} +/* +** Receive content back from the open socket connection. +*/ +size_t socket_receive(void *NotUsed, void *pContent, size_t N){ + size_t got; + size_t total = 0; + while( N>0 ){ + got = recv(iSocket, pContent, N, 0); + if( got<=0 ) break; + total += got; + N -= got; + pContent = (void*)&((char*)pContent)[got]; + } + return total; +}
Added src/http_transport.c version [e1a8cc2e75]
@@ -1,1 +1,204 @@ +/* +** Copyright (c) 2009 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public +** License version 2 as published by the Free Software Foundation. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** General Public License for more details. +** +** You should have received a copy of the GNU General Public +** License along with this library; if not, write to the +** Free Software Foundation, Inc., 59 Temple Place - Suite 330, +** Boston, MA 02111-1307, USA. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This module implements the transport layer for the client side HTTP +** connection. The purpose of this layer is to provide a common interface +** for both HTTP and HTTPS and to provide a common "fetch one line" +** interface that is used for parsing the reply. +*/ +#include "config.h" +#include "http_transport.h" + +/* +** State information +*/ +static struct { + int isOpen; /* True when the transport layer is open */ + char *pBuf; /* Buffer used to hold the reply */ + int nAlloc; /* Space allocated for transportBuf[] */ + int nUsed ; /* Space of transportBuf[] used */ + int iCursor; /* Next unread by in transportBuf[] */ +} transport = { + 0, 0, 0, 0 +}; + +/* +** Return the current transport error message. +*/ +const char *transport_errmsg(void){ + return socket_errmsg(); +} + +/* +** Open a connection to the server. The server is defined by the following +** global variables: +** +** g.urlName Name of the server. Ex: www.fossil-scm.org +** g.urlPort TCP/IP port. Ex: 80 +** g.urlIsHttps Use TLS for the connection +** +** Return the number of errors. +*/ +int transport_open(void){ + int rc = 0; + if( g.urlIsHttps ){ + socket_set_errmsg("TLS is not yet implemented"); + rc = 1; + }else{ + rc = socket_open(); + if( rc==0 ) transport.isOpen = 1; + } + return rc; +} + +/* +** Close the current connection +*/ +void transport_close(void){ + if( transport.isOpen ){ + socket_close(); + free(transport.pBuf); + transport.pBuf = 0; + transport.nAlloc = 0; + transport.nUsed = 0; + transport.iCursor = 0; + } +} + +/* +** Send content over the wire. +*/ +void transport_send(Blob *toSend){ + char *z = blob_buffer(toSend); + int n = blob_size(toSend); + int sent; + while( n>0 ){ + sent = socket_send(0, z, n); + if( sent<=0 ) break; + n -= sent; + } +} + +/* +** Read N bytes of content from the wire and store in the supplied buffer. +** Return the number of bytes actually received. +*/ +int transport_receive(char *zBuf, int N){ + int onHand; /* Bytes current held in the transport buffer */ + int nByte = 0; /* Bytes of content received */ + + onHand = transport.nUsed - transport.iCursor; + if( onHand>0 ){ + int toMove = onHand; + if( toMove>N ) toMove = N; + memcpy(zBuf, &transport.pBuf[transport.iCursor], toMove); + transport.iCursor += toMove; + if( transport.iCursor>=transport.nUsed ){ + transport.nUsed = 0; + transport.iCursor = 0; + } + N -= toMove; + zBuf += toMove; + nByte += toMove; + } + if( N>0 ){ + int got = socket_receive(0, zBuf, N); + if( got>0 ){ + nByte += got; + } + } + return nByte; +} + +/* +** Load up to N new bytes of content into the transport.pBuf buffer. +** The buffer itself might be moved. And the transport.iCursor value +** might be reset to 0. +*/ +static void transport_load_buffer(int N){ + int i, j; + if( transport.nAlloc==0 ){ + transport.nAlloc = N; + transport.pBuf = malloc( N ); + if( transport.pBuf==0 ) fossil_panic("out of memory"); + transport.iCursor = 0; + transport.nUsed = 0; + } + if( transport.iCursor>0 ){ + for(i=0, j=transport.iCursor; j<transport.nUsed; i++, j++){ + transport.pBuf[i] = transport.pBuf[j]; + } + transport.nUsed -= transport.iCursor; + transport.iCursor = 0; + } + if( transport.nUsed + N > transport.nAlloc ){ + char *pNew; + transport.nAlloc = transport.nUsed + N; + pNew = realloc(transport.pBuf, transport.nAlloc); + if( pNew==0 ) fossil_panic("out of memory"); + transport.pBuf = pNew; + } + if( N>0 ){ + i = transport_receive(&transport.pBuf[transport.nUsed], N); + if( i>0 ){ + transport.nUsed += i; + } + } +} + +/* +** Fetch a single line of input where a line is all text up to the next +** \n character or until the end of input. Remove all trailing whitespace +** from the received line and zero-terminate the result. Return a pointer +** to the line. +** +** Each call to this routine potentially overwrites the returned buffer. +*/ +char *transport_receive_line(void){ + int i; + int iStart; + i = iStart = transport.iCursor; + while(1){ + if( i >= transport.nUsed ){ + transport_load_buffer(1000); + i -= iStart; + iStart = 0; + if( i >= transport.nUsed ){ + transport.pBuf[i] = 0; + transport.iCursor = i; + break; + } + } + if( transport.pBuf[i]=='\n' ){ + transport.iCursor = i+1; + while( i>=iStart && isspace(transport.pBuf[i]) ){ + transport.pBuf[i] = 0; + i--; + } + break; + } + i++; + } + return &transport.pBuf[iStart]; +}
Modified src/main.mk from [1e02eecb11] to [9bd0c8cb97].
@@ -37,10 +37,12 @@ $(SRCDIR)/diffcmd.c \ $(SRCDIR)/doc.c \ $(SRCDIR)/encode.c \ $(SRCDIR)/file.c \ $(SRCDIR)/http.c \ + $(SRCDIR)/http_socket.c \ + $(SRCDIR)/http_transport.c \ $(SRCDIR)/info.c \ $(SRCDIR)/login.c \ $(SRCDIR)/main.c \ $(SRCDIR)/manifest.c \ $(SRCDIR)/md5.c \ @@ -105,10 +107,12 @@ diffcmd_.c \ doc_.c \ encode_.c \ file_.c \ http_.c \ + http_socket_.c \ + http_transport_.c \ info_.c \ login_.c \ main_.c \ manifest_.c \ md5_.c \ @@ -173,10 +177,12 @@ diffcmd.o \ doc.o \ encode.o \ file.o \ http.o \ + http_socket.o \ + http_transport.o \ info.o \ login.o \ main.o \ manifest.o \ md5.o \ @@ -255,16 +261,16 @@ # noop clean: rm -f *.o *_.c $(APPNAME) VERSION.h rm -f translate makeheaders mkindex page_index.h headers - rm -f add.h admin.h allrepo.h bag.h blob.h branch.h browse.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h construct.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h http.h info.h login.h main.h manifest.h md5.h merge.h merge3.h my_page.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h rstats.h schema.h setup.h sha1.h shun.h stat.h style.h sync.h tag.h tagview.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h + rm -f add.h admin.h allrepo.h bag.h blob.h branch.h browse.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h construct.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h my_page.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h rstats.h schema.h setup.h sha1.h shun.h stat.h style.h sync.h tag.h tagview.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h page_index.h: $(TRANS_SRC) mkindex ./mkindex $(TRANS_SRC) >$@ headers: page_index.h makeheaders VERSION.h - ./makeheaders add_.c:add.h admin_.c:admin.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h http_.c:http.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h my_page_.c:my_page.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h rstats_.c:rstats.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tagview_.c:tagview.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h + ./makeheaders add_.c:add.h admin_.c:admin.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h my_page_.c:my_page.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h rstats_.c:rstats.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tagview_.c:tagview.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h touch headers headers: Makefile Makefile: add_.c: $(SRCDIR)/add.c $(SRCDIR)/VERSION translate ./translate $(SRCDIR)/add.c | sed -f $(SRCDIR)/VERSION >add_.c @@ -446,10 +452,24 @@ http.o: http_.c http.h $(SRCDIR)/config.h $(XTCC) -o http.o -c http_.c http.h: headers +http_socket_.c: $(SRCDIR)/http_socket.c $(SRCDIR)/VERSION translate + ./translate $(SRCDIR)/http_socket.c | sed -f $(SRCDIR)/VERSION >http_socket_.c + +http_socket.o: http_socket_.c http_socket.h $(SRCDIR)/config.h + $(XTCC) -o http_socket.o -c http_socket_.c + +http_socket.h: headers +http_transport_.c: $(SRCDIR)/http_transport.c $(SRCDIR)/VERSION translate + ./translate $(SRCDIR)/http_transport.c | sed -f $(SRCDIR)/VERSION >http_transport_.c + +http_transport.o: http_transport_.c http_transport.h $(SRCDIR)/config.h + $(XTCC) -o http_transport.o -c http_transport_.c + +http_transport.h: headers info_.c: $(SRCDIR)/info.c $(SRCDIR)/VERSION translate ./translate $(SRCDIR)/info.c | sed -f $(SRCDIR)/VERSION >info_.c info.o: info_.c info.h $(SRCDIR)/config.h $(XTCC) -o info.o -c info_.c
Modified src/makemake.tcl from [aa8817088a] to [263645557b].
@@ -31,10 +31,12 @@ diffcmd doc encode file http + http_socket + http_transport info login main manifest md5 @@ -195,6 +197,5 @@ puts "th.o:\t\$(SRCDIR)/th.c" puts "\t\$(XTCC) -I\$(SRCDIR) -c \$(SRCDIR)/th.c -o th.o\n" puts "th_lang.o:\t\$(SRCDIR)/th_lang.c" puts "\t\$(XTCC) -I\$(SRCDIR) -c \$(SRCDIR)/th_lang.c -o th_lang.o\n" -
Modified src/xfer.c from [cc749b7254] to [0e2cdc621f].
@@ -848,31 +848,32 @@ ** Records are pushed to the server if pushFlag is true. Records ** are pulled if pullFlag is true. A full sync occurs if both are ** true. */ void client_sync( - int pushFlag, /* True to do a push (or a sync) */ - int pullFlag, /* True to do a pull (or a sync) */ - int cloneFlag, /* True if this is a clone */ - int configRcvMask, /* Receive these configuration items */ - int configSendMask /* Send these configuration items */ + int pushFlag, /* True to do a push (or a sync) */ + int pullFlag, /* True to do a pull (or a sync) */ + int cloneFlag, /* True if this is a clone */ + int configRcvMask, /* Receive these configuration items */ + int configSendMask /* Send these configuration items */ ){ - int go = 1; /* Loop until zero */ - const char *zSCode = db_get("server-code", "x"); - const char *zPCode = db_get("project-code", 0); - int nCard = 0; /* Number of cards sent or received */ - int nCycle = 0; /* Number of round trips to the server */ + int go = 1; /* Loop until zero */ + int nCard = 0; /* Number of cards sent or received */ + int nCycle = 0; /* Number of round trips to the server */ int size; /* Size of a config value */ int nFileSend = 0; int origConfigRcvMask; /* Original value of configRcvMask */ int nFileRecv; /* Number of files received */ int mxPhantomReq = 200; /* Max number of phantoms to request per comm */ const char *zCookie; /* Server cookie */ - Blob send; /* Text we are sending to the server */ - Blob recv; /* Reply we got back from the server */ - Xfer xfer; /* Transfer data */ - + Blob send; /* Text we are sending to the server */ + Blob recv; /* Reply we got back from the server */ + Xfer xfer; /* Transfer data */ + const char *zSCode = db_get("server-code", "x"); + const char *zPCode = db_get("project-code", 0); + + socket_global_init(); memset(&xfer, 0, sizeof(xfer)); xfer.pIn = &recv; xfer.pOut = &send; xfer.mxSend = db_get_int("max-upload", 250000); @@ -1176,9 +1177,10 @@ */ if( xfer.nFileSent+xfer.nDeltaSent>0 ){ go = 1; } }; - http_close(); + transport_close(); + socket_global_shutdown(); db_multi_exec("DROP TABLE onremote"); db_end_transaction(0); }