| | @@ -23,22 +23,55 @@ |
| 23 | 23 | ** |
| 24 | 24 | ** This file contains code that implements the client-side HTTP protocol |
| 25 | 25 | */ |
| 26 | 26 | #include "config.h" |
| 27 | 27 | #include "http.h" |
| 28 | +#ifdef __MINGW32__ |
| 29 | +# include <windows.h> |
| 30 | +# include <winsock2.h> |
| 31 | +#else |
| 32 | +# include <arpa/inet.h> |
| 33 | +# include <sys/socket.h> |
| 34 | +# include <netdb.h> |
| 35 | +# include <netinet/in.h> |
| 36 | +#endif |
| 28 | 37 | #include <assert.h> |
| 29 | | -#include <arpa/inet.h> |
| 30 | 38 | #include <sys/types.h> |
| 31 | | -#include <sys/socket.h> |
| 32 | | -#include <netdb.h> |
| 33 | | -#include <netinet/in.h> |
| 34 | 39 | #include <signal.h> |
| 35 | 40 | |
| 36 | 41 | /* |
| 37 | 42 | ** Persistent information about the HTTP connection. |
| 38 | 43 | */ |
| 39 | | -static FILE *pSocket = 0; /* The socket on which we talk to the server */ |
| 44 | + |
| 45 | +#ifdef __MINGW32__ |
| 46 | +static WSADATA ws_info; |
| 47 | +static int pSocket = 0; /* The socket on which we talk to the server on */ |
| 48 | +#else |
| 49 | +static FILE *pSocket = 0; /* The socket filehandle on which we talk to the server */ |
| 50 | +#endif |
| 51 | + |
| 52 | +/* |
| 53 | +** Winsock must be initialize before use. This helper method allows us to |
| 54 | +** always call ws_init in our code regardless of platform but only actually |
| 55 | +** initialize winsock on the windows platform. |
| 56 | +*/ |
| 57 | +static void ws_init(){ |
| 58 | +#ifdef __MINGW32__ |
| 59 | + if (WSAStartup(MAKEWORD(2,0), &ws_info) != 0){ |
| 60 | + fossil_panic("can't initialize winsock"); |
| 61 | + } |
| 62 | +#endif |
| 63 | +} |
| 64 | + |
| 65 | +/* |
| 66 | +** Like ws_init, winsock must also be cleaned up after. |
| 67 | +*/ |
| 68 | +static void ws_cleanup(){ |
| 69 | +#ifdef __MINGW32__ |
| 70 | + WSACleanup(); |
| 71 | +#endif |
| 72 | +} |
| 40 | 73 | |
| 41 | 74 | /* |
| 42 | 75 | ** Open a socket connection to the server. Return 0 on success and |
| 43 | 76 | ** non-zero if an error occurs. |
| 44 | 77 | */ |
| | @@ -45,11 +78,14 @@ |
| 45 | 78 | static int http_open_socket(void){ |
| 46 | 79 | static struct sockaddr_in addr; /* The server address */ |
| 47 | 80 | static int addrIsInit = 0; /* True once addr is initialized */ |
| 48 | 81 | int s; |
| 49 | 82 | |
| 83 | + ws_init(); |
| 84 | + |
| 50 | 85 | if( !addrIsInit ){ |
| 86 | + |
| 51 | 87 | addr.sin_family = AF_INET; |
| 52 | 88 | addr.sin_port = htons(g.urlPort); |
| 53 | 89 | *(int*)&addr.sin_addr = inet_addr(g.urlName); |
| 54 | 90 | if( -1 == *(int*)&addr.sin_addr ){ |
| 55 | 91 | #ifndef FOSSIL_STATIC_LINK |
| | @@ -76,14 +112,65 @@ |
| 76 | 112 | fossil_panic("cannot create a socket"); |
| 77 | 113 | } |
| 78 | 114 | if( connect(s,(struct sockaddr*)&addr,sizeof(addr))<0 ){ |
| 79 | 115 | fossil_panic("cannot connect to host %s:%d", g.urlName, g.urlPort); |
| 80 | 116 | } |
| 117 | +#ifdef __MINGW32__ |
| 118 | + pSocket = s; |
| 119 | +#else |
| 81 | 120 | pSocket = fdopen(s,"r+"); |
| 82 | 121 | signal(SIGPIPE, SIG_IGN); |
| 122 | +#endif |
| 83 | 123 | return 0; |
| 84 | 124 | } |
| 125 | + |
| 126 | +/* |
| 127 | +** Read the socket until a newline '\n' is found. Return the number |
| 128 | +** of characters read. pSockId contains the socket handel. pOut |
| 129 | +** contains a pointer to the buffer to write to. pOutSize contains |
| 130 | +** the maximum size of the line that pOut can handle. |
| 131 | +*/ |
| 132 | +static int socket_recv_line(int pSockId, char* pOut, int pOutSize){ |
| 133 | + int received=0; |
| 134 | + char letter; |
| 135 | + memset(pOut,0,pOutSize); |
| 136 | + for(; received<pOutSize-1;received++){ |
| 137 | + if( recv(pSockId,(char*)&letter,1,0)>0 ){ |
| 138 | + pOut[received]=letter; |
| 139 | + if( letter=='\n' ){ |
| 140 | + break; |
| 141 | + } |
| 142 | + }else{ |
| 143 | + break; |
| 144 | + } |
| 145 | + } |
| 146 | + return received; |
| 147 | +} |
| 148 | + |
| 149 | +/* |
| 150 | +** Initialize a blob to the data on an input socket. return |
| 151 | +** the number of bytes read into the blob. Any prior content |
| 152 | +** of the blob is discarded, not freed. |
| 153 | +** |
| 154 | +** The function was placed here in http.c due to it's socket |
| 155 | +** nature and we did not want to introduce socket headers into |
| 156 | +** the socket netural blob.c file. |
| 157 | +*/ |
| 158 | +int socket_read_blob(Blob *pBlob, int pSockId, int nToRead){ |
| 159 | + int i=0,read=0; |
| 160 | + char rbuf[50]; |
| 161 | + blob_zero(pBlob); |
| 162 | + while ( i<nToRead ){ |
| 163 | + read = recv(pSockId, rbuf, 50, 0); |
| 164 | + i += read; |
| 165 | + if( read<0 ){ |
| 166 | + return 0; |
| 167 | + } |
| 168 | + blob_append(pBlob, rbuf, read); |
| 169 | + } |
| 170 | + return blob_size(pBlob); |
| 171 | +} |
| 85 | 172 | |
| 86 | 173 | /* |
| 87 | 174 | ** Make a single attempt to talk to the server. Return TRUE on success |
| 88 | 175 | ** and FALSE on a failure. |
| 89 | 176 | ** |
| | @@ -93,40 +180,78 @@ |
| 93 | 180 | ** |
| 94 | 181 | ** If an error occurs, this routine return false, resets pReply and |
| 95 | 182 | ** closes the persistent connection, if any. |
| 96 | 183 | */ |
| 97 | 184 | static int http_send_recv(Blob *pHeader, Blob *pPayload, Blob *pReply){ |
| 185 | + int closeConnection=1; /* default to closing the connection */ |
| 98 | 186 | int rc; |
| 99 | | - int closeConnection; |
| 100 | 187 | int iLength; |
| 101 | 188 | int iHttpVersion; |
| 102 | 189 | int i; |
| 103 | 190 | int nRead; |
| 104 | 191 | char zLine[2000]; |
| 105 | 192 | |
| 106 | 193 | if( pSocket==0 && http_open_socket() ){ |
| 107 | 194 | return 0; |
| 108 | 195 | } |
| 196 | + iLength = -1; |
| 197 | +#ifdef __MINGW32__ |
| 198 | + /* |
| 199 | + ** Use recv/send on the windows platform as winsock does not allow |
| 200 | + ** sockets to be used as FILE handles, thus fdopen, fwrite, fgets |
| 201 | + ** does not function on windows for sockets. |
| 202 | + */ |
| 203 | + rc = send(pSocket, blob_buffer(pHeader), blob_size(pHeader), 0); |
| 204 | + if( rc!=blob_size(pHeader) ) goto write_err; |
| 205 | + rc = send(pSocket, blob_buffer(pPayload), blob_size(pPayload), 0); |
| 206 | + if( rc!=blob_size(pPayload) ) goto write_err; |
| 207 | + |
| 208 | + /* Read the response */ |
| 209 | + while( socket_recv_line(pSocket, zLine, 2000) ){ |
| 210 | + for( i=0; zLine[i] && zLine[i]!='\n' && zLine[i]!='\r'; i++ ){} |
| 211 | + if( i==0 ) break; |
| 212 | + zLine[i] = 0; |
| 213 | + if( strncasecmp(zLine, "http/1.", 7)==0 ){ |
| 214 | + if( sscanf(zLine, "HTTP/1.%d %d", &iHttpVersion, &rc)!=2 ) goto write_err; |
| 215 | + if( rc!=200 ) goto write_err; |
| 216 | + if( iHttpVersion==0 ){ |
| 217 | + closeConnection = 1; |
| 218 | + }else{ |
| 219 | + closeConnection = 0; |
| 220 | + } |
| 221 | + } else if( strncasecmp(zLine, "content-length:", 15)==0 ){ |
| 222 | + iLength = atoi(&zLine[16]); |
| 223 | + }else if( strncasecmp(zLine, "connection:", 11)==0 ){ |
| 224 | + for(i=12; isspace(zLine[i]); i++){} |
| 225 | + if( zLine[i]=='c' || zLine[i]=='C' ){ |
| 226 | + closeConnection = 1; |
| 227 | + }else if( zLine[i]=='k' || zLine[i]=='K' ){ |
| 228 | + closeConnection = 0; |
| 229 | + } |
| 230 | + } |
| 231 | + } |
| 232 | + if( iLength<0 ) goto write_err; |
| 233 | + nRead = socket_read_blob(pReply, pSocket, iLength); |
| 234 | +#else |
| 109 | 235 | rc = fwrite(blob_buffer(pHeader), 1, blob_size(pHeader), pSocket); |
| 110 | 236 | if( rc!=blob_size(pHeader) ) goto write_err; |
| 111 | 237 | rc = fwrite(blob_buffer(pPayload), 1, blob_size(pPayload), pSocket); |
| 112 | 238 | if( rc!=blob_size(pPayload) ) goto write_err; |
| 113 | 239 | if( fflush(pSocket) ) goto write_err; |
| 114 | 240 | if( fgets(zLine, sizeof(zLine), pSocket)==0 ) goto write_err; |
| 115 | 241 | if( sscanf(zLine, "HTTP/1.%d %d", &iHttpVersion, &rc)!=2 ) goto write_err; |
| 116 | 242 | if( rc!=200 ) goto write_err; |
| 117 | 243 | if( iHttpVersion==0 ){ |
| 118 | | - closeConnection = 1; |
| 244 | + closeConnection = 1; /* Connection: close */ |
| 119 | 245 | }else{ |
| 120 | | - closeConnection = 0; |
| 246 | + closeConnection = 0; /* Connection: keep-alive */ |
| 121 | 247 | } |
| 122 | | - iLength = -1; |
| 123 | 248 | while( fgets(zLine, sizeof(zLine), pSocket) ){ |
| 124 | 249 | for(i=0; zLine[i] && zLine[i]!='\n' && zLine[i]!='\r'; i++){} |
| 125 | 250 | if( i==0 ) break; |
| 126 | 251 | zLine[i] = 0; |
| 127 | | - if( strncasecmp(zLine, "content-length:",15)==0 ){ |
| 252 | + if( strncasecmp(zLine,"content-length:",15)==0 ){ |
| 128 | 253 | iLength = atoi(&zLine[16]); |
| 129 | 254 | }else if( strncasecmp(zLine, "connection:", 11)==0 ){ |
| 130 | 255 | for(i=12; isspace(zLine[i]); i++){} |
| 131 | 256 | if( zLine[i]=='c' || zLine[i]=='C' ){ |
| 132 | 257 | closeConnection = 1; /* Connection: close */ |
| | @@ -135,10 +260,11 @@ |
| 135 | 260 | } |
| 136 | 261 | } |
| 137 | 262 | } |
| 138 | 263 | if( iLength<0 ) goto write_err; |
| 139 | 264 | nRead = blob_read_from_channel(pReply, pSocket, iLength); |
| 265 | +#endif |
| 140 | 266 | if( nRead!=iLength ){ |
| 141 | 267 | blob_reset(pReply); |
| 142 | 268 | goto write_err; |
| 143 | 269 | } |
| 144 | 270 | if( closeConnection ){ |
| | @@ -260,9 +386,21 @@ |
| 260 | 386 | /* |
| 261 | 387 | ** Make sure the socket to the HTTP server is closed |
| 262 | 388 | */ |
| 263 | 389 | void http_close(void){ |
| 264 | 390 | if( pSocket ){ |
| 391 | +#ifdef __MINGW32__ |
| 392 | + closesocket(pSocket); |
| 393 | +#else |
| 265 | 394 | fclose(pSocket); |
| 395 | +#endif |
| 266 | 396 | pSocket = 0; |
| 267 | 397 | } |
| 398 | + /* |
| 399 | + ** This is counter productive. Each time we open a connection we initialize |
| 400 | + ** winsock and then when closing we cleanup. It would be better to |
| 401 | + ** initialize winsock once at application start when we know we are going to |
| 402 | + ** use the socket interface and then cleanup once at application exit when |
| 403 | + ** we are all done with all socket operations. |
| 404 | + */ |
| 405 | + ws_cleanup(); |
| 268 | 406 | } |
| 269 | 407 | |