test

Fork of mbed-libxively-6eca970 by Xively Official

src/libxively/http_layer_parser.c

Committer:
xively
Date:
2013-06-26
Revision:
0:82702e998d3f

File content as of revision 0:82702e998d3f:

// Copyright (c) 2003-2013, LogMeIn, Inc. All rights reserved.
// This is part of Xively C library, it is under the BSD 3-Clause license.

/**
 * \file    http_layer_parser.c
 * \author  Olgierd Humenczuk
 * \brief   Our simple HTTP parser [see http_layer_parser.h]
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "xively.h"
#include "xi_macros.h"
#include "http_consts.h"
#include "http_layer_parser.h"
#include "xi_debug.h"
#include "xi_err.h"

static const char XI_HTTP_STATUS_PATTERN[] =
    "HTTP/%d.%d %d %" XI_STR(XI_HTTP_STATUS_STRING_SIZE) "[^\r\n]\r\n"; //!< the match pattern

#define SET_HTTP_STATUS_PATTERN(a,b,c,d) XI_HTTP_STATUS_PATTERN, &a, &b, &c, d

static const char XI_HTTP_TOKEN_NAMES[ XI_HTTP_HEADERS_COUNT ][ XI_HTTP_HEADER_NAME_MAX_SIZE ] =
    {
          "date"            // XI_HTTP_HEADER_DATE
        , "content-type"    // XI_HTTP_HEADER_CONTENT_TYPE
        , "content-length"  // XI_HTTP_HEADER_CONTENT_LENGTH
        , "connection"      // XI_HTTP_HEADER_CONNECTION
        , "x-request-id"    // XI_HTTP_HEADER_X_REQUEST_ID
        , "cache-control"   // XI_HTTP_HEADER_CACHE_CONTROL
        , "age"             // XI_HTTP_HEADER_AGE
        , "vary"            // XI_HTTP_HEADER_VARY
        , "unknown"         // XI_HTTP_HEADER_UNKNOWN, //!< !!!! this must be always on the last position
    };

static inline http_header_type_t classify_header( const char* header )
{
    for( unsigned short i = 0; i < XI_HTTP_HEADER_COUNT - 1; ++i )
    {
        if( strcasecmp( header, XI_HTTP_TOKEN_NAMES[ i ] ) == 0 )
            return ( http_header_type_t ) i;
    }

    return XI_HTTP_HEADER_UNKNOWN;
}

const char* parse_http_status( http_response_t* response, const char* content )
{
    // variables
    int c = 0;

    // find the first occurrence of CRLF
    const char* header_end_ptr = strstr( content, XI_HTTP_CRLF );

    // check continuation condition
    XI_CHECK_ZERO( header_end_ptr, XI_HTTP_STATUS_PARSE_ERROR );

    // update the pointer
    header_end_ptr += 2;

    // parse
    c = sscanf( content
        , SET_HTTP_STATUS_PATTERN(
              response->http_version1
            , response->http_version2
            , response->http_status
            , response->http_status_string ) );

    // check if all arguments found
    XI_CHECK_CND( c != 4, XI_HTTP_STATUS_PARSE_ERROR );

    // return updated ptr
    return header_end_ptr;

err_handling:
    return 0;
}

const char* parse_http_header( http_response_t* response
    , const char* content )
{
    // find the first occurrence of CRLF
    const char* header_end_ptr      = strstr( content, XI_HTTP_CRLF );
    const char* header_name_end_ptr = strstr( content, ":" );

    // check continuation condition
    XI_CHECK_ZERO( header_end_ptr, XI_HTTP_HEADER_PARSE_ERROR );
    XI_CHECK_ZERO( header_name_end_ptr, XI_HTTP_HEADER_PARSE_ERROR );
    XI_CHECK_CND( header_name_end_ptr > header_end_ptr, XI_HTTP_HEADER_PARSE_ERROR );

    {
        int size = sizeof( response->http_headers[ response->http_headers_size ].name );

        XI_CHECK_CND( header_name_end_ptr - content > size - 1, XI_HTTP_HEADER_PARSE_ERROR );

        memcpy( response->http_headers[ response->http_headers_size ].name
            , content, XI_MIN( size - 1, header_name_end_ptr - content ) );

        // set the guard
        XI_GUARD_EOS( response->http_headers[ response->http_headers_size ].name, size );
    }

    // update the pointer
    header_name_end_ptr += 2;

    {
        int size = sizeof( response->http_headers[ response->http_headers_size ].value );

        XI_CHECK_CND( header_end_ptr - header_name_end_ptr > size - 1, XI_HTTP_HEADER_PARSE_ERROR );

        memcpy( response->http_headers[ response->http_headers_size ].value
            , header_name_end_ptr, XI_MIN( size - 1, header_end_ptr - header_name_end_ptr ) );

        // set the guard
        XI_GUARD_EOS( response->http_headers[ response->http_headers_size ].value, size );
    }

    // @TODO change the complexity of the classify header
    // implementation now it's O( n*m ) it can be done in O( n )
    // using some sort of simple DFA
    // I'm working on a very simple Python DFA generator for C
    // but still this is a w.i.p. at the moment

    // parse the header name
    {
        http_header_type_t ht = classify_header(
            response->http_headers[ response->http_headers_size ].name );

        // accept headers that differs from unknown
        if( ht != XI_HTTP_HEADER_UNKNOWN )
        {
            response->http_headers_checklist[ ht ] =
                &response->http_headers[ response->http_headers_size ];
        }

        // set header type
        response->http_headers[ response->http_headers_size ].header_type = ht;

        // increment headers size and check if it's okay
        response->http_headers_size += 1;

        XI_CHECK_CND( response->http_headers_size >= XI_HTTP_MAX_HEADERS
            , XI_HTTP_HEADER_PARSE_ERROR );

        // update the pointer
        header_end_ptr += sizeof( XI_HTTP_CRLF ) - 1;
    }

    return header_end_ptr;

err_handling:
    return 0;
}

http_response_t* parse_http( http_response_t* response, const char* content )
{
    memset( response, 0, sizeof( http_response_t ) );

    // remember where is the end of header section
    const char* ptr_to_headers_end = strstr( content, XI_HTTP_CRLFX2 );

    // check the continuation condition
    XI_CHECK_ZERO( ptr_to_headers_end, XI_HTTP_PARSE_ERROR );

    // update the pointer
    ptr_to_headers_end += sizeof( XI_HTTP_CRLF ) - 1;

    const char* payload_begin = ptr_to_headers_end + sizeof( XI_HTTP_CRLF ) - 1;

    XI_CHECK_ZERO( payload_begin, XI_HTTP_PARSE_ERROR );

    // parse status
    const char* ptr = parse_http_status( response, content );

    // check the continuation condition
    XI_CHECK_ZERO( ptr, XI_HTTP_PARSE_ERROR );

    // read the headers
    while( ( ptr = parse_http_header( response, ptr ) ) != '\0'
        && ptr != ptr_to_headers_end );

    // if there was an error, forward it
    xi_err_t e = xi_get_last_error();
    XI_CHECK_CND( e != XI_NO_ERR, e );

    // just copy the content
    strncpy( response->http_content, payload_begin, sizeof( response->http_content ) );

    return response;

err_handling:
    return 0;
}