/* ========================================================================== ** * stibclilib.c * * Copyright: * Copyright (C) 2007,2011 by Christopher R. Hertel * * Email: crh@ubiqx.mn.org * * $Id: stibclilib.c 60 2011-06-28 02:12:29Z crh $ * * -------------------------------------------------------------------------- ** * * Description: * Client library for transferring files via MS-BITS and HTTP protocols. * * -------------------------------------------------------------------------- ** * * License: * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 * * -------------------------------------------------------------------------- ** * * Notes: * This module five main types of objects: * Connection Contexts - These are represented by the stib_Ctx type. * Connection contexts are exported as opaque * pointers, since they are almost entirely * handled within the module. There are * exceptions, of course. At present, the * can be used to extract * the current error status of a connection. * Other exceptions will be introduced as * needed. * * Error class/code sets - There are three sources for the errors that * may be returned by this module. There are * standard system error codes (errno, et. al.), * the special set returned by getaddrinfo(3), * plus our own module-specific error codes. * The type is used to keep track * of the error code itself as well as the * source of the error. The latter provides * context. Function is used * to set the values in a , and * returns a pointer to a * static string describing the error. * * URLs (URIs) - The type is used to hold the * results of parsing an HTTP or HTTPS URI string * (or is it a URL? I don't really understand the * difference). The function * can be used to parse a URI string. * * Message blocks - These are basically Gbfr types (see the Gbfr * module) which are used to compose the BITS * headers. * * Parsed BITS replies - In response to a BITS or HTTP Request, the * server sends a Response message that is * formatted just like any HTTP message would * be formatted. There is a start line with * the status code followed by a series of * header lines (terminated by an empty header * line). After the headers there may be a data * blob. The function will * attempt to and parse the following pieces: * + The Start message and, extracted from that, * the status code returned by the server. * + A linked list of key/value pairs extracted * from the header list. * does not attempt to read * the message body. Use for * that. * * Fix: * BUG: Fails to read Chunked Encoding properly when reading * a stream following a GET request. We're okay up to a few * bytes less than 12K... then it fails. * * * Reading from network connections is sloppy and needs to be * re-written. * * * When composing messages, there are several instances in which * return values should be checked but are not. * * * Instead of a composition buffer (named ) in each BITS * command function, add Gbfr output buffer to the context * structure. Doing so will reduce the number of malloc/frees. * * * findHdr() probably needs to be exported. * * * Remove the use of the term "block" from the code. It doesn't * really make sense. It's a Gbufr, so call it a buffer. * * * Add support for secure (HTTPS) transfers. * * * Search for "Fix:" for specific bugs to be fixed througout the * code. * * ========================================================================== ** */ #include /* For vsprintf(3) and vsnprintf(3). */ #include /* Variable argument lists. */ #include /* For strlen(3), strcpy(3), strncpy(3). */ #include /* For isdigit(3), isxdigit(3). */ #include /* For the variable. */ #include /* For close(2). */ #include /* For getaddrinfo(3), etc. */ #include /* For socket(2), getaddrinfo(3), etc. */ #include /* For getaddrinfo(3). */ #include "stibclilib.h" /* Module header. */ /* -------------------------------------------------------------------------- ** * Defined Constants: * * BITS_GUID - The BITS GUID. * * rbSIZE - The size of the read buffer associated with an open * connection. */ #define BITS_GUID "{7df0354d-249b-430f-820d-3d2a9bef4931}" #define rbSIZE 4096 /* -------------------------------------------------------------------------- ** * Macros: * * isEmpty( S ) - If the string passed as is NULL or the empty * string (""), return (meaning "yes, it is * empty"). Otherwise return . * * safeFree( P ) - Calls free(3) if the pointer

