Check-in [737e76a69f]
Not logged in
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
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);
 }