cryptlib  3.4.1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros
http_wr.c
Go to the documentation of this file.
1 /****************************************************************************
2 * *
3 * cryptlib HTTP Write Routines *
4 * Copyright Peter Gutmann 1998-2009 *
5 * *
6 ****************************************************************************/
7 
8 #include <ctype.h>
9 #include <stdio.h>
10 #if defined( INC_ALL )
11  #include "crypt.h"
12  #include "http.h"
13 #else
14  #include "crypt.h"
15  #include "io/http.h"
16 #endif /* Compiler-specific includes */
17 
18 #ifdef USE_HTTP
19 
20 /****************************************************************************
21 * *
22 * Utility Functions *
23 * *
24 ****************************************************************************/
25 
26 /* Encode a string as per RFC 1866 (although the list of characters that
27  need to be escaped is itself given in RFC 2396). Characters that are
28  permitted/not permitted are:
29 
30  !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
31  x..x.xx....x...xxxxxxxxxxxx.xxxxx
32 
33  Because of this it's easier to check for the most likely permitted
34  characters (alphanumerics) first, and only then to check for any special-
35  case characters */
36 
37 STDC_NONNULL_ARG( ( 1, 2 ) ) \
38 static void encodeRFC1866( INOUT STREAM *headerStream,
39  IN_BUFFER( stringLength) const char *string,
40  IN_LENGTH_SHORT const int stringLength )
41  {
42  static const char allowedChars[] = "$-_.!*'(),\"/\x00\x00"; /* RFC 1738 + '/' */
43  int index;
44 
45  assert( isWritePtr( headerStream, sizeof( STREAM ) ) );
46  assert( isReadPtr( string, stringLength ) );
47 
48  REQUIRES_V( stringLength > 0 && stringLength < MAX_INTLENGTH_SHORT );
49 
50  for( index = 0; index < stringLength && \
51  index < MAX_INTLENGTH_SHORT; index++ )
52  {
53  const int ch = byteToInt( string[ index ] );
54  int i;
55 
56  if( isAlnum( ch ) )
57  {
58  sputc( headerStream, ch );
59  continue;
60  }
61  if( ch == ' ' )
62  {
63  sputc( headerStream, '+' );
64  continue;
65  }
66  for( i = 0; allowedChars[ i ] != '\0' && ch != allowedChars[ i ] && \
67  i < FAILSAFE_ARRAYSIZE( allowedChars, char ); i++ );
68  ENSURES_V( i < FAILSAFE_ARRAYSIZE( allowedChars, char ) );
69  if( allowedChars[ i ] != '\0' )
70  {
71  /* It's in the allowed-chars list, output it verbatim */
72  sputc( headerStream, ch );
73  }
74  else
75  {
76  char escapeString[ 8 + 8 ];
77  int escapeStringLen;
78 
79  /* It's a special char, escape it */
80  escapeStringLen = sprintf_s( escapeString, 8, "%%%02X", ch );
81  swrite( headerStream, escapeString, escapeStringLen );
82  }
83  }
84  ENSURES_V( index < MAX_INTLENGTH_SHORT );
85  }
86 
87 /* If we time out when sending HTTP header data this would usually be
88  reported as a CRYPT_ERROR_TIMEOUT by the lower-level network I/O
89  routines, however due to the multiple layers of I/O and special case
90  timeout handling when (for example) a cryptlib transport session is
91  layered over the network I/O layer and the fact that to the caller the
92  write of the out-of-band HTTP header data (which can occur as part of a
93  standard HTTP write, but also in a GET or when sending an error
94  response) is invisible, we have to perform an explicit check to make
95  sure that we sent everything */
96 
97 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
98 int sendHTTPData( INOUT STREAM *stream,
99  IN_BUFFER( length ) void *buffer,
100  IN_LENGTH const int length,
101  IN_FLAGS( HTTP ) const int flags )
102  {
103  NET_STREAM_INFO *netStream = ( NET_STREAM_INFO * ) stream->netStreamInfo;
104  int bytesWritten, status;
105 
106  assert( isWritePtr( stream, sizeof( STREAM ) ) );
107  assert( isWritePtr( buffer, length ) );
108 
109  REQUIRES( length > 0 && length < MAX_INTLENGTH );
110  REQUIRES( flags >= HTTP_FLAG_NONE && flags <= HTTP_FLAG_MAX );
111 
112  status = netStream->bufferedTransportWriteFunction( stream, buffer,
113  length,
114  &bytesWritten, flags );
115  if( cryptStatusError( status ) )
116  {
117  /* Network-level error, the lower-level layers have reported the
118  error details */
119  return( status );
120  }
121  if( bytesWritten < length )
122  {
123  /* The write timed out, convert the incomplete HTTP header write to
124  the appropriate timeout error */
127  "HTTP write timed out before all data could be written" ) );
128  }
129  return( CRYPT_OK );
130  }
131 
132 /****************************************************************************
133 * *
134 * Write Request/Response Header *
135 * *
136 ****************************************************************************/
137 
138 /* Write an HTTP request header. The forceGet flag is used when we should
139  be using a POST but a broken server forces the use of a GET */
140 
142 int writeRequestHeader( INOUT STREAM *stream,
144  IN_BUFFER_OPT( contentTypeLen ) const char *contentType,
145  IN_LENGTH_SHORT_Z const int contentTypeLen,
146  IN_LENGTH_Z const int contentLength,
147  const BOOLEAN forceGet )
148  {
149  NET_STREAM_INFO *netStream = ( NET_STREAM_INFO * ) stream->netStreamInfo;
150  STREAM headerStream;
151  char headerBuffer[ HTTP_LINEBUF_SIZE + 8 ];
152  const int transportFlag = ( contentLength > 0 && !forceGet ) ? \
153  TRANSPORT_FLAG_NONE : TRANSPORT_FLAG_FLUSH;
155 
156  assert( isWritePtr( stream, sizeof( STREAM ) ) );
157  assert( ( httpReqInfo == NULL ) || \
158  isReadPtr( httpReqInfo, sizeof( HTTP_URI_INFO * ) ) );
159  assert( ( contentLength == 0 && contentType == NULL && \
160  contentTypeLen == 0 ) || \
161  ( contentLength >= 1 && \
162  isReadPtr( contentType, contentTypeLen ) ) );
163 
164  REQUIRES( ( contentLength == 0 && contentType == NULL && \
165  contentTypeLen == 0 ) || \
166  ( contentLength > 0 && contentLength < MAX_INTLENGTH && \
167  contentType != NULL && \
168  contentTypeLen > 0 && contentTypeLen < MAX_INTLENGTH ) );
169  REQUIRES( ( httpReqInfo == NULL ) || \
170  ( httpReqInfo->attributeLen == 0 && \
171  httpReqInfo->valueLen == 0 ) || \
172  ( httpReqInfo->attributeLen > 0 && \
173  httpReqInfo->valueLen > 0 ) );
174 
175  sMemOpen( &headerStream, headerBuffer, HTTP_LINEBUF_SIZE );
176  if( netStream->nFlags & STREAM_NFLAG_HTTPTUNNEL )
177  swrite( &headerStream, "CONNECT ", 8 );
178  else
179  {
180  if( contentLength > 0 && !forceGet )
181  swrite( &headerStream, "POST ", 5 );
182  else
183  swrite( &headerStream, "GET ", 4 );
184  }
185  if( netStream->nFlags & ( STREAM_NFLAG_HTTPPROXY | STREAM_NFLAG_HTTPTUNNEL ) )
186  {
187  /* If we're going through an HTTP proxy/tunnel, send an absolute URL
188  rather than just the relative location */
189  if( netStream->nFlags & STREAM_NFLAG_HTTPPROXY )
190  swrite( &headerStream, "http://", 7 );
191  swrite( &headerStream, netStream->host, netStream->hostLen );
192  if( netStream->port != 80 )
193  {
194  char portString[ 16 + 8 ];
195  int portStringLength;
196 
197  portStringLength = sprintf_s( portString, 16, ":%d",
198  netStream->port );
199  swrite( &headerStream, portString, portStringLength );
200  }
201  }
202  if( !( netStream->nFlags & STREAM_NFLAG_HTTPTUNNEL ) )
203  {
204  if( netStream->path != NULL && netStream->pathLen > 0 )
205  swrite( &headerStream, netStream->path, netStream->pathLen );
206  else
207  sputc( &headerStream, '/' );
208  }
209  if( httpReqInfo != NULL )
210  {
211  if( httpReqInfo->attributeLen > 0 && httpReqInfo->valueLen > 0 )
212  {
213  sputc( &headerStream, '?' );
214  swrite( &headerStream, httpReqInfo->attribute,
215  httpReqInfo->attributeLen );
216  sputc( &headerStream, '=' );
217  encodeRFC1866( &headerStream, httpReqInfo->value,
218  httpReqInfo->valueLen );
219  }
220  if( httpReqInfo->extraDataLen > 0 )
221  {
222  sputc( &headerStream, '&' );
223  swrite( &headerStream, httpReqInfo->extraData,
224  httpReqInfo->extraDataLen );
225  }
226  }
227  if( !forceGet )
228  {
229  if( isHTTP10( stream ) )
230  swrite( &headerStream, " HTTP/1.0\r\n", 11 );
231  else
232  {
233  swrite( &headerStream, " HTTP/1.1\r\nHost: ", 17 );
234  swrite( &headerStream, netStream->host, netStream->hostLen );
235  swrite( &headerStream, "\r\n", 2 );
236  if( netStream->nFlags & STREAM_NFLAG_LASTMSG )
237  swrite( &headerStream, "Connection: close\r\n", 19 );
238  }
239  if( contentLength > 0 )
240  {
241  char lengthString[ 16 + 8 ];
242  int lengthStringLength;
243 
244  /* About 5% of connections have HTTP caches present, and of
245  those about half get cacheing wrong, i.e. they'll cache even
246  if the "no-cache" value is set (from the ISCI Netalyzr result
247  data), so in the presence of cacheing there's a coin-toss
248  probability of the "Cache-Control" indicator below actually
249  doing what it's supposed to */
250  swrite( &headerStream, "Content-Type: ", 14 );
251  swrite( &headerStream, contentType, contentTypeLen );
252  swrite( &headerStream, "\r\nContent-Length: ", 18 );
253  lengthStringLength = sprintf_s( lengthString, 16, "%d",
254  contentLength );
255  swrite( &headerStream, lengthString, lengthStringLength );
256  swrite( &headerStream, "\r\nCache-Control: no-cache\r\n", 27 );
257  }
258  }
259  status = swrite( &headerStream, "\r\n", 2 );
260  if( cryptStatusOK( status ) )
261  headerLength = stell( &headerStream );
262  sMemDisconnect( &headerStream );
263  ENSURES( cryptStatusOK( status ) );
264  return( sendHTTPData( stream, headerBuffer, headerLength,
265  transportFlag ) );
266  }
267 
268 /* Write an HTTP response header */
269 
270 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
271 static int writeResponseHeader( INOUT STREAM *stream,
272  IN_BUFFER( contentTypeLen ) const char *contentType,
273  IN_LENGTH_SHORT const int contentTypeLen,
274  IN_LENGTH const int contentLength )
275  {
276  NET_STREAM_INFO *netStream = ( NET_STREAM_INFO * ) stream->netStreamInfo;
277  STREAM headerStream;
278  char headerBuffer[ HTTP_LINEBUF_SIZE + 8 ], lengthString[ 16 + 8 ];
279  int headerLength = DUMMY_INIT, lengthStringLength, status;
280 
281  assert( isWritePtr( stream, sizeof( STREAM ) ) );
282  assert( isReadPtr( contentType, contentTypeLen ) );
283 
284  REQUIRES( contentTypeLen > 0 && contentTypeLen < MAX_INTLENGTH );
285  REQUIRES( contentLength > 0 && contentLength < MAX_INTLENGTH );
286 
287  sMemOpen( &headerStream, headerBuffer, HTTP_LINEBUF_SIZE );
288  if( isHTTP10( stream ) )
289  swrite( &headerStream, "HTTP/1.0 200 OK\r\n", 17 );
290  else
291  {
292  swrite( &headerStream, "HTTP/1.1 200 OK\r\n", 17 );
293  if( netStream->nFlags & STREAM_NFLAG_LASTMSG )
294  swrite( &headerStream, "Connection: close\r\n", 19 );
295  }
296  swrite( &headerStream, "Content-Type: ", 14 );
297  swrite( &headerStream, contentType, contentTypeLen );
298  swrite( &headerStream, "\r\nContent-Length: ", 18 );
299  lengthStringLength = sprintf_s( lengthString, 16, "%d",
300  contentLength );
301  swrite( &headerStream, lengthString, lengthStringLength );
302  swrite( &headerStream, "\r\nCache-Control: no-cache\r\n", 27 );
303  if( isHTTP10( stream ) ) /* See note above on "no-cache" */
304  swrite( &headerStream, "Pragma: no-cache\r\n", 18 );
305  status = swrite( &headerStream, "\r\n", 2 );
306  if( cryptStatusOK( status ) )
307  headerLength = stell( &headerStream );
308  sMemDisconnect( &headerStream );
309  ENSURES( cryptStatusOK( status ) );
310  return( sendHTTPData( stream, headerBuffer, headerLength,
312  }
313 
314 /****************************************************************************
315 * *
316 * HTTP Access Functions *
317 * *
318 ****************************************************************************/
319 
320 /* Write data to an HTTP stream */
321 
322 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2, 4 ) ) \
323 static int writeFunction( INOUT STREAM *stream,
324  IN_BUFFER( maxLength ) const void *buffer,
325  IN_LENGTH_FIXED( sizeof( HTTP_DATA_INFO ) ) \
326  const int maxLength,
327  OUT_LENGTH_Z int *length )
328  {
329  NET_STREAM_INFO *netStream = ( NET_STREAM_INFO * ) stream->netStreamInfo;
330  HTTP_DATA_INFO *httpDataInfo = ( HTTP_DATA_INFO * ) buffer;
331  BOOLEAN forceGet = FALSE;
332  int status;
333 
334  assert( isWritePtr( stream, sizeof( STREAM ) ) );
335  assert( isReadPtr( buffer, maxLength ) );
336  assert( isWritePtr( length, sizeof( int ) ) );
337 
338  REQUIRES( maxLength == sizeof( HTTP_DATA_INFO ) );
339 
340  /* Clear return value */
341  *length = 0;
342 
343  /* Send the out-of-band HTTP header data to the client or server */
344  if( netStream->nFlags & STREAM_NFLAG_ISSERVER )
345  {
346  /* If it's an error status response, send the translated error
347  status and exit */
348  if( cryptStatusError( httpDataInfo->reqStatus ) )
349  {
350  char headerBuffer[ HTTP_LINEBUF_SIZE + 8 ];
351 
352  status = sendHTTPError( stream, headerBuffer, HTTP_LINEBUF_SIZE,
353  ( httpDataInfo->reqStatus == CRYPT_ERROR_NOTFOUND ) ? \
354  404 : \
355  ( httpDataInfo->reqStatus == CRYPT_ERROR_PERMISSION ) ? \
356  401 : 400 );
357  if( cryptStatusError( status ) )
358  return( status );
359  *length = maxLength;
360 
361  return( CRYPT_OK );
362  }
363 
364  status = writeResponseHeader( stream, httpDataInfo->contentType,
365  httpDataInfo->contentTypeLen,
366  httpDataInfo->bufSize );
367  }
368  else
369  {
370  REQUIRES( ( netStream->nFlags & STREAM_NFLAG_HTTPTUNNEL ) || \
371  httpDataInfo->contentTypeLen > 0 );
372  REQUIRES( !( ( netStream->nFlags & STREAM_NFLAG_HTTPPROXY ) &&
373  ( netStream->nFlags & STREAM_NFLAG_HTTPTUNNEL ) ) );
374  REQUIRES( netStream->host != NULL && netStream->hostLen > 0 );
375 
376  /* If there's content present to send to the server and the only
377  allowed method is GET then we have to override the use of the
378  (correct) POST with the (incorrect) GET in order to deal with a
379  broken server */
380  if( httpDataInfo->bufSize > 0 && \
381  ( netStream->nFlags & STREAM_NFLAG_HTTPREQMASK ) == STREAM_NFLAG_HTTPGET )
382  forceGet = TRUE;
383 
384  status = writeRequestHeader( stream, httpDataInfo->reqInfo,
385  httpDataInfo->contentType,
386  httpDataInfo->contentTypeLen,
387  httpDataInfo->bufSize, forceGet );
388  }
389  if( cryptStatusError( status ) )
390  return( status );
391 
392  /* Send the payload data to the client/server */
393  status = netStream->bufferedTransportWriteFunction( stream,
394  httpDataInfo->buffer, httpDataInfo->bufSize,
395  &httpDataInfo->bytesTransferred,
396  forceGet ? TRANSPORT_FLAG_NONE : \
398  if( cryptStatusError( status ) )
399  return( status );
400  if( forceGet )
401  {
402  STREAM headerStream;
403  char headerBuffer[ HTTP_LINEBUF_SIZE + 8 ];
404  int headerLength = DUMMY_INIT;
405 
406  /* We've been forced to override the use of a POST with a GET due to
407  a broken server so the header write was split into two parts with
408  the request data in the middle, we now have to send the remainder
409  of the header */
410  sMemOpen( &headerStream, headerBuffer, HTTP_LINEBUF_SIZE );
411  if( isHTTP10( stream ) )
412  swrite( &headerStream, " HTTP/1.0\r\n", 11 );
413  else
414  {
415  swrite( &headerStream, " HTTP/1.1\r\nHost: ", 17 );
416  swrite( &headerStream, netStream->host, netStream->hostLen );
417  swrite( &headerStream, "\r\n", 2 );
418  if( netStream->nFlags & STREAM_NFLAG_LASTMSG )
419  swrite( &headerStream, "Connection: close\r\n", 19 );
420  }
421  status = swrite( &headerStream, "\r\n", 2 );
422  if( cryptStatusOK( status ) )
423  headerLength = stell( &headerStream );
424  sMemDisconnect( &headerStream );
425  ENSURES( cryptStatusOK( status ) );
426  status = sendHTTPData( stream, headerBuffer, headerLength,
427  TRANSPORT_FLAG_FLUSH );
428  if( cryptStatusError( status ) )
429  return( status );
430  }
431  *length = maxLength;
432 
433  return( CRYPT_OK );
434  }
435 
436 STDC_NONNULL_ARG( ( 1 ) ) \
437 void setStreamLayerHTTPwrite( INOUT NET_STREAM_INFO *netStream )
438  {
439  /* Set the remaining access method pointers */
440  netStream->writeFunction = writeFunction;
441  }
442 #endif /* USE_HTTP */