is *not* NULL. * * Min( A, B ) - Return the minimum of two values. */ #define isEmpty( S ) ( ((NULL==(S))||('\0'==(*(S)))) ? true : false ) #define safeFree( P ) (NULL==(P) ? : free( P )) #define Min( A, B ) (((A)<(B))?(A):(B)) /* -------------------------------------------------------------------------- ** * Typedefs: * * chunkState - Parsing states for chunked encoding. * FIX: This needs better docs. * * stibContext - STiB open connection context including: * + the connected socket, * + any current error code values, * + the size of the file being sent over this connection. * + the resolved address information, so that we can * reconnect should the socket be reset. * + barious bits of information to be included in the * BITS messages. * + and a fixed-size read buffer (not a Gbfr). * The buffer is used as a queue. The field * indicates the number of bytes currently in the * buffer. The field should always be <= . * It indicates the next unused byte within the * bytes contained within . */ typedef enum { tNoChunk = 0, /* Not using chunked encoding. */ tChunked, /* Using chunked encoding & reading chunks. */ tChunkEnd, /* Chunked encoding; Reading footers. */ tFooterEoln /* Found Eoln in footer. */ } chunkState; typedef struct { int sock; /* Open and connected socket. */ stib_Error err[1]; /* Current context error status. */ int ad_family; /* Address family (see getaddrinfo(3)). */ int ad_socktype; /* Socket type. */ int ad_protocol; /* Protocol. */ size_t ad_addrlen; /* Size of the address blob. */ struct sockaddr *ad_addr; /* Pointer to an address blob. */ char *srvname; /* The server name. */ char *pathname; /* The URI path portion. */ char *agent; /* The user agent name. */ char *sessionid; /* The BITS session ID. */ chunkState Chunked; /* Chunk-encoding state. */ off_t filesize; /* Size, in bytes, of a file being sent. */ off_t chunksize; /* On reads, this is the data/chunk size. */ int bLen; /* Number of bytes currently in the . */ int bPos; /* Offset of next available char. */ unsigned char bufr[rbSIZE]; /* Read buffer. */ } stibContext; /* -------------------------------------------------------------------------- ** * Static Constants: * * stibErrString - A set of strings describing the augmented (that is, * STiB-specific) set of errors. There must be a one-to-one * mapping from stib_errCode values to stibErrString strings. */ static char *stibErrString[] = { "no error (move along, nothing to see here)", "invalid value in input", "memory allocation failure", "end of file", "could not establish a server session", "unknown error", NULL }; /* -------------------------------------------------------------------------- ** * Static Functions: */ static int charxval( const char c ) /* ------------------------------------------------------------------------ ** * Return the hex numeric value represented by the input character. * * Input: c - A hexidecimal numeral. * * Output: -1 if c was not actually a hexidecimal numeral. For shame. * Otherwise, the numeric value that the c represents. * * ------------------------------------------------------------------------ ** */ { if( isdigit( c ) ) return( c - '0' ); switch( c ) { case 'a': case 'A': return( 10 ); case 'b': case 'B': return( 11 ); case 'c': case 'C': return( 12 ); case 'd': case 'D': return( 13 ); case 'e': case 'E': return( 14 ); case 'f': case 'F': return( 15 ); } return( -1 ); } /* charxval */ static int UnEscape( char *s ) /* ------------------------------------------------------------------------ ** * Convert URL escape sequences into single byte values. * * Input: s - Pointer to the string to be un-escaped. * * Output: The string length (strlen(3)) of the un-escaped string. * * Notes: The converted string will always be nul terminated, and the * string length of the converted string will always be less than * or equal to the string length of the original value of . * * ------------------------------------------------------------------------ ** */ { int i, j; if( (NULL == s) || ('\0' == *s) ) return( 0 ); i = j = 0; do { if( '%' == s[i] && isxdigit( s[i+1] ) && isxdigit( s[i+2] ) ) { s[j++] = (16 * charxval( s[i+1] )) + charxval( s[i+2] ); i += 3; } else { s[j++] = s[i++]; } } while( '\0' != s[j] ); return( j ); } /* UnEscape */ static int reconnectCtx( stibContext *ctx ) /* ------------------------------------------------------------------------ ** * Reconnect a disconnected connection. * * Input: ctx - A pointer to an active STiB context. * * Output: 0 on success, -1 on error. The error codes will be updated * in the STiB context. * * ------------------------------------------------------------------------ ** */ { int sock; /* Create the new socket. */ sock = socket( ctx->ad_family, ctx->ad_socktype, ctx->ad_protocol ); if( sock < 0 ) { stib_errSetCC( ctx->err, stib_errClsErrno, errno ); return( -1 ); } /* Connect to the remote server. */ if( connect( sock, ctx->ad_addr, ctx->ad_addrlen ) < 0 ) { stib_errSetCC( ctx->err, stib_errClsErrno, errno ); return( -1 ); } /* Copy the new socket into the context, clear the error status, * and return. */ ctx->sock = sock; stib_errSetCC( ctx->err, stib_errClsNone, stib_errSuccess ); return( 0 ); } /* reconnectCtx */ static ssize_t ctxSend( stibContext *ctx, Gbfr *gb, int flags ) /* ------------------------------------------------------------------------ ** * Wrapper around send(2) that reconnects dropped connections. * * Input: ctx - A pointer to the connection context structure. * gb - A Gbfr containing the buffer to be transmitted. * flags - Flags. See send(2). * * Output: On success, the number of characters sent. On error, the * return value is -1 and the error code is set in . * * Notes: This function may set the context error structure to indicate * any of the system errors that can be returned by send(2) or * connect(2). * * ------------------------------------------------------------------------ ** */ { int result; int ErrNo; result = send( ctx->sock, bfrGbfr( gb ), lenGbfr( gb ), flags ); if( result < 0 ) { ErrNo = errno; switch( ErrNo ) { case ECONNRESET: (void)close( ctx->sock ); /* drop through */ case EBADF: { if( reconnectCtx( ctx ) < 0 ) return( -1 ); result = send( ctx->sock, bfrGbfr( gb ), lenGbfr( gb ), flags ); break; } } if( result < 0 ) { stib_errSetCC( ctx->err, stib_errClsErrno, errno ); return( -1 ); } } return( result ); } /* ctxSend */ static int getCharCtx( stib_Ctx *Ctx ) /* ------------------------------------------------------------------------ ** * Read a character from a connection. * * Input: Ctx - A pointer to a STiB connection context, which represents * the connection from which the character will be read. * * Output: On error, -1 is returned and the error message is set within * the connection context (). Use to * directly access the error information. * On success, a byte value is returned. It will, of course, be * in the range 0..255. * * Errors: stib_errClsErrno / EAGAIN or * stib_errClsErrno / EWOULDBLOCK * Returned if there are currently no bytes available on the * connection input stream, but the stream is not closed. * (The stream is read in non-blocking mode.) * * stib_errClsErrno / * * An error returned from recv(2). * * stib_errClsSTiB / stib_errEOF * Returned if there are no remaining characters in the input * buffer and the remote server has closed the connection. * * ------------------------------------------------------------------------ ** */ { stibContext *ctx = (stibContext *)Ctx; ssize_t result; if( ctx->bPos >= ctx->bLen ) { ctx->bPos = ctx->bLen = 0; result = recv( ctx->sock, ctx->bufr, rbSIZE, 0 ); if( result <= 0 ) { if( 0 == result ) stib_errSetCC( ctx->err, stib_errClsSTiB, stib_errEOF ); else stib_errSetCC( ctx->err, stib_errClsErrno, errno ); return( -1 ); } ctx->bLen = result; } return( ctx->bufr[(ctx->bPos)++] ); } /* getCharCtx */ static off_t readHexLine( stibContext *ctx ) /* ------------------------------------------------------------------------ ** * Read what should be a line containing only a hexadecimal number. * * Input: ctx - A pointer to an active STiB context. * * Output: On success, the integer value of the string that was read. * On error, -1 is returned and the error condition is stored * in the context. * * Notes: This is primarily used for parsing chunked encoding. * * The hex line is read from the context read buffer. * * ------------------------------------------------------------------------ ** */ { int i, c; char tmp[16]; /* Read the expected hex string. */ c = getCharCtx( ctx ); for( i = 0; (i < 15) && isxdigit( c ); i++ ) { tmp[i] = c; c = getCharCtx( ctx ); } tmp[i] = '\0'; /* Read to the end of the line and/or check for errors. * getCharCtx() will set the context error status if there's a problem. */ while( (c != '\n') && (c > 0) ) c = getCharCtx( ctx ); if( c < 0 ) return( -1 ); return( (off_t)strtoll( tmp, NULL, 16 ) ); } /* readHexLine */ static stib_parsedMsg *allocpMsg( Gbfr *gb ) /* ------------------------------------------------------------------------ ** * Allocate a parsed message structure with space for the start string. * * Input: gb - Pointer to a Gbfr containing the start string to be * copied into the newly allocated parsed message * structure. * * Output: A pointer to the allocated and initialized parsed message, * structure, or NULL if memory allocation failed. * * If the return status could not be read from the status line * of the response, the value of the field will be -1. * Otherwise, the field of the parsed message structure * will contain the extracted status code. * * Notes: The start string is copied into its own special location * within the structure, and the return code is extracted and * stored as well. The header linked list is initialized to * an empty list, and the pointer to the message body Gbfr is * preset to empty. * * ------------------------------------------------------------------------ ** */ { stib_parsedMsg *pMsg; const char *s; /* Allocate the parsed message structure, plus enough room to store the * start string. len> is the total (including NUL) length of the * start string, but the size of includes one byte for * the start string. */ pMsg = (stib_parsedMsg *)calloc( 1, sizeof( stib_parsedMsg ) + gb->len - 1 ); /* If we were able to allocate the memory... */ if( pMsg ) { (void)ubi_slInitList( (ubi_slListPtr)pMsg ); /* Init the header list. */ (void)strcpy( pMsg->start, bfrGbfr( gb ) ); /* Copy the start string. */ /* Parse out and convert the status code. */ s = bfrGbfr( gb ); while( (' ' != *s) && ('\0' != *s) ) s++; if( (' ' == *s) && isdigit( s[1] ) ) pMsg->status = atoi( s ); else pMsg->status = -1; } return( pMsg ); } /* allocpMsg */ static stib_hdrNode *findHdr( const stib_parsedMsg *pMsg, const char *findme ) /* ------------------------------------------------------------------------ ** * Scan a parsed list of message headers for the given header name. * * Input: pMsg - Pointer to the parsed message structure that * contains the linked list of headers to be searched. * findme - A pointer to a string for which to search. * * Output: A pointer to the node that was found, or NULL. * * Notes: The comparison is case insensitive. * * ------------------------------------------------------------------------ ** */ { ubi_slNode *node; stib_hdrNode *hdr; for( node = ubi_slFirst( pMsg ); NULL != node; node = ubi_slNext( node ) ) { hdr = (stib_hdrNode *)node; if( 0 == strcasecmp( hdr->key, findme ) ) return( hdr ); } return( NULL ); } /* findHdr */ static bool addHdrEntry( ubi_slList *list, const Gbfr *gb, const int delim_pos ) /* ------------------------------------------------------------------------ ** * Add a header string to a STiB header linked list. * * Input: list - Pointer to the linked list header. * gb - Pointer to a Gbfr containing the header string * to be copied into the stib_hdrNode structure. * delim_pos - The position of the first colon (':') character, * if any, found in the header string. * * Output: True if successful, else false. False is returned if a new * linked list node could not be allocated. The correct STiB * error in this case is stib_errClsSTiB/stib_errMallocFail. * * Notes: If is greater than zero, the string is split into * two at the offset given by . The field of * the stib_hdrNode structure is set to point to the second half * of the string. If is <= zero, will be NULL. * * ------------------------------------------------------------------------ ** */ { stib_hdrNode *tmpNode; tmpNode = (stib_hdrNode *)calloc( 1, sizeof( stib_hdrNode ) + gb->len - 1 ); if( NULL == tmpNode ) return( false ); (void)strcpy( tmpNode->key, gb->bufr ); if( delim_pos > 0 ) { tmpNode->key[delim_pos] = '\0'; tmpNode->val = &(tmpNode->key[delim_pos + 1]); } (void)ubi_slAddTail( list, tmpNode ); return( true ); } /* addHdrEntry */ static bool parseHdrEntries( stibContext *ctx, stib_parsedMsg *pMsg, Gbfr *scratch ) /* ------------------------------------------------------------------------ ** * Parse HTTP/BITS headers and store them in a linked list. * * Input: ctx - Pointer to the connection context. * pMsg - Pointer to the parsed message context. * scratch - Pre-allocated scratch space. Might as well use it. * * Output: True on success, false on error. * If an error is encountered, the error class/code will be * updated in . * * Errors: stib_errClsSTiB/stib_errMallocFail * Any error returned by . * * ------------------------------------------------------------------------ ** */ { int delim_pos = 0; int c; bool trim; clearGbfr( scratch ); c = getCharCtx( ctx ); while( c > 0 ) { switch( c ) { case '\n': if( 0 == scratch->len ) /* Blank line. End of headers. */ return( true ); /* Non-blank lines get added to the linked list. */ if( (addcGbfr( scratch, '\0' ) < 0) || !addHdrEntry( (ubi_slList *)pMsg, scratch, delim_pos ) ) { stib_errSetCC( ctx->err, stib_errClsSTiB, stib_errMallocFail ); return( false ); } delim_pos = 0; clearGbfr( scratch ); c = getCharCtx( ctx ); break; default: /* Add the byte to the scratch buffer. */ trim = false; if( (':' == c) && (0 == delim_pos) ) { delim_pos = scratch->len; trim = true; /* Trim any blanks that immediately follow. */ } if( ('\r' != c) && (addcGbfr( scratch, c ) < 0) ) { stib_errSetCC( ctx->err, stib_errClsSTiB, stib_errMallocFail ); return( false ); } /* If we just read the delimiting colon (':'), * skip any blanks that immediately follow. */ if( trim ) while( ' ' == (c = getCharCtx( ctx )) ); else c = getCharCtx( ctx ); break; } } /* Check to see if getCharCtx() returned an error. */ if( c ) return( false ); return( true ); } /* parseHdrEntries */ /* -------------------------------------------------------------------------- ** * Functions: */ void stib_errSetCC( stib_Error *err, const stib_errClass class, const int code ) /* ------------------------------------------------------------------------ ** * Set the error class and code of a stib_Error structure. * * Input: err - A pointer to the error variable to be set, or NULL. * class - The error class. * code - The error code. * * Output: * * Notes: We permit to be NULL because it makes it easy for the * caller to choose to ignore the error codes. If is * NULL, no error code is set and the function just returns. * * ------------------------------------------------------------------------ ** */ { if( NULL != err ) { err->class = class; err->code = code; } } /* stib_errSetCC */ const char *stib_errStr( const stib_Error *err ) /* ------------------------------------------------------------------------ ** * Return a pointer to a string describing an error code. * * Input: err - A pointer to a STiB error structure. * * Output: A pointer to a static constant string that describes (in * americlish) the error indicated by the error class/code. * * Notes: The error class indicates the source of the error code, which * may be the getaddrinfo(3) API, an error, or a STiB- * specific error. * * ------------------------------------------------------------------------ ** */ { int ecode = err->code; switch( err->class ) { case stib_errClsSTiB: if( (ecode < 0) || (stib_errCodeMAX < ecode) ) ecode = stib_errCodeMAX; return( stibErrString[ecode] ); case stib_errClsGAI: return( gai_strerror( ecode ) ); case stib_errClsErrno: return( strerror( ecode ) ); default: /* Only here to silence a warning. */ break; } return( "" ); } /* stib_errStr */ const stib_Error *stib_ctxErr( stib_Ctx *Ctx ) /* ------------------------------------------------------------------------ ** * Return a pointer to the error status of the given connection context. * * Input: Ctx - A pointer to a connection context. * * Output: A pointer to the error status of the context. * * ------------------------------------------------------------------------ ** */ { return( ((stibContext *)Ctx)->err ); } /* stib_ctxErr */ stib_parsedURI *stib_parseURI( const char *uri ) /* ------------------------------------------------------------------------ ** * Parse an HTTP/HTTPS URI string into a parsed URI structure. * * Input: uri - A pointer to a string supposedly containing a URI. * * Output: If the string could not be parsed, this function returns * NULL. Otherwise it returns a pointer to a stib_parsedURI * structure. String fields in the structure will be NULL if * the respective part of the URI was not given. * * Notes: If the return value is not NULL, the memory allocated to the * parsed URI object returned must be freed with a call to * free(3). * * This is a fairly simplistic parser. It handles only * HTTP/HTTPS URIs and does not fill in any default values. * * ------------------------------------------------------------------------ ** * * This is highly inelegant brute-force code, but it works. * * At the outset, we allocate (from the stack) at least as much * space as we need for parsed strings, plus a parsed URI * structure. We parse our URI into those spaces, calculate * the amount of memory we *actually* need, and then mallocate * from the heap. Then we copy the entire thing into the * malloc(3)'d memory and return a pointer. * * Since we allocate all of the memory in a single malloc(3) * call, it can be freed using a single call to free(3). * * ------------------------------------------------------------------------ ** */ { int i; int size; char scratch[ 5 + strlen( uri ) ]; /* +5 for NUL terminators. */ char *s = scratch; stib_parsedURI tmp_pUri = { false, NULL, NULL, NULL, NULL, NULL }; stib_parsedURI *result; /* If there is a scheme name ("http:" or "https:") use it to determine * whether or not we will be using a secured connection. We default to * an unsecured (http) connection. */ if( 0 == strncmp( "http://", uri, 7 ) ) uri += 7; else { if( 0 == strncmp( "https://", uri, 8 ) ) { uri += 8; tmp_pUri.secure = true; } } /* We should now be at the [user[:pass]@]server[:port] part of the * URI. Start by trying to parse out the [user[:pass]@] substring. */ for( i = 0; ('@' != uri[i]) && ('/' != uri[i]) && ('\0' != uri[i]); i++ ) ; if( '@' == uri[i] ) { /* Copy the [user[:pass]] substring into the scratch buffer, * then skip past it in the string. */ if( i > 0 ) tmp_pUri.user = strncpy( s, uri, i ); s[i] = '\0'; s += (i + 1); uri += (i + 1); /* If there is a [:pass] substring, find it and carve it out. */ for( i = 0; '\0' != scratch[i]; i++ ) { if( ':' == scratch[i] ) { scratch[i] = '\0'; tmp_pUri.pass = scratch + i + 1; break; } } } /* We have now read past the [user[:pass]@] substring, if there is one. * The next thing to look for is the server[:port] substring. * The server ID is a required piece. */ for( i = 0; ('/' != uri[i]) && ('\0' != uri[i]); i++ ) ; if( 0 == i ) return( NULL ); tmp_pUri.serv = strncpy( scratch, uri, i ); scratch[i] = '\0'; uri += i; /* If the server ID is an IPv6 address, skip past it in our copy. */ if( '[' == *s ) { while( (']' != *s) && ('\0' != *s) ) s++; if( ']' != *s ) return( NULL ); } /* Now look in our copy for a colon that would indicate the [:port] substring, * if there is one. */ while( (':' != *s) && ('\0' != *s) ) s++; if( ':' == *s ) { *s = '\0'; s++; tmp_pUri.port = s; while( '\0' != *s ) /* Skip to the end of the copied port string. */ s++; } s++; /* One byte past the end of server[:port]. */ /* If there's a path, copy that too. */ if( '/' == *uri ) tmp_pUri.path = strcpy( s, uri ); /* All parsed, now unescape each string and collect the total length. * NULL-out any empty strings. */ size = 0; i = UnEscape( tmp_pUri.user ); if( 0 == i ) tmp_pUri.user = NULL; else size += (i + 1 ); i = UnEscape( tmp_pUri.pass ); if( 0 == i ) tmp_pUri.pass = NULL; else size += (i + 1); i = UnEscape( tmp_pUri.serv ); if( 0 == i ) tmp_pUri.serv = NULL; else size += (i + 1); i = UnEscape( tmp_pUri.port ); if( 0 == i ) tmp_pUri.port = NULL; else size += (i + 1); i = UnEscape( tmp_pUri.path ); if( 0 == i ) tmp_pUri.path = NULL; else size += (i + 1); /* Now allocate the stib_parsedURI structure plus needed string space, * and copy the parsed strings into the new structure. */ result = (stib_parsedURI *)calloc( (sizeof(stib_parsedURI) + size), 1 ); if( NULL != result ) { result->secure = tmp_pUri.secure; s = (char *)&result[1]; if( tmp_pUri.user ) { result->user = strcpy( s, tmp_pUri.user ); s += 1 + strlen( s ); } if( tmp_pUri.pass ) { result->pass = strcpy( s, tmp_pUri.pass ); s += 1 + strlen( s ); } if( tmp_pUri.serv ) { result->serv = strcpy( s, tmp_pUri.serv ); s += 1 + strlen( s ); } if( tmp_pUri.port ) { result->port = strcpy( s, tmp_pUri.port ); s += 1 + strlen( s ); } if( tmp_pUri.path ) { result->path = strcpy( s, tmp_pUri.path ); s += 1 + strlen( s ); } } return( result ); } /* stib_parseURI */ int stib_addHdr2Block( Gbfr *hdr, char *fmt, ... ) /* ------------------------------------------------------------------------ ** * Add a header line to a buffer. * * Input: hdr - A pointer to the Gbfr buffer structure that holds the * message header as it is being composed. * fmt - A format string. * ... - The variable list of parameters to be merged with the * format string to create the new string. * * Output: The total number of bytes of the resulting data block. * The function returns -1 if the string could not be formed * due to a memory allocation failure. * * Notes: The return value indicates the number of bytes in the composed * message block. There is no terminating NUL (that is, the * composed message is not a C string). The result is a * length-delimited block of bytes. * * ------------------------------------------------------------------------ ** */ { Gbfr tmp[1]; /* Scratch space. */ va_list ap; /* Variable argument element. */ int result; /* Initialize the temporary Gbfr. */ if( !initGbfr( tmp ) ) return( -1 ); /* Write the formatted string to a temporary buffer. * This may require two attempts. If the buffer is not large * enough, we will at least be told how large it needs to be. * Then we extend the buffer and try again. See vsnprintf(3). * Notes: * + The "2 +" is to account for the terminating "\r\n", which * will be added later. * + We use directly here, not via the Gbfr API. */ va_start( ap, fmt ); result = 2 + vsnprintf( tmp->bufr, tmp->bSize, fmt, ap ); va_end( ap ); /* Did it fit? */ if( result <= tmp->bSize ) tmp->len = result; else { clearGbfr( tmp ); if( growGbfr( tmp, result ) < 0 ) { (void)freebfrGbfr( tmp ); return( -1 ); } /* Second try. */ va_start( ap, fmt ); tmp->len = 2 + vsprintf( tmp->bufr, fmt, ap ); va_end( ap ); } tmp->bufr[tmp->len - 2] = '\r'; tmp->bufr[tmp->len - 1] = '\n'; /* If we made it this far, the formatted result is contained in * and len> is the total length (there is no NUL byte) needed * to store it. Add the new byte string to the existing header block. */ result = concatGbfr( hdr, tmp->bufr, tmp->len ); /* Finished. */ (void)freebfrGbfr( tmp ); return( result ); } /* stib_addHdr2Block */ stib_Ctx *stib_Connect( const char *server, const char *service, stib_Error *err ) /* ------------------------------------------------------------------------ ** * Create a TCP socket and connect it to the destination server. * * Input: server - The name (or IPv[4|6] address as a string) of the * server to which to connect. * service - The service port, as a string. This may either be * a decimal numeric value (e.g. "8080") or a service * name (e.g."http-alt"). * err - A pointer to a stib_Error structure, or NULL. * * Output: On success, an opaque pointer to a connection context (type * stib_Ctx *). * On error, the return value is NULL. * * Notes: If the function fails to establish a connection and is * not NULL, then will contain an error class/code * indicating the cause of the failure. * * Errors: stib_errClsSTiB/stib_errBadValue * Indicates that at least one of the or * parameters was either NULL or the empty string. * * stib_errClsGAI/ * Indicates an error returned from getaddrinfo(3). See the * getaddrinfo(3) manual page for possible error codes. * * stib_errClsErrno/ * Indicates an error returned either from socket(2) or * connect(2). See errno(3) for a list of possible values. * * ------------------------------------------------------------------------ ** */ { struct addrinfo hints[1]; struct addrinfo *servinfo; int gai_result; int sock; char *tmpname; struct sockaddr *tmpaddr; stibContext *ctx; /* Check for valid values. */ if( isEmpty( server ) || isEmpty( service ) ) { stib_errSetCC( err, stib_errClsSTiB, stib_errBadValue ); return( NULL ); } /* Lookup the number. */ (void)memset( hints, 0, sizeof( struct addrinfo ) ); hints->ai_family = AF_UNSPEC; /* Either IPv4 or IPv6. */ hints->ai_socktype = SOCK_STREAM; /* TCP stream socket. */ hints->ai_flags = AI_CANONNAME; /* Request official name. */ gai_result = getaddrinfo( server, /* Destination server. */ service, /* Destination port. */ hints, /* Hints to fill in. */ &servinfo ); /* Results returned. */ if( gai_result ) { stib_errSetCC( err, stib_errClsGAI, gai_result ); return( NULL ); } /* Create the socket. */ sock = socket( servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol ); if( sock < 0 ) { stib_errSetCC( err, stib_errClsErrno, errno ); freeaddrinfo( servinfo ); return( NULL ); } /* Connect to the remote server. * We just use the first address returned because * we don't know how to select the "best" result. */ if( connect( sock, servinfo->ai_addr, servinfo->ai_addrlen ) < 0 ) { stib_errSetCC( err, stib_errClsErrno, errno ); freeaddrinfo( servinfo ); return( NULL ); } /* Allocate and initialize the stibContext. */ ctx = (stibContext *)calloc( 1, sizeof( stibContext ) ); tmpaddr = (struct sockaddr *)malloc( servinfo->ai_addrlen ); tmpname = strdup( servinfo->ai_canonname ? servinfo->ai_canonname : server ); if( NULL == ctx || NULL == tmpaddr || NULL == tmpname ) { safeFree( ctx ); safeFree( tmpaddr ); safeFree( tmpname ); freeaddrinfo( servinfo ); stib_errSetCC( err, stib_errClsSTiB, stib_errMallocFail ); return( NULL ); } /* Success! * Clear the error code, and copy in additional important fields. */ stib_errSetCC( err, stib_errClsNone, stib_errSuccess ); (void)memcpy( tmpaddr, servinfo->ai_addr, servinfo->ai_addrlen ); ctx->sock = sock; ctx->ad_family = servinfo->ai_family; ctx->ad_socktype = servinfo->ai_socktype; ctx->ad_protocol = servinfo->ai_protocol; ctx->ad_addrlen = servinfo->ai_addrlen; ctx->ad_addr = tmpaddr; ctx->srvname = tmpname; /* Some cleanup, then done. */ freeaddrinfo( servinfo ); return( (stib_Ctx *)ctx ); } /* stib_Connect */ void stib_Disconnect( stib_Ctx *Ctx ) /* ------------------------------------------------------------------------ ** * Disconnect the context TCP connection without losing state. * * Input: Ctx - A pointer to the connection context. * * Output: * * Notes: This function is primarily for testing. It closes the socket * connecting the client to the BITS server. The next time a * message is to be sent to the server, the connection should be * automatically re-established. * * ------------------------------------------------------------------------ ** */ { stibContext *ctx = (stibContext *)Ctx; (void)close( ctx->sock ); } /* stib_Disconnect */ int stib_CloseCtx( stib_Ctx *Ctx ) /* ------------------------------------------------------------------------ ** * Close an open STiB connection, freeing all associated memory. * * Input: Ctx - Pointer to the STiB connection context to be closed. * * Output: 0 on success. If the socket (included as part of the * structure) cannot be closed, the system error code is * returned. See close(2) and errno(3). * * ------------------------------------------------------------------------ ** */ { stibContext *ctx = (stibContext *)Ctx; int err = 0; /* Close the socket, capturing any errors. */ if( close( ctx->sock ) < 0 ) err = errno; /* Free the additional memory tacked on to our main structure. * ensures that the passed pointer is not NULL before * calling free(3). */ safeFree( ctx->srvname ); safeFree( ctx->pathname ); safeFree( ctx->agent ); safeFree( ctx->sessionid ); safeFree( ctx->ad_addr ); free( ctx ); return( err ); } /* stib_CloseCtx */ int stib_sendBlocks( stib_Ctx *Ctx, ... ) /* ------------------------------------------------------------------------ ** * Send one or more blocks on the connected socket. * * Input: Ctx - A pointer to a STiB context, which is the connection on * which to send the block contents. * ... - A NULL-terminated list of pointers to Gbfrs. * * Output: The total number of bytes sent, or -1 on error. * * Notes: If there is an error sending the blocks, the error code will * be set in . * * Errors: stib_errClsErrno/ * See send(2) for possible error codes. * * ------------------------------------------------------------------------ ** */ { stibContext *ctx = (stibContext *)Ctx; va_list ap; Gbfr *gb; Gbfr *next; ssize_t result; int total = 0; int flags = MSG_MORE; /* Initialize the error code. */ stib_errSetCC( ctx->err, stib_errClsNone, stib_errSuccess ); /* Read through the buffers one at a time. * Maintain a look-ahead () so that we know when to * stop using the MSG_MORE flag. */ va_start( ap, Ctx ); for( gb = va_arg( ap, Gbfr * ); (gb != NULL); gb = next ) { next = va_arg( ap, Gbfr * ); if( NULL == next ) flags = 0; if( (result = ctxSend( ctx, gb, flags )) < 0 ) return( -1 ); total += (int)result; } va_end( ap ); return( total ); } /* stib_sendBlocks */ void stib_freepMsg( stib_parsedMsg *pMsg ) /* ------------------------------------------------------------------------ ** * Free a previously allocated stib_parsedMsg structure, and header nodes. * * Input: pMsg - A pointer to an allocated and potentially populated * stib_parsedMsg structure. * * Output: * * ------------------------------------------------------------------------ ** */ { ubi_slNodePtr node; if( pMsg ) { while( NULL != (node = ubi_slRemHead( pMsg )) ) free( node ); free( pMsg ); } } /* stib_freepMsg */ stib_parsedMsg *stib_recvHeaders( stib_Ctx *Ctx ) /* ------------------------------------------------------------------------ ** * Receive the headers of an HTTP message into a parsed message structure. * * Input: Ctx - A pointer to the connection context on which a * message is expected to be received. * * Output: On error, NULL is returned and the error class/code are set * in . * On success, a pointer to a stib_parsedMsg structure is * returned. The parsed message structure will contain a list * of header key/value pairs, but the message body will NOT be * read. * * Errors: stib_errClsSTiB/stib_errMallocFail * Unable to allocate memory from the heap. * Any error returned by . * See: . * * ------------------------------------------------------------------------ ** */ { stibContext *ctx = (stibContext *)Ctx; int c; Gbfr *scratch; stib_parsedMsg *pMsg; stib_hdrNode *hdr; /* Allocate and initialize the scratch space. */ scratch = allocGbfr( 250 ); if( NULL == scratch ) { stib_errSetCC( ctx->err, stib_errClsSTiB, stib_errMallocFail ); return( NULL ); } /* Read the start message. */ for( c = getCharCtx( Ctx ); ('\n' != c) && (c > 0); c = getCharCtx( Ctx ) ) { if( ('\r' != c) && (addcGbfr( scratch, c ) < 0) ) { stib_errSetCC( ctx->err, stib_errClsSTiB, stib_errMallocFail ); freeGbfr( scratch ); return( NULL ); } } if( c < 0 ) /* Check for errors. */ { /* If getCharCtx() returned a value less than zero, then it has * already set the error code in the context. */ freeGbfr( scratch ); return( NULL ); } if( addcGbfr( scratch, '\0' ) < 0 ) /* Terminate the string. */ { stib_errSetCC( ctx->err, stib_errClsSTiB, stib_errMallocFail ); freeGbfr( scratch ); return( NULL ); } /* Allocate the stib_parsedMsg structure and copy the start string into it. */ pMsg = allocpMsg( scratch ); if( NULL == pMsg ) { stib_errSetCC( ctx->err, stib_errClsSTiB, stib_errMallocFail ); freeGbfr( scratch ); return( NULL ); } /* Read header lines from the connection stream. * Note: can set the ctx error code. * Not mentioned in the function header because it's a static * function and the codes returned are already covered. */ if( !parseHdrEntries( ctx, pMsg, scratch ) ) { freeGbfr( scratch ); stib_freepMsg( pMsg ); return( NULL ); } /* Scan the headers to find the message body length. */ ctx->Chunked = tNoChunk; ctx->chunksize = 0; if( (hdr = findHdr( pMsg, "Content-Length" )) && (hdr->val) ) ctx->chunksize = (off_t)strtoll( hdr->val, NULL, 0 ); else { /* No message body length. Check for chunked encoding. */ if( (hdr = findHdr( pMsg, "Transfer-Encoding" )) && (hdr->val) && (0 == strncasecmp( "chunked", hdr->val, 7 )) ) { ctx->Chunked = tChunked; ctx->chunksize = readHexLine( Ctx ); } } /* Clean up and go home. */ freeGbfr( scratch ); return( pMsg ); } /* stib_recvHeaders */ ssize_t stib_readBody( stib_Ctx *Ctx, unsigned char *bufr, const size_t bsize ) /* ------------------------------------------------------------------------ ** * Read the body of an HTTP/BITS message. * * Input: Ctx - A pointer to the connection context. * bufr - A pointer to a buffer into which received bytes will * be copied. * bsize - The size, in bytes, of . * That is, the maximum number of bytes to be read into * . * * Output: The number of bytes actually read into . * * + If the return value is less than but greater than * zero, then the read was successful and more bytes may be * available. * * + If the return value is zero, then no bytes were read. * This indicates EOF. * * + On error, the function returns -1 and the error code is * stored in the context. Use stib_ctxErr() to retrieve the * error code. * * ------------------------------------------------------------------------ ** */ { stibContext *ctx = (stibContext *)Ctx; ssize_t result; ssize_t cpylen; ssize_t len; len = 0; while( len < bsize ) { /* Read as much as we can from the context buffer. */ cpylen = ctx->bLen - ctx->bPos; if( cpylen && ctx->chunksize ) { cpylen = Min( cpylen, bsize ); cpylen = Min( cpylen, ctx->chunksize ); (void)memcpy( bufr, &(ctx->bufr[ctx->bPos]), cpylen ); ctx->bPos += cpylen; len += cpylen; ctx->chunksize -= cpylen; } /* If there's more room in and we're not at the end of a chunk, * then we can bypass the context buffer (which should now be empty) * and read directly from the socket. */ cpylen = (bsize - len); if( cpylen && ctx->chunksize ) { cpylen = Min( cpylen, ctx->chunksize ); result = recv( ctx->sock, &bufr[len], cpylen, 0 ); if( result < 0 ) { stib_errSetCC( ctx->err, stib_errClsErrno, errno ); return( -1 ); } if( 0 == result ) { stib_errSetCC( ctx->err, stib_errClsSTiB, stib_errEOF ); return( len ); } len += result; ctx->chunksize -= result; } /* If there is still room in , then perhaps we've just read to * the end of a chunk and need to read another chunk. * Note that the call to will call * which will refill the context buffer. If there is another * chunk to read, we'll loop 'round and read it. * If we've reached the last chunk, however, then we will need to * read until we get a blank line ("/r/n/r/n") or fill . */ cpylen = (bsize - len); if( cpylen ) { switch( ctx->Chunked ) { case tNoChunk: /* Not using chunked encoding. No footers. * If there's nothing left to read, then we are finished. */ if( 0 == ctx->chunksize ) return( len ); break; case tChunked: /* Currently reading chunks. * If we're done with this chunk, check for another. * If the next chunk is zero length, then we are done with chunks, * and will look for footers. Otherwise, we'll go back to the * top of the loop and read more. */ if( 0 == ctx->chunksize ) { if( 0 == (ctx->chunksize = readHexLine( ctx )) ) ctx->Chunked = tChunkEnd; } break; default: /* Finished reading chunks, looking for footers. */ //FIX: We don't actually read the footers. We ignore them. // We need a testing system that generates footers in // order to test the code that should go here. // Think: For the Get command, we never want to read // the footers. They'd become part of the // output. Reading footers should be done // separately. Somehow. return( len ); } } } /* end while */ return( len ); } /* stib_readBody */ stib_parsedMsg *stib_Ping( stib_Ctx *Ctx, const char *pathname, const char *agent ) /* ------------------------------------------------------------------------ ** * Perform a BITS Ping operation. * * Input: Ctx - Pointer to an open connection to the server. * pathname - A pathname, required in the BITS_PING start line. * agent - Optional; may be NULL. * Used to identify the user agent making the request. * * Output: A pointer to a stib_parsedMsg structure that will contain the * server's response, if any. * * If the server did not respond, or if an error occurred, this * function will return NULL and will be set with an * appropriate error class and code. Use to * retrieve the error. * * Errors: stib_errClsSTiB/stib_errBadValue * The parameter is either NULL or the empty string. * stib_errClsSTiB, stib_errMallocFail * Temporary storage could not be allocated from the heap. * Any error that can be returned from . * See for additional error codes. * * Notes: The BITS Ping may be used stand-alone or at the start of an * upload. It is also optional as the first packet. With that * in mind, this function does not store the and * strings in the connection context. * * ------------------------------------------------------------------------ ** */ { stibContext *ctx = (stibContext *)Ctx; Gbfr *blk; /* Sanity check. */ if( isEmpty( pathname ) ) { stib_errSetCC( ctx->err, stib_errClsSTiB, stib_errBadValue ); return( NULL ); } /* Create and send the ping. */ blk = allocGbfr( 1024 ); if( NULL == blk ) { stib_errSetCC( ctx->err, stib_errClsSTiB, stib_errMallocFail ); return( NULL ); } // Fix: Test return values. // Can stib_addHdr2Block() set the hdr to invalid somehow? (void)stib_addHdr2Block( blk, "BITS_POST %s HTTP/1.1", pathname ); (void)stib_addHdr2Block( blk, "BITS-Packet-Type: Ping" ); if( agent ) (void)stib_addHdr2Block( blk, "User-Agent: %s", agent ); (void)stib_addHdr2Block( blk, "Accept: */*" ); (void)stib_addHdr2Block( blk, "Host: %s", ctx->srvname ); (void)stib_addHdr2Block( blk, "Content-Length: 0" ); (void)stib_addHdr2Block( blk, "Connection: Keep-Alive" ); (void)stib_addHdr2Block( blk, "" ); (void)stib_sendBlocks( ctx, blk, NULL ); freeGbfr( blk ); /* Receive and parse the response. */ return( stib_recvHeaders( ctx ) ); } /* stib_Ping */ stib_parsedMsg *stib_CreateSession( stib_Ctx *Ctx, const char *pathname, const char *agent, const off_t fsize ) /* ------------------------------------------------------------------------ ** * Create a BITS upload session. * * Input: Ctx - Pointer to an open connection to the server. * pathname - A pointer to a pathname string. This is the * pathname portion of the destination URL. * This value is required in the BITS_POST start line. * agent - Used to identify the user agent making the request. * This value is optional, and may be NULL. * fsize - Size, in bytes, of the file to be transferred. * This value is required, and is used in the BITS * Fragment commands that actually transfer the * chunks of file data. * * Output: A pointer to a stib_parsedMsg structure that will contain the * server's response, if any. * * If the server did not respond, or if some other error occurred, * this function will return NULL and will be set with an * appropriate error class and code. Use to * retrieve the error. * * Errors: stib_errClsSTiB/stib_errBadValue * The parameter is zero, or the parameter * is NULL or the empty string. * stib_errClsSTiB, stib_errMallocFail * Temporary storage could not be allocated from the heap. * Any error that can be returned from . * See for additional error codes. * * Notes: This function creates a BITS session. The and * strings are copied into the connection context * () for use in future calls. * * ------------------------------------------------------------------------ ** */ { stibContext *ctx = (stibContext *)Ctx; stib_parsedMsg *pMsg; Gbfr *blk; stib_hdrNode *found; /* Sanity check. */ if( isEmpty( pathname ) || (fsize < 1) ) { stib_errSetCC( ctx->err, stib_errClsSTiB, stib_errBadValue ); return( NULL ); } /* Create and send the Create-Session message. */ blk = allocGbfr( 1024 ); if( NULL == blk ) { stib_errSetCC( ctx->err, stib_errClsSTiB, stib_errMallocFail ); return( NULL ); } // Fix: Test return values. // Can stib_addHdr2Block() set the hdr to invalid somehow? (void)stib_addHdr2Block( blk, "BITS_POST %s HTTP/1.1", pathname ); (void)stib_addHdr2Block( blk, "BITS-Packet-Type: Create-Session" ); (void)stib_addHdr2Block( blk, "BITS-Supported-Protocols: %s", BITS_GUID ); (void)stib_addHdr2Block( blk, "Accept: */*" ); (void)stib_addHdr2Block( blk, "Host: %s", ctx->srvname ); if( agent ) (void)stib_addHdr2Block( blk, "User-Agent: %s", agent ); (void)stib_addHdr2Block( blk, "Content-Length: 0" ); (void)stib_addHdr2Block( blk, "Connection: Keep-Alive" ); (void)stib_addHdr2Block( blk, "" ); (void)stib_sendBlocks( ctx, blk, NULL ); freeGbfr( blk ); /* Receive the response. */ pMsg = stib_recvHeaders( ctx ); if( NULL == pMsg ) return( NULL ); /* Got a response. * Make sure it's good and, if so, copy important context strings. */ found = findHdr( pMsg, "BITS-Session-Id" ); if( !found ) stib_errSetCC( ctx->err, stib_errClsSTiB, stib_errNoSession ); else { ctx->sessionid = strdup( found->val ); ctx->pathname = strdup( pathname ); ctx->agent = agent ? strdup( agent ) : NULL; ctx->filesize = fsize; } /* All done. */ return( pMsg ); } /* stib_CreateSession */ stib_parsedMsg *stib_Fragment( stib_Ctx *Ctx, const off_t off, const int len, const char *src ) /* ------------------------------------------------------------------------ ** * Send a file fragment to the BITS server. * * Input: Ctx - A pointer to an open connection to the server. * off - Offset, within the file being copied, of the bytes * being sent to the server. * len - Number of bytes to send. * src - Pointer to the block of bytes to be sent. * * Output: A pointer to a stib_parsedMsg structure that will contain the * server's response, if any. * * If the server did not respond, or if some other error occurred, * this function will return NULL and will be set with an * appropriate error class and code. Use to * retrieve the error. * * Errors: stib_errClsSTiB/stib_errBadValue * The value of is either zero or less, or would cause * the total number of bytes written to exceed the file size * given in the parameter to . * stib_errClsSTiB/stib_errMallocFail * Temporary storage could not be allocated from the heap. * Any error that can be returned from . * See for additional error codes. * * Notes: Fragments can only be sent over a session previously * established using . * * ------------------------------------------------------------------------ ** */ { stibContext *ctx = (stibContext *)Ctx; Gbfr *blk; ulong newpos; size_t result; /* Sanity check. */ newpos = off + len; if( (len < 0) || (newpos > ctx->filesize) ) { stib_errSetCC( ctx->err, stib_errClsSTiB, stib_errBadValue ); return( NULL ); } /* Create and send the Fragment message. */ blk = allocGbfr( 1024 ); if( NULL == blk ) { stib_errSetCC( ctx->err, stib_errClsSTiB, stib_errMallocFail ); return( NULL ); } // Fix: Test return values. // Can stib_addHdr2Block() set the hdr to invalid somehow? (void)stib_addHdr2Block( blk, "BITS_POST %s HTTP/1.1", ctx->pathname ); (void)stib_addHdr2Block( blk, "BITS-Packet-Type: Fragment" ); (void)stib_addHdr2Block( blk, "BITS-Session-Id: %s", ctx->sessionid ); if( ctx->agent ) (void)stib_addHdr2Block( blk, "User-Agent: %s", ctx->agent ); (void)stib_addHdr2Block( blk, "Accept: */*" ); (void)stib_addHdr2Block( blk, "Host: %s", ctx->srvname ); (void)stib_addHdr2Block( blk, "Content-Range: bytes %d-%d/%d", off, (newpos - 1), ctx->filesize ); (void)stib_addHdr2Block( blk, "Content-Length: %d", len ); (void)stib_addHdr2Block( blk, "Connection: Keep-Alive" ); (void)stib_addHdr2Block( blk, "" ); (void)stib_sendBlocks( ctx, blk, NULL ); freeGbfr( blk ); /* Send the raw data. */ result = send( ctx->sock, src, len, 0 ); if( result < 0 ) { stib_errSetCC( ctx->err, stib_errClsErrno, errno ); return( NULL ); } /* Receive and parse the response. */ return( stib_recvHeaders( ctx ) ); } /* stib_Fragment */ stib_parsedMsg *stib_CancelSession( stib_Ctx *Ctx ) /* ------------------------------------------------------------------------ ** * Cancel an in-progress BITS upload session. * * Input: Ctx - The connection context of the session to be cancelled. * * Output: A pointer to a stib_parsedMsg structure that will contain the * server's response, if any. * * If the server did not respond, or if some other error occurred, * this function will return NULL and will be set with an * appropriate error class and code. Use to * retrieve the error. * * Errors: stib_errClsSTiB/stib_errMallocFail * Temporary storage could not be allocated from the heap. * Any error that can be returned from . * See for additional error codes. * * Notes: If the server fails to handle the cancel, and returns an error * code in the range 500..599, the client is expected to re-send * the Cancel-Session request unless the server has specified * that the error is BG_E_SESSION_NOT_FOUND (0x8020001F). * See: http://msdn.microsoft.com/en-us/library/aa362710.aspx * * ------------------------------------------------------------------------ ** */ { stibContext *ctx = (stibContext *)Ctx; Gbfr *blk; /* Create and send the Cancel-Session message. */ blk = allocGbfr( 1024 ); if( NULL == blk ) { stib_errSetCC( ctx->err, stib_errClsSTiB, stib_errMallocFail ); return( NULL ); } // Fix: Test return values. // Can stib_addHdr2Block() set the hdr to invalid somehow? (void)stib_addHdr2Block( blk, "BITS_POST %s HTTP/1.1", ctx->pathname ); (void)stib_addHdr2Block( blk, "BITS-Packet-Type: Cancel-Session" ); (void)stib_addHdr2Block( blk, "BITS-Session-Id: %s", ctx->sessionid ); if( ctx->agent ) (void)stib_addHdr2Block( blk, "User-Agent: %s", ctx->agent ); (void)stib_addHdr2Block( blk, "Accept: */*" ); (void)stib_addHdr2Block( blk, "Host: %s", ctx->srvname ); (void)stib_addHdr2Block( blk, "Content-Length: 0" ); (void)stib_addHdr2Block( blk, "Connection: Keep-Alive" ); (void)stib_addHdr2Block( blk, "" ); (void)stib_sendBlocks( ctx, blk, NULL ); freeGbfr( blk ); /* Receive and parse the response. */ return( stib_recvHeaders( ctx ) ); } /* stib_CancelSession */ stib_parsedMsg *stib_CloseSession( stib_Ctx *Ctx ) /* ------------------------------------------------------------------------ ** * Close a completed BITS upload session. * * Input: Ctx - The connection context of the session to be closed. * * Output: A pointer to a stib_parsedMsg structure that will contain the * server's response, if any. * * If the server did not respond, or if some other error occurred, * this function will return NULL and will be set with an * appropriate error class and code. Use to * retrieve the error. * * Errors: stib_errClsSTiB/stib_errMallocFail * Temporary storage could not be allocated from the heap. * Any error that can be returned from . * See for additional error codes. * * ------------------------------------------------------------------------ ** */ { stibContext *ctx = (stibContext *)Ctx; Gbfr *blk; /* Create and send the Close-Session message. */ blk = allocGbfr( 1024 ); if( NULL == blk ) { stib_errSetCC( ctx->err, stib_errClsSTiB, stib_errMallocFail ); return( NULL ); } // Fix: Test return values. // Can stib_addHdr2Block() set the hdr to invalid somehow? (void)stib_addHdr2Block( blk, "BITS_POST %s HTTP/1.1", ctx->pathname ); (void)stib_addHdr2Block( blk, "BITS-Packet-Type: Close-Session" ); (void)stib_addHdr2Block( blk, "BITS-Session-Id: %s", ctx->sessionid ); if( ctx->agent ) (void)stib_addHdr2Block( blk, "User-Agent: %s", ctx->agent ); (void)stib_addHdr2Block( blk, "Accept: */*" ); (void)stib_addHdr2Block( blk, "Host: %s", ctx->srvname ); (void)stib_addHdr2Block( blk, "Content-Length: 0" ); (void)stib_addHdr2Block( blk, "Connection: Keep-Alive" ); (void)stib_addHdr2Block( blk, "" ); (void)stib_sendBlocks( ctx, blk, NULL ); freeGbfr( blk ); /* Receive and parse the response. */ return( stib_recvHeaders( ctx ) ); } /* stib_CloseSession */ stib_parsedMsg *stib_Get( stib_Ctx *Ctx, const char *pathname, const char *agent, const off_t off, const uint len, const bool BCE ) /* ------------------------------------------------------------------------ ** * Download a file using the HTTP[S] GET command. * * Input: Ctx - A pointer to a connection context structure. * pathname - A pointer to a pathname string. This is the * pathname portion of the URL, indicating the * remote resource (file) to be retrieved. * This value is required in the GET start line. * agent - Used to identify the user agent making the request. * This value is optional, and may be NULL. * off - For Range requests, this is the offset within * the source file at which our request range begins. * len - For Range requests, this is the number of bytes we * are requesting. * BCE - If true, request BranchCache peerdist encoding. * * Output: A pointer to a stib_parsedMsg structure that will contain the * server's response, if any. * * If the request was successful (2xx), the message body can be * retrieved using the function. * * If both and are zero, then the "Range:" header * will not be sent and this will be a normal GET command. If * either or are non-zero, the "Range" header will * be used to request a portion of the content. If is * non-zero and is zero, then the request will be for * the range starting at through to the end of the content. * * ------------------------------------------------------------------------ ** */ { stibContext *ctx = (stibContext *)Ctx; Gbfr *blk; /* Create and send the HTTP Get request. */ blk = allocGbfr( 1024 ); if( NULL == blk ) { stib_errSetCC( ctx->err, stib_errClsSTiB, stib_errMallocFail ); return( NULL ); } // Fix: Test return values. // Can stib_addHdr2Block() set the hdr to invalid somehow? (void)stib_addHdr2Block( blk, "GET %s HTTP/1.1", pathname ); (void)stib_addHdr2Block( blk, "Accept: */*" ); if( agent ) (void)stib_addHdr2Block( blk, "User-Agent: %s", agent ); if( off || len ) { if( 0 == len ) (void)stib_addHdr2Block( blk, "Range: bytes=%d-", off ); else (void)stib_addHdr2Block( blk, "Range: bytes=%d-%d", off, (off+len-1) ); } if( BCE ) { (void)stib_addHdr2Block( blk, "Accept-Encoding: peerdist" ); (void)stib_addHdr2Block( blk, "X-P2P-PeerDist: Version=1.0" ); } (void)stib_addHdr2Block( blk, "Host: %s", ctx->srvname ); (void)stib_addHdr2Block( blk, "Content-Length: 0" ); (void)stib_addHdr2Block( blk, "Connection: Keep-Alive" ); (void)stib_addHdr2Block( blk, "" ); (void)stib_sendBlocks( ctx, blk, NULL ); freeGbfr( blk ); /* Receive and parse the response. */ return( stib_recvHeaders( ctx ) ); } /* stib_Get */ char const *stib_Info( void ) /* ------------------------------------------------------------------------ ** * Return a module information string. * * Input: * * Output: A pointer to a constant, static string that provides some * basic information about this module. * * ------------------------------------------------------------------------ ** */ { static char const *info = "stibclilib -- STiB client tookkit.\n" "Copyright (c) 2011 by Christopher R. Hertel\n" "License: GNU LGPLv2.1. See the source.\n" "$Id: stibclilib.c 60 2011-06-28 02:12:29Z crh $\n"; /* The primary purpose of this function is to ensure that the strings are * available in the code without generating errors for unused variables. */ return( info ); } /* stib_Info */ /* ========================================================================== */