/* ==================================================================== * The Kannel Software License, Version 1.0 * * Copyright (c) 2001-2005 Kannel Group * Copyright (c) 1998-2001 WapIT Ltd. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Kannel Group (http://www.kannel.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Kannel" and "Kannel Group" must not be used to * endorse or promote products derived from this software without * prior written permission. For written permission, please * contact org@kannel.org. * * 5. Products derived from this software may not be called "Kannel", * nor may "Kannel" appear in their name, without prior written * permission of the Kannel Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Kannel Group. For more information on * the Kannel Group, please see . * * Portions of this software are based upon software originally written at * WapIT Ltd., Helsinki, Finland for the Kannel project. */ /* * wsp_headers.c - Implement WSP PDU headers * * References: * WSP specification version 1.1 * RFC 2068, Hypertext Transfer Protocol HTTP/1.1 * RFC 2616, Hypertext Transfer Protocol HTTP/1.1 * * For push headers, WSP specification, June 2000 conformance release * * This file has two parts. The first part decodes the request's headers * from WSP to HTTP. The second part encodes the response's headers from * HTTP to WSP. * * Note that push header encoding and decoding are divided two parts: * first decoding and encoding numeric values and then packing these values * into WSP format and unpacking them from WSP format. This module contains * only packing and unpacking parts. * * Some functions are declared non-static to provide them for external use, * ie. the MMS encapsulation encoding and decoding routines implemented in * other files. * * Richard Braakman * Stipe Tolj */ #include #include #include #include "gwlib/gwlib.h" #include "wsp.h" #include "wsp_headers.h" #include "wsp_strings.h" /* * get field value and return its type as predefined data types * There are three kinds of field encodings: * WSP_FIELD_VALUE_NUL_STRING: 0-terminated string * WSP_FIELD_VALUE_ENCODED: short integer, range 0-127 * WSP_FIELD_VALUE_DATA: octet string defined by length * The function will return one of those values, and modify the parse context * to make it easy to get the field data. * WSP_FIELD_VALUE_NUL_STRING: Leave parsing position at start of string * WSP_FIELD_VALUE_ENCODED: Put value in *well_known_value, leave * parsing position after field value. * WSP_FIELD_VALUE_DATA: Leave parsing position at start of data, and set * a parse limit at the end of data. */ int wsp_field_value(ParseContext *context, int *well_known_value) { int val; unsigned long len; val = parse_get_char(context); if (val > 0 && val < 31) { *well_known_value = -1; parse_limit(context, val); return WSP_FIELD_VALUE_DATA; } else if (val == 31) { *well_known_value = -1; len = parse_get_uintvar(context); parse_limit(context, len); return WSP_FIELD_VALUE_DATA; } else if (val > 127) { *well_known_value = val - 128; return WSP_FIELD_VALUE_ENCODED; } else if (val == WSP_QUOTE) { /* 127 */ *well_known_value = -1; /* We already consumed the Quote */ return WSP_FIELD_VALUE_NUL_STRING; } else { /* implicite val == 0 */ *well_known_value = -1; /* Un-parse the character we just read */ parse_skip(context, -1); return WSP_FIELD_VALUE_NUL_STRING; } } /* Skip over a field_value as defined above. */ void wsp_skip_field_value(ParseContext *context) { int val; int ret; ret = wsp_field_value(context, &val); if (ret == WSP_FIELD_VALUE_DATA) { parse_skip_to_limit(context); parse_pop_limit(context); } } /* Multi-octet-integer is defined in 8.4.2.1 */ static long unpack_multi_octet_integer(ParseContext *context, long len) { long val = 0; if (len > (long) sizeof(val) || len < 0) return -1; while (len > 0) { val = val * 256 + parse_get_char(context); len--; } if (parse_error(context)) return -1; return val; } /* This function is similar to field_value, but it is used at various * places in the grammar where we expect either an Integer-value * or some kind of NUL-terminated text. * * Return values are just like field_value except that WSP_FIELD_VALUE_DATA * will not be returned. * * As a special case, we parse a 0-length Long-integer as an * WSP_FIELD_VALUE_NONE, so that we can distinguish between No-value * and an Integer-value of 0. (A real integer 0 would be encoded as * a Short-integer; the definition of Long-integer seems to allow * 0-length integers, but the definition of Multi-octet-integer does * not, so this is an unclear area of the specification.) */ int wsp_secondary_field_value(ParseContext *context, long *result) { int val; long length; val = parse_get_char(context); if (val == 0) { *result = 0; return WSP_FIELD_VALUE_NONE; } else if (val > 0 && val < 31) { *result = unpack_multi_octet_integer(context, val); return WSP_FIELD_VALUE_ENCODED; } else if (val == 31) { length = parse_get_uintvar(context); *result = unpack_multi_octet_integer(context, length); return WSP_FIELD_VALUE_ENCODED; } else if (val > 127) { *result = val - 128; return WSP_FIELD_VALUE_ENCODED; } else if (val == WSP_QUOTE) { /* 127 */ *result = -1; return WSP_FIELD_VALUE_NUL_STRING; } else { *result = -1; /* Un-parse the character we just read */ parse_skip(context, -1); return WSP_FIELD_VALUE_NUL_STRING; } } /* Integer-value is defined in 8.4.2.3 */ Octstr *wsp_unpack_integer_value(ParseContext *context) { Octstr *decoded; unsigned long value; int val; val = parse_get_char(context); if (val < 31) { value = unpack_multi_octet_integer(context, val); } else if (val > 127) { value = val - 128; } else { warning(0, "WSP headers: bad integer-value."); return NULL; } decoded = octstr_create(""); octstr_append_decimal(decoded, value); return decoded; } /* Q-value is defined in 8.4.2.3 */ static Octstr *convert_q_value(int q) { Octstr *result = NULL; /* When quality factor 0 and quality factors with one or two * decimal digits are encoded, they shall be multiplied by 100 * and incremented by one, so that they encode as a one-octet * value in range 1-100. */ if (q >= 1 && q <= 100) { q = q - 1; result = octstr_create("0."); octstr_append_char(result, (q / 10) + '0'); if (q % 10 > 0) octstr_append_char(result, (q % 10) + '0'); return result; } /* Three decimal quality factors shall be multiplied with 1000 * and incremented by 100. */ if (q > 100 && q <= 1000) { q = q - 100; result = octstr_create("0."); octstr_append_char(result, (q / 100) + '0'); if (q % 100 > 0) octstr_append_char(result, (q / 10 % 10) + '0'); if (q % 10 > 0) octstr_append_char(result, (q % 10) + '0'); return result; } return NULL; } /* Q-value is defined in 8.4.2.3 */ static Octstr *unpack_q_value(ParseContext *context) { int c, c2; c = parse_get_char(context); if (c < 0) return NULL; if (c & 0x80) { c2 = parse_get_char(context); if (c2 < 0 || (c2 & 0x80)) return NULL; c = ((c & 0x7f) << 8) + c2; } return convert_q_value(c); } /* Version-value is defined in 8.4.2.3. Encoding-Version uses coding * defined in this chapter, see 8.4.2.70. */ Octstr *wsp_unpack_version_value(long value) { Octstr *result; int major, minor; major = ((value >> 4) & 0x7); minor = (value & 0xf); result = octstr_create(""); octstr_append_char(result, major + '0'); if (minor != 15) { octstr_append_char(result, '.'); octstr_append_decimal(result, minor); } return result; } static Octstr *unpack_encoding_version(ParseContext *context) { int ch; ch = parse_get_char(context); if (ch < 128) { warning(0, "WSP: bad Encoding-Version value"); return NULL; } return wsp_unpack_version_value(((long) ch) - 128); } /* Called with the parse limit set to the end of the parameter data, * and decoded containing the unpacked header line so far. * Parameter is defined in 8.4.2.4. */ static int unpack_parameter(ParseContext *context, Octstr *decoded) { Octstr *parm = NULL; Octstr *value = NULL; int ret; long type; long val; ret = wsp_secondary_field_value(context, &type); if (parse_error(context) || ret == WSP_FIELD_VALUE_NONE) { warning(0, "bad parameter"); goto error; } if (ret == WSP_FIELD_VALUE_ENCODED) { /* Typed-parameter */ parm = wsp_parameter_to_string(type); if (!parm) warning(0, "Unknown parameter %02lx.", type); } else if (ret == WSP_FIELD_VALUE_NUL_STRING) { /* Untyped-parameter */ parm = parse_get_nul_string(context); if (!parm) warning(0, "Format error in parameter."); type = -1; /* We treat Untyped-value as a special type. Its format * Integer-value | Text-value is pretty similar to most * typed formats. */ } else { panic(0, "Unknown secondary field value type %d.", ret); } if (type == 0x00) /* q */ value = unpack_q_value(context); else { ret = wsp_secondary_field_value(context, &val); if (parse_error(context)) { warning(0, "bad parameter value"); goto error; } if (ret == WSP_FIELD_VALUE_ENCODED) { switch (type) { case -1: /* untyped: Integer-value */ case 3: /* type: Integer-value */ case 8: /* padding: Short-integer */ value = octstr_create(""); octstr_append_decimal(value, val); break; case 0: /* q, already handled above */ gw_assert(0); break; case 1: /* charset: Well-known-charset */ value = wsp_charset_to_string(val); if (!value) warning(0, "Unknown charset %04lx.", val); break; case 2: /* level: Version-value */ value = wsp_unpack_version_value(val); break; case 5: /* name: Text-string */ case 6: /* filename: Text-string */ warning(0, "Text-string parameter with integer encoding"); break; case 7: /* differences: Field-name */ value = wsp_header_to_string(val); if (!value) warning(0, "Unknown differences header %02lx.", val); break; default: warning(0, "Unknown parameter encoding %02lx.", type); break; } } else if (ret == WSP_FIELD_VALUE_NONE) { value = octstr_create(""); } else { gw_assert(ret == WSP_FIELD_VALUE_NUL_STRING); /* Text-value = No-value | Token-text | Quoted-string */ value = parse_get_nul_string(context); if (!value) warning(0, "Format error in parameter value."); else { if (octstr_get_char(value, 0) == '"') { /* Quoted-string */ octstr_append_char(value, '"'); } else { /* DAVI! */ octstr_insert(value, octstr_imm("\""), 0); octstr_append_char(value, '"'); } } } } if (!parm || !value) { warning(0, "Skipping parameters"); goto error; } octstr_append(decoded, octstr_imm("; ")); octstr_append(decoded, parm); if (octstr_len(value) > 0) { octstr_append_char(decoded, '='); octstr_append(decoded, value); } octstr_destroy(parm); octstr_destroy(value); return 0; error: parse_skip_to_limit(context); octstr_destroy(parm); octstr_destroy(value); parse_set_error(context); return -1; } void wsp_unpack_all_parameters(ParseContext *context, Octstr *decoded) { int ret = 0; while (ret >= 0 && !parse_error(context) && parse_octets_left(context) > 0) { ret = unpack_parameter(context, decoded); } } /* Unpack parameters in the format used by credentials and challenge, * which differs from the format used by all other HTTP headers. */ static void unpack_broken_parameters(ParseContext *context, Octstr *decoded) { int ret = 0; int first = 1; long pos; while (ret >= 0 && !parse_error(context) && parse_octets_left(context) > 0) { pos = octstr_len(decoded); ret = unpack_parameter(context, decoded); if (ret >= 0) { if (first) { /* Zap ';' */ octstr_delete(decoded, pos, 1); first = 0; } else { /* Replace ';' with ',' */ octstr_set_char(decoded, pos, first ? ' ' : ','); } } } } static void unpack_optional_q_value(ParseContext *context, Octstr *decoded) { if (parse_octets_left(context) > 0) { Octstr *qval = unpack_q_value(context); if (qval) { octstr_append(decoded, octstr_imm("; q=")); octstr_append(decoded, qval); octstr_destroy(qval); } else warning(0, "Bad q-value"); } } /* Date-value is defined in 8.4.2.3. */ Octstr *wsp_unpack_date_value(ParseContext *context) { unsigned long timeval; int length; length = parse_get_char(context); if (length > 30) { warning(0, "WSP headers: bad date-value."); return NULL; } timeval = unpack_multi_octet_integer(context, length); if (timeval < 0) { warning(0, "WSP headers: cannot unpack date-value."); return NULL; } return date_format_http(timeval); } /* Accept-general-form is defined in 8.4.2.7 */ Octstr *wsp_unpack_accept_general_form(ParseContext *context) { Octstr *decoded = NULL; int ret; long val; /* The definition for Accept-general-form looks quite complicated, * but the "Q-token Q-value" part fits the normal expansion of * Parameter, so it simplifies to: * Value-length Media-range *(Parameter) * and we've already parsed Value-length. */ /* We use this function to parse content-general-form too, * because its definition of Media-type is identical to Media-range. */ ret = wsp_secondary_field_value(context, &val); if (parse_error(context) || ret == WSP_FIELD_VALUE_NONE) { warning(0, "bad media-range or media-type"); return NULL; } if (ret == WSP_FIELD_VALUE_ENCODED) { decoded = wsp_content_type_to_string(val); if (!decoded) { warning(0, "Unknown content type 0x%02lx.", val); return NULL; } } else if (ret == WSP_FIELD_VALUE_NUL_STRING) { decoded = parse_get_nul_string(context); if (!decoded) { warning(0, "Format error in content type"); return NULL; } } else { panic(0, "Unknown secondary field value type %d.", ret); } wsp_unpack_all_parameters(context, decoded); return decoded; } /* Accept-charset-general-form is defined in 8.4.2.8 */ Octstr *wsp_unpack_accept_charset_general_form(ParseContext *context) { Octstr *decoded = NULL; int ret; long val; ret = wsp_secondary_field_value(context, &val); if (parse_error(context) || ret == WSP_FIELD_VALUE_NONE) { warning(0, "Bad accept-charset-general-form"); return NULL; } if (ret == WSP_FIELD_VALUE_ENCODED) { decoded = wsp_charset_to_string(val); if (!decoded) { warning(0, "Unknown character set %04lx.", val); return NULL; } } else if (ret == WSP_FIELD_VALUE_NUL_STRING) { decoded = parse_get_nul_string(context); if (!decoded) { warning(0, "Format error in accept-charset"); return NULL; } } else { panic(0, "Unknown secondary field value type %d.", ret); } unpack_optional_q_value(context, decoded); return decoded; } /* Accept-language-general-form is defined in 8.4.2.10 */ static Octstr *unpack_accept_language_general_form(ParseContext *context) { Octstr *decoded = NULL; int ret; long val; ret = wsp_secondary_field_value(context, &val); if (parse_error(context) || ret == WSP_FIELD_VALUE_NONE) { warning(0, "Bad accept-language-general-form"); return NULL; } if (ret == WSP_FIELD_VALUE_ENCODED) { /* Any-language is handled by a special entry in the * language table. */ decoded = wsp_language_to_string(val); if (!decoded) { warning(0, "Unknown language %02lx.", val); return NULL; } } else if (ret == WSP_FIELD_VALUE_NUL_STRING) { decoded = parse_get_nul_string(context); if (!decoded) { warning(0, "Format error in accept-language"); return NULL; } } else { panic(0, "Unknown secondary field value type %d.", ret); } unpack_optional_q_value(context, decoded); return decoded; } /* Credentials is defined in 8.4.2.5 */ static Octstr *unpack_credentials(ParseContext *context) { Octstr *decoded = NULL; int val; val = parse_peek_char(context); if (val == BASIC_AUTHENTICATION) { Octstr *userid, *password; parse_skip(context, 1); userid = parse_get_nul_string(context); password = parse_get_nul_string(context); if (parse_error(context)) { octstr_destroy(userid); octstr_destroy(password); } else { /* Create the user-pass cookie */ decoded = octstr_duplicate(userid); octstr_append_char(decoded, ':'); octstr_append(decoded, password); /* XXX Deal with cookies that overflow the 76-per-line * limit of base64. Either go through and zap all * CR LF sequences, or give the conversion function * a flag or something to leave them out. */ octstr_binary_to_base64(decoded); /* Zap the CR LF at the end */ octstr_delete(decoded, octstr_len(decoded) - 2, 2); octstr_insert_data(decoded, 0, "Basic ", 6); octstr_destroy(userid); octstr_destroy(password); } } else if (val >= 32 && val < 128) { /* Generic authentication scheme */ decoded = parse_get_nul_string(context); if (decoded) unpack_broken_parameters(context, decoded); } if (!decoded) warning(0, "Cannot parse credentials."); return decoded; } /* Credentials is defined in 8.4.2.5 * but as Proxy-Authentication is to be used by kannel, * a simplier to parse version is used here */ static Octstr *proxy_unpack_credentials(ParseContext *context) { Octstr *decoded = NULL; int val; val = parse_peek_char(context); if (val == BASIC_AUTHENTICATION) { Octstr *userid, *password; parse_skip(context, 1); userid = parse_get_nul_string(context); password = parse_get_nul_string(context); if (parse_error(context)) { octstr_destroy(userid); octstr_destroy(password); } else { /* Create the user-pass cookie */ decoded = octstr_duplicate(userid); octstr_append_char(decoded, ':'); octstr_append(decoded, password); octstr_destroy(userid); octstr_destroy(password); } } else if (val >= 32 && val < 128) { /* Generic authentication scheme */ decoded = parse_get_nul_string(context); if (decoded) unpack_broken_parameters(context, decoded); } if (!decoded) warning(0, "Cannot parse credentials."); return decoded; } /* Challenge is defined in 8.4.2.5 */ static Octstr *unpack_challenge(ParseContext *context) { Octstr *decoded = NULL; Octstr *realm_value = NULL; int val; val = parse_peek_char(context); if (val == BASIC_AUTHENTICATION) { parse_skip(context, 1); realm_value = parse_get_nul_string(context); if (realm_value) { decoded = octstr_create("Basic realm=\""); octstr_append(decoded, realm_value); octstr_append_char(decoded, '"'); } } else if (val >= 32 && val < 128) { /* Generic authentication scheme */ decoded = parse_get_nul_string(context); realm_value = parse_get_nul_string(context); if (decoded && realm_value) { octstr_append(decoded, octstr_imm(" realm=\"")); octstr_append(decoded, realm_value); octstr_append_char(decoded, '"'); if (parse_octets_left(context) > 0) { /* Prepare for following parameter list */ octstr_append_char(decoded, ','); } unpack_broken_parameters(context, decoded); } } if (!decoded) warning(0, "Cannot parse challenge."); octstr_destroy(realm_value); return decoded; } /* Content-range is defined in 8.4.2.23 */ static Octstr *unpack_content_range(ParseContext *context) { /* We'd have to figure out how to access the content range * length (i.e. user_data size) from here to parse this, * and I don't see why the _client_ would send this in any case. */ warning(0, "Decoding of content-range not supported"); return NULL; /* Octstr *decoded = NULL; unsigned long first_byte_pos, entity_length; unsigned long last_byte_pos; first_byte_pos = parse_get_uintvar(context); entity_length = parse_get_uintvar(context); if (parse_error(context)) { warning(0, "Cannot parse content-range header"); return NULL; } decoded = octstr_create("bytes "); octstr_append_decimal(decoded, first_byte_pos); octstr_append_char(decoded, '-'); octstr_append_decimal(decoded, last_byte_pos); octstr_append_char(decoded, '/'); octstr_append_decimal(decoded, entity_length); return decoded; */ } /* Field-name is defined in 8.4.2.6 */ static Octstr *unpack_field_name(ParseContext *context) { Octstr *decoded = NULL; int ret; int val; ret = wsp_field_value(context, &val); if (parse_error(context) || ret == WSP_FIELD_VALUE_DATA) { warning(0, "Bad field-name encoding"); return NULL; } if (ret == WSP_FIELD_VALUE_ENCODED) { decoded = wsp_header_to_string(val); if (!decoded) { warning(0, "Unknown field-name 0x%02x.", val); return NULL; } } else if (ret == WSP_FIELD_VALUE_NUL_STRING) { decoded = parse_get_nul_string(context); if (!decoded) { warning(0, "Bad field-name encoding"); return NULL; } } else { panic(0, "Unknown field value type %d.", ret); } return decoded; } /* Cache-directive is defined in 8.4.2.15 */ static Octstr *unpack_cache_directive(ParseContext *context) { Octstr *decoded = NULL; int ret; int val; ret = wsp_field_value(context, &val); if (parse_error(context) || ret == WSP_FIELD_VALUE_DATA) { warning(0, "Bad cache-directive"); goto error; } if (ret == WSP_FIELD_VALUE_ENCODED) { decoded = wsp_cache_control_to_string(val); if (!decoded) { warning(0, "Bad cache-directive 0x%02x.", val); goto error; } octstr_append_char(decoded, '='); switch (val) { case WSP_CACHE_CONTROL_NO_CACHE: case WSP_CACHE_CONTROL_PRIVATE: if (parse_octets_left(context) == 0) { warning(0, "Too short cache-directive"); goto error; } octstr_append_char(decoded, '"'); do { Octstr *fieldname = unpack_field_name(context); if (!fieldname) { warning(0, "Bad field name in cache directive"); goto error; } octstr_append(decoded, fieldname); octstr_destroy(fieldname); if (parse_octets_left(context) > 0) { octstr_append_char(decoded, ','); octstr_append_char(decoded, ' '); } } while (parse_octets_left(context) > 0 && !parse_error(context)); octstr_append_char(decoded, '"'); break; case WSP_CACHE_CONTROL_MAX_AGE: case WSP_CACHE_CONTROL_MAX_STALE: case WSP_CACHE_CONTROL_MIN_FRESH: { Octstr *seconds; seconds = wsp_unpack_integer_value(context); if (!seconds) { warning(0, "Bad integer value in cache directive"); goto error; } octstr_append(decoded, seconds); octstr_destroy(seconds); } break; default: warning(0, "Unexpected value 0x%02x in cache directive.", val); break; } } else if (ret == WSP_FIELD_VALUE_NUL_STRING) { /* XXX: WSP grammar seems wrong here. It works out * to Token-text followed by Parameter. But the * grammar in RFC2616 works out to a key = value * pair, i.e. only a Parameter. */ decoded = parse_get_nul_string(context); if (!decoded) { warning(0, "Format error in cache-control."); return NULL; } /* Yes, the grammar allows only one */ unpack_parameter(context, decoded); } else { panic(0, "Unknown field value type %d.", ret); } return decoded; error: octstr_destroy(decoded); return NULL; } /* Retry-after is defined in 8.4.2.44 */ static Octstr *unpack_retry_after(ParseContext *context) { int selector; selector = parse_get_char(context); if (selector == ABSOLUTE_TIME) { return wsp_unpack_date_value(context); } else if (selector == RELATIVE_TIME) { return wsp_unpack_integer_value(context); } else { warning(0, "Cannot parse retry-after value."); return NULL; } } /* Disposition is defined in 8.4.2.53 */ static Octstr *unpack_disposition(ParseContext *context) { Octstr *decoded = NULL; int selector; selector = parse_get_char(context) - 128; decoded = wsp_disposition_to_string(selector); if (!decoded) { warning(0, "Cannot parse content-disposition value."); return NULL; } wsp_unpack_all_parameters(context, decoded); return decoded; } /* Range-value is defined in 8.4.2.42 */ static Octstr *unpack_range_value(ParseContext *context) { Octstr *decoded = NULL; int selector; unsigned long first_byte_pos, last_byte_pos, suffix_length; selector = parse_get_char(context); if (selector == BYTE_RANGE) { first_byte_pos = parse_get_uintvar(context); if (parse_error(context)) goto error; decoded = octstr_create("bytes = "); octstr_append_decimal(decoded, first_byte_pos); octstr_append_char(decoded, '-'); last_byte_pos = parse_get_uintvar(context); if (parse_error(context)) { /* last_byte_pos is optional */ parse_clear_error(context); } else { octstr_append_decimal(decoded, last_byte_pos); } } else if (selector == SUFFIX_BYTE_RANGE) { suffix_length = parse_get_uintvar(context); if (parse_error(context)) goto error; decoded = octstr_create("bytes = -"); octstr_append_decimal(decoded, suffix_length); } else { goto error; } return decoded; error: warning(0, "Bad format for range-value."); octstr_destroy(decoded); return NULL; } /* Warning-value is defined in 8.4.2.51 */ static Octstr *unpack_warning_value(ParseContext *context) { Octstr *decoded = NULL; Octstr *warn_code = NULL; Octstr *warn_agent = NULL; Octstr *warn_text = NULL; unsigned char quote = '"'; warn_code = wsp_unpack_integer_value(context); warn_agent = parse_get_nul_string(context); if (warn_agent && octstr_get_char(warn_agent, 0) == WSP_QUOTE) octstr_delete(warn_agent, 0, 1); warn_text = parse_get_nul_string(context); if (warn_text && octstr_get_char(warn_text, 0) == WSP_QUOTE) octstr_delete(warn_text, 0, 1); if (octstr_get_char(warn_text, 0) != quote) octstr_insert_data(warn_text, 0, "e, 1); if (octstr_get_char(warn_text, octstr_len(warn_text) - 1) != quote) octstr_append_char(warn_text, quote); if (parse_error(context) || !warn_agent || !warn_text) goto error; decoded = octstr_create(""); octstr_append(decoded, warn_code); octstr_append_char(decoded, ' '); octstr_append(decoded, warn_agent); octstr_append_char(decoded, ' '); octstr_append(decoded, warn_text); octstr_destroy(warn_agent); octstr_destroy(warn_code); octstr_destroy(warn_text); return decoded; error: warning(0, "Bad format for warning-value."); octstr_destroy(warn_agent); octstr_destroy(warn_code); octstr_destroy(warn_text); octstr_destroy(decoded); return NULL; } void wsp_unpack_well_known_field(List *unpacked, int field_type, ParseContext *context) { int val, ret; unsigned char *headername = NULL; unsigned char *ch = NULL; Octstr *decoded = NULL; ret = wsp_field_value(context, &val); if (parse_error(context)) { warning(0, "Faulty header, skipping remaining headers."); parse_skip_to_limit(context); return; } headername = wsp_header_to_cstr(field_type); /* headername can still be NULL. This is checked after parsing * the field value. We want to parse the value before exiting, * so that we are ready for the next header. */ /* The following code must set "ch" or "decoded" to a non-NULL * value if the header is valid. */ if (ret == WSP_FIELD_VALUE_NUL_STRING) { /* We allow any header to have a text value, even if that * is not defined in the grammar. Be generous in what * you accept, etc. */ /* This covers Text-string, Token-Text, and Uri-value rules */ decoded = parse_get_nul_string(context); } else if (ret == WSP_FIELD_VALUE_ENCODED) { switch (field_type) { case WSP_HEADER_ACCEPT: case WSP_HEADER_CONTENT_TYPE: ch = wsp_content_type_to_cstr(val); if (!ch) warning(0, "Unknown content type 0x%02x.", val); break; case WSP_HEADER_ACCEPT_CHARSET: ch = wsp_charset_to_cstr(val); if (!ch) warning(0, "Unknown charset 0x%02x.", val); break; case WSP_HEADER_ACCEPT_ENCODING: case WSP_HEADER_CONTENT_ENCODING: ch = wsp_encoding_to_cstr(val); if (!ch) warning(0, "Unknown encoding 0x%02x.", val); break; case WSP_HEADER_ACCEPT_LANGUAGE: case WSP_HEADER_CONTENT_LANGUAGE: ch = wsp_language_to_cstr(val); if (!ch) warning(0, "Unknown language 0x%02x.", val); break; case WSP_HEADER_ACCEPT_RANGES: ch = wsp_ranges_to_cstr(val); if (!ch) warning(0, "Unknown ranges value 0x%02x.", val); break; case WSP_HEADER_AGE: case WSP_HEADER_CONTENT_LENGTH: case WSP_HEADER_MAX_FORWARDS: /* Short-integer version of Integer-value */ decoded = octstr_create(""); octstr_append_decimal(decoded, val); break; case WSP_HEADER_ALLOW: case WSP_HEADER_PUBLIC: ch = wsp_method_to_cstr(val); if (!ch) { /* FIXME Support extended methods */ warning(0, "Unknown method 0x%02x.", val); } break; case WSP_HEADER_CACHE_CONTROL: case WSP_HEADER_CACHE_CONTROL_V13: case WSP_HEADER_CACHE_CONTROL_V14: ch = wsp_cache_control_to_cstr(val); if (!ch) warning(0, "Unknown cache-control value 0x%02x.", val); break; case WSP_HEADER_CONNECTION: ch = wsp_connection_to_cstr(val); if (!ch) warning(0, "Unknown connection value 0x%02x.", val); break; case WSP_HEADER_PRAGMA: if (val == 0) ch = "no-cache"; else warning(0, "Unknown pragma value 0x%02x.", val); break; case WSP_HEADER_TRANSFER_ENCODING: ch = wsp_transfer_encoding_to_cstr(val); if (!ch) warning(0, "Unknown transfer encoding value 0x%02x.", val); break; case WSP_HEADER_VARY: ch = wsp_header_to_cstr(val); if (!ch) warning(0, "Unknown Vary field name 0x%02x.", val); break; case WSP_HEADER_WARNING: decoded = octstr_create(""); octstr_append_decimal(decoded, val); break; case WSP_HEADER_BEARER_INDICATION: ch = wsp_bearer_indication_to_cstr(val); if (!ch) warning(0, "Unknown Bearer-Indication field name 0x%02x.", val); break; case WSP_HEADER_ACCEPT_APPLICATION: ch = wsp_application_id_to_cstr(val); if (!ch) warning(0, "Unknown Accept-Application field name 0x%02x.", val); break; default: if (headername) { warning(0, "Did not expect short-integer with " "'%s' header, skipping.", headername); } break; } } else if (ret == WSP_FIELD_VALUE_DATA) { switch (field_type) { case WSP_HEADER_ACCEPT: case WSP_HEADER_CONTENT_TYPE: /* Content-general-form and Accept-general-form * are defined separately in WSP, but their * definitions are equivalent. */ decoded = wsp_unpack_accept_general_form(context); break; case WSP_HEADER_ACCEPT_CHARSET: decoded = wsp_unpack_accept_charset_general_form(context); break; case WSP_HEADER_ACCEPT_LANGUAGE: decoded = unpack_accept_language_general_form(context); break; case WSP_HEADER_AGE: case WSP_HEADER_CONTENT_LENGTH: case WSP_HEADER_MAX_FORWARDS: case WSP_HEADER_BEARER_INDICATION: case WSP_HEADER_ACCEPT_APPLICATION: /* Long-integer version of Integer-value */ { long l = unpack_multi_octet_integer(context, parse_octets_left(context)); decoded = octstr_create(""); octstr_append_decimal(decoded, l); } break; case WSP_HEADER_AUTHORIZATION: decoded = unpack_credentials(context); break; case WSP_HEADER_PROXY_AUTHORIZATION: decoded = proxy_unpack_credentials(context); break; case WSP_HEADER_CACHE_CONTROL: decoded = unpack_cache_directive(context); break; case WSP_HEADER_CONTENT_MD5: decoded = parse_get_octets(context, parse_octets_left(context)); octstr_binary_to_base64(decoded); /* Zap the CR LF sequence at the end */ octstr_delete(decoded, octstr_len(decoded) - 2, 2); break; case WSP_HEADER_CONTENT_RANGE: decoded = unpack_content_range(context); break; case WSP_HEADER_DATE: case WSP_HEADER_EXPIRES: case WSP_HEADER_IF_MODIFIED_SINCE: case WSP_HEADER_IF_RANGE: case WSP_HEADER_IF_UNMODIFIED_SINCE: case WSP_HEADER_LAST_MODIFIED: /* Back up to get the length byte again */ parse_skip(context, -1); decoded = wsp_unpack_date_value(context); break; case WSP_HEADER_PRAGMA: /* The value is a bare Parameter, without a preceding * header body. unpack_parameter wasn't really * designed for this. We work around it here. */ decoded = octstr_create(""); if (unpack_parameter(context, decoded) < 0) { octstr_destroy(decoded); decoded = NULL; } else { /* Remove the leading "; " */ octstr_delete(decoded, 0, 2); } break; case WSP_HEADER_PROXY_AUTHENTICATE: case WSP_HEADER_WWW_AUTHENTICATE: decoded = unpack_challenge(context); break; case WSP_HEADER_RANGE: decoded = unpack_range_value(context); break; case WSP_HEADER_RETRY_AFTER: decoded = unpack_retry_after(context); break; case WSP_HEADER_WARNING: decoded = unpack_warning_value(context); break; case WSP_HEADER_CONTENT_DISPOSITION: decoded = unpack_disposition(context); break; case WSP_HEADER_ENCODING_VERSION: decoded = unpack_encoding_version(context); break; default: if (headername) { warning(0, "Did not expect value-length with " "'%s' header, skipping.", headername); } break; } if (headername && parse_octets_left(context) > 0) { warning(0, "WSP: %s: skipping %ld trailing octets.", headername, parse_octets_left(context)); } parse_skip_to_limit(context); parse_pop_limit(context); } else { panic(0, "Unknown field-value type %d.", ret); } if (ch == NULL && decoded != NULL) ch = octstr_get_cstr(decoded); if (ch == NULL) goto value_error; if (!headername) { warning(0, "Unknown header number 0x%02x.", field_type); goto value_error; } http_header_add(unpacked, headername, ch); octstr_destroy(decoded); return; value_error: warning(0, "Skipping faulty header."); octstr_destroy(decoded); } void wsp_unpack_app_header(List *unpacked, ParseContext *context) { Octstr *header = NULL; Octstr *value = NULL; header = parse_get_nul_string(context); value = parse_get_nul_string(context); if (header && value) { http_header_add(unpacked, octstr_get_cstr(header), octstr_get_cstr(value)); } if (parse_error(context)) warning(0, "Error parsing application-header."); octstr_destroy(header); octstr_destroy(value); } List *wsp_headers_unpack(Octstr *headers, int content_type_present) { ParseContext *context; int byte; List *unpacked; int code_page; unpacked = http_create_empty_headers(); context = parse_context_create(headers); if (octstr_len(headers) > 0) { debug("wsp", 0, "WSP: decoding headers:"); octstr_dump(headers, 0); } if (content_type_present) wsp_unpack_well_known_field(unpacked, WSP_HEADER_CONTENT_TYPE, context); code_page = 1; /* default */ while (parse_octets_left(context) > 0 && !parse_error(context)) { byte = parse_get_char(context); if (byte == 127 || (byte >= 1 && byte <= 31)) { if (byte == 127) code_page = parse_get_char(context); else code_page = byte; if (code_page == 1) info(0, "Returning to code page 1 (default)."); else { warning(0, "Shift to unknown code page %d.", code_page); warning(0, "Will try to skip headers until " "next known code page."); } } else if (byte >= 128) { /* well-known-header */ if (code_page == 1) wsp_unpack_well_known_field(unpacked, byte - 128, context); else { debug("wsp", 0, "Skipping field 0x%02x.", byte); wsp_skip_field_value(context); } } else if (byte > 31 && byte < 127) { /* Un-parse the character we just read */ parse_skip(context, -1); wsp_unpack_app_header(unpacked, context); } else { warning(0, "Unsupported token or header (start 0x%x)", byte); break; } } if (gwlist_len(unpacked) > 0) { long i; debug("wsp", 0, "WSP: decoded headers:"); for (i = 0; i < gwlist_len(unpacked); i++) { Octstr *header = gwlist_get(unpacked, i); debug("wsp", 0, "%s", octstr_get_cstr(header)); } debug("wsp", 0, "WSP: End of decoded headers."); } parse_context_destroy(context); return unpacked; } /**********************************************************************/ /* Start of header packing code (HTTP to WSP) */ /**********************************************************************/ static int pack_accept(Octstr *packet, Octstr *value); static int pack_accept_charset(Octstr *packet, Octstr *value); static int pack_accept_encoding(Octstr *packet, Octstr *value); static int pack_accept_language(Octstr *packet, Octstr *value); static int pack_cache_control(Octstr *packet, Octstr *value); static int pack_challenge(Octstr *packet, Octstr *value); static int pack_connection(Octstr *packet, Octstr *value); static int pack_content_disposition(Octstr *packet, Octstr *value); static int pack_content_range(Octstr *packet, Octstr *value); static int pack_credentials(Octstr *packet, Octstr *value); static int pack_encoding(Octstr *packet, Octstr *value); static int pack_expires(Octstr *packet, Octstr *value); static int pack_field_name(Octstr *packet, Octstr *value); static int pack_if_range(Octstr *packet, Octstr *value); static int pack_language(Octstr *packet, Octstr *value); static int pack_md5(Octstr *packet, Octstr *value); static int pack_method(Octstr *packet, Octstr *value); static int pack_pragma(Octstr *packet, Octstr *value); static int pack_range(Octstr *packet, Octstr *value); static int pack_range_unit(Octstr *packet, Octstr *value); static int pack_transfer_encoding(Octstr *packet, Octstr *value); static int pack_uri(Octstr *packet, Octstr *value); static int pack_warning(Octstr *packet, Octstr *value); /* these are used in MMS encapsulation code too */ struct headerinfo headerinfo[] = { { WSP_HEADER_ACCEPT, pack_accept, LIST }, { WSP_HEADER_ACCEPT_CHARSET, pack_accept_charset, LIST }, { WSP_HEADER_ACCEPT_ENCODING, pack_accept_encoding, LIST }, { WSP_HEADER_ACCEPT_LANGUAGE, pack_accept_language, LIST }, { WSP_HEADER_ACCEPT_RANGES, pack_range_unit, LIST }, { WSP_HEADER_AGE, wsp_pack_integer_string, 0 }, /* pack_method is slightly too general because Allow is only * supposed to encode well-known-methods. */ { WSP_HEADER_ALLOW, pack_method, LIST }, { WSP_HEADER_AUTHORIZATION, pack_credentials, BROKEN_LIST }, { WSP_HEADER_CACHE_CONTROL, pack_cache_control, LIST }, { WSP_HEADER_CACHE_CONTROL_V13, pack_cache_control, LIST }, { WSP_HEADER_CACHE_CONTROL_V14, pack_cache_control, LIST }, { WSP_HEADER_CONNECTION, pack_connection, LIST }, { WSP_HEADER_CONTENT_BASE, pack_uri, 0 }, { WSP_HEADER_CONTENT_ENCODING, pack_encoding, LIST }, { WSP_HEADER_CONTENT_LANGUAGE, pack_language, LIST }, { WSP_HEADER_CONTENT_LENGTH, wsp_pack_integer_string, 0 }, { WSP_HEADER_CONTENT_LOCATION, pack_uri, 0 }, { WSP_HEADER_CONTENT_MD5, pack_md5, 0 }, { WSP_HEADER_CONTENT_RANGE, pack_content_range, 0 }, { WSP_HEADER_CONTENT_TYPE, wsp_pack_content_type, 0 }, { WSP_HEADER_DATE, wsp_pack_date, 0 }, { WSP_HEADER_ETAG, wsp_pack_text, 0 }, { WSP_HEADER_EXPIRES, pack_expires, 0 }, { WSP_HEADER_FROM, wsp_pack_text, 0 }, { WSP_HEADER_HOST, wsp_pack_text, 0 }, { WSP_HEADER_IF_MODIFIED_SINCE, wsp_pack_date, 0 }, { WSP_HEADER_IF_MATCH, wsp_pack_text, 0 }, { WSP_HEADER_IF_NONE_MATCH, wsp_pack_text, 0 }, { WSP_HEADER_IF_RANGE, pack_if_range, 0 }, { WSP_HEADER_IF_UNMODIFIED_SINCE, wsp_pack_date, 0 }, { WSP_HEADER_LAST_MODIFIED, wsp_pack_date, 0 }, { WSP_HEADER_LOCATION, pack_uri, 0 }, { WSP_HEADER_MAX_FORWARDS, wsp_pack_integer_string, 0 }, { WSP_HEADER_PRAGMA, pack_pragma, LIST }, { WSP_HEADER_PROXY_AUTHENTICATE, pack_challenge, BROKEN_LIST }, { WSP_HEADER_PROXY_AUTHORIZATION, pack_credentials, BROKEN_LIST }, { WSP_HEADER_PUBLIC, pack_method, LIST }, { WSP_HEADER_RANGE, pack_range, 0 }, { WSP_HEADER_REFERER, pack_uri, 0 }, { WSP_HEADER_RETRY_AFTER, wsp_pack_retry_after, 0 }, { WSP_HEADER_SERVER, wsp_pack_text, 0 }, { WSP_HEADER_TRANSFER_ENCODING, pack_transfer_encoding, LIST }, { WSP_HEADER_UPGRADE, wsp_pack_text, LIST }, { WSP_HEADER_USER_AGENT, wsp_pack_text, 0 }, { WSP_HEADER_VARY, pack_field_name, LIST }, { WSP_HEADER_VIA, wsp_pack_text, LIST }, { WSP_HEADER_WARNING, pack_warning, LIST }, { WSP_HEADER_WWW_AUTHENTICATE, pack_challenge, BROKEN_LIST }, { WSP_HEADER_CONTENT_DISPOSITION, pack_content_disposition, 0 }, { WSP_HEADER_PUSH_FLAG, wsp_pack_integer_string, 0}, { WSP_HEADER_X_WAP_CONTENT_URI, pack_uri, 0}, { WSP_HEADER_X_WAP_INITIATOR_URI, pack_uri, 0}, { WSP_HEADER_X_WAP_APPLICATION_ID, wsp_pack_integer_string, 0}, { WSP_HEADER_CONTENT_ID, wsp_pack_text, 0}, { WSP_HEADER_ENCODING_VERSION, wsp_pack_version_value, 0 } // DAVI { WSP_HEADER_SET_COOKIE, pack_version_value, 0 } }; static Parameter *parm_create(Octstr *key, Octstr *value) { Parameter *parm; parm = gw_malloc(sizeof(*parm)); parm->key = key; parm->value = value; return parm; } static void parm_destroy(Parameter *parm) { if (parm == NULL) return; octstr_destroy(parm->key); octstr_destroy(parm->value); gw_free(parm); } void parm_destroy_item(void *parm) { parm_destroy(parm); } static Parameter *parm_parse(Octstr *value) { long pos; Octstr *key, *val; pos = octstr_search_char(value, '=', 0); if (pos > 0) { key = octstr_copy(value, 0, pos); val = octstr_copy(value, pos + 1, octstr_len(value) - pos); octstr_strip_blanks(key); octstr_strip_blanks(val); } else { key = octstr_duplicate(value); val = NULL; } return parm_create(key, val); } /* Many HTTP field elements can take parameters in a standardized * form: parameters appear after the main value, each is introduced * by a semicolon (;), and consists of a key=value pair or just * a key, where the key is a token and the value is either a token * or a quoted-string. * The main value itself is a series of tokens, separators, and * quoted-strings. * * This function will take such a field element, and remove all * parameters from it. The parameters are returned as a List * of Parameter, where the value field is left NULL * if the parameter was just a key. * It returns NULL if there were no parameters. */ List *wsp_strip_parameters(Octstr *value) { long pos; long len; int c; long end; List *parms; long firstparm; len = octstr_len(value); /* Find the start of the first parameter. */ for (pos = 0; pos < len; pos++) { c = octstr_get_char(value, pos); if (c == ';') break; else if (c == '"') pos += http_header_quoted_string_len(value, pos) - 1; } if (pos >= len) return NULL; /* no parameters */ parms = gwlist_create(); firstparm = pos; for (pos++; pos > 0 && pos < len; pos++) { Octstr *key = NULL; Octstr *val = NULL; end = octstr_search_char(value, '=', pos); if (end < 0) end = octstr_search_char(value, ';', pos); if (end < 0) end = octstr_len(value); key = octstr_copy(value, pos, end - pos); octstr_strip_blanks(key); pos = end; if (octstr_get_char(value, pos) == '=') { pos++; while (isspace(octstr_get_char(value, pos))) pos++; if (octstr_get_char(value, pos) == '"') end = pos + http_header_quoted_string_len(value, pos); else end = octstr_search_char(value, ';', pos); if (end < 0) end = octstr_len(value); val = octstr_copy(value, pos, end - pos); octstr_strip_blanks(val); pos = end; pos = octstr_search_char(value, ';', pos); } gwlist_append(parms, parm_create(key, val)); } octstr_delete(value, firstparm, octstr_len(value) - firstparm); octstr_strip_blanks(value); return parms; } int wsp_pack_text(Octstr *packed, Octstr *text) { /* This check catches 0-length strings as well, because * octstr_get_char will return -1. */ if (octstr_get_char(text, 0) >= 128 || octstr_get_char(text, 0) < 32) octstr_append_char(packed, WSP_QUOTE); octstr_append(packed, text); octstr_append_char(packed, 0); return 0; } /* Pack text as Quoted-string if it starts with a " character. * Pack it as Text-string otherwise. */ static void pack_quoted_string(Octstr *packed, Octstr *text) { octstr_append(packed, text); if (octstr_get_char(text, octstr_len(text) - 1) == '"' && octstr_get_char(text, 0) == '"') octstr_delete(packed, octstr_len(packed) - 1, 1); octstr_append_char(packed, 0); } /* Is this char in the 'separators' set defined by HTTP? */ static int is_separator_char(int c) { switch (c) { case '(': case ')': case '<': case '>': case '@': case ',': case ';': case ':': case '\\': case '"': case '/': case '[': case ']': case '?': case '=': case '{': case '}': case 32: /* SP */ case 9: /* HT */ return 1; default: return 0; } } /* Is this char part of a 'token' as defined by HTTP? */ static int is_token_char(int c) { return c >= 32 && c < 127 && !is_separator_char(c); } /* Is this string a 'token' as defined by HTTP? */ static int is_token(Octstr *token) { return octstr_len(token) > 0 && octstr_check_range(token, 0, octstr_len(token), is_token_char); } /* We represent qvalues as integers from 0 through 1000, rather than * as floating values. */ static int parse_qvalue(Octstr *value) { int qvalue; if (value == NULL) return -1; if (!isdigit(octstr_get_char(value, 0))) return -1; qvalue = (octstr_get_char(value, 0) - '0') * 1000; if (octstr_get_char(value, 1) != '.') goto gotvalue; if (!isdigit(octstr_get_char(value, 2))) goto gotvalue; qvalue += (octstr_get_char(value, 2) - '0') * 100; if (!isdigit(octstr_get_char(value, 3))) goto gotvalue; qvalue += (octstr_get_char(value, 3) - '0') * 10; if (!isdigit(octstr_get_char(value, 4))) goto gotvalue; qvalue += (octstr_get_char(value, 4) - '0'); gotvalue: if (qvalue < 0 || qvalue > 1000) return -1; return qvalue; } static int get_qvalue(List *parms, int default_qvalue) { long i; Parameter *parm; int qvalue; for (i = 0; i < gwlist_len(parms); i++) { parm = gwlist_get(parms, i); if (octstr_str_compare(parm->key, "q") == 0 || octstr_str_compare(parm->key, "Q") == 0) { qvalue = parse_qvalue(parm->value); if (qvalue >= 0) return qvalue; } } return default_qvalue; } static int pack_qvalue(Octstr *packed, int qvalue) { /* "Quality factor 1 is the default value and shall never * be sent." */ if (qvalue == 1000) return -1; /* Remember that our qvalues are already multiplied by 1000. */ if (qvalue % 10 == 0) qvalue = qvalue / 10 + 1; else qvalue = qvalue + 100; octstr_append_uintvar(packed, qvalue); return 0; } /* Pack value as a Value-length followed by the encoded value. */ void wsp_pack_value(Octstr *packed, Octstr *encoded) { long len; len = octstr_len(encoded); if (len <= 30) octstr_append_char(packed, len); else { octstr_append_char(packed, 31); octstr_append_uintvar(packed, len); } octstr_append(packed, encoded); } void wsp_pack_long_integer(Octstr *packed, unsigned long integer) { long oldlen = octstr_len(packed); unsigned char octet; long len; if (integer == 0) { /* The Multi-octet-integer has to be at least 1 octet long. */ octstr_append_char(packed, 1); /* length */ octstr_append_char(packed, 0); /* value */ return; } /* Encode it back-to-front, by repeatedly inserting * at the same position, because that's easier. */ for (len = 0; integer != 0; integer >>= 8, len++) { octet = integer & 0xff; octstr_insert_data(packed, oldlen, &octet, 1); } octet = len; octstr_insert_data(packed, oldlen, &octet, 1); } void wsp_pack_short_integer(Octstr *packed, unsigned long integer) { gw_assert(integer <= MAX_SHORT_INTEGER); octstr_append_char(packed, integer + 0x80); } void wsp_pack_integer_value(Octstr *packed, unsigned long integer) { if (integer <= MAX_SHORT_INTEGER) wsp_pack_short_integer(packed, integer); else wsp_pack_long_integer(packed, integer); } int wsp_pack_integer_string(Octstr *packed, Octstr *value) { unsigned long integer; long pos; int c; int digit; integer = 0; for (pos = 0; pos < octstr_len(value); pos++) { c = octstr_get_char(value, pos); if (!isdigit(c)) break; digit = c - '0'; if (integer > ULONG_MAX / 10) goto overflow; integer *= 10; if (integer > ULONG_MAX - digit) goto overflow; integer += digit; } wsp_pack_integer_value(packed, integer); return 0; overflow: warning(0, "WSP: Number too large to handle: '%s'.", octstr_get_cstr(value)); return -1; } int wsp_pack_version_value(Octstr *packed, Octstr *version) { long major, minor; long pos; pos = octstr_parse_long(&major, version, 0, 10); if (pos < 0 || major < 1 || major > 7) goto usetext; if (pos == octstr_len(version)) minor = 15; else { if (octstr_get_char(version, pos) != '.') goto usetext; pos = octstr_parse_long(&minor, version, pos + 1, 10); if (pos != octstr_len(version) || minor < 0 || minor > 14) goto usetext; } wsp_pack_short_integer(packed, major << 4 | minor); return 0; usetext: wsp_pack_text(packed, version); return 0; } int wsp_pack_constrained_value(Octstr *packed, Octstr *text, long value) { if (value >= 0) wsp_pack_short_integer(packed, value); else wsp_pack_text(packed, text); return 0; } static void pack_parameter(Octstr *packed, Parameter *parm) { long keytoken; long tmp; long start; start = octstr_len(packed); /* Parameter = Typed-parameter | Untyped-parameter */ /* keytoken = wsp_string_to_parameter(parm->key); */ /* XXX this should obey what kind of WSP Encoding-Version the client is using */ keytoken = wsp_string_to_versioned_parameter(parm->key, WSP_1_2); if (keytoken >= 0) { /* Typed-parameter = Well-known-parameter-token Typed-value */ /* Well-known-parameter-token = Integer-value */ wsp_pack_integer_value(packed, keytoken); /* Typed-value = Compact-value | Text-value */ /* First try to pack as Compact-value or No-value. * If that fails, pack as Text-value. */ if (parm->value == NULL) { octstr_append_char(packed, 0); /* No-value */ return; } else switch (keytoken) { case 0: /* q */ tmp = parse_qvalue(parm->value); if (tmp >= 0) { if (pack_qvalue(packed, tmp) < 0) octstr_delete(packed, start, octstr_len(packed) - start); return; } break; case 1: /* charset */ tmp = wsp_string_to_charset(parm->value); if (tmp >= 0) { wsp_pack_integer_value(packed, tmp); return; } break; case 2: /* level */ wsp_pack_version_value(packed, parm->value); return; case 3: /* type */ if (octstr_check_range(parm->value, 0, octstr_len(parm->value), gw_isdigit) && wsp_pack_integer_string(packed, parm->value) >= 0) return; break; case 5: /* name */ case 6: /* filename */ break; case 7: /* differences */ if (pack_field_name(packed, parm->value) >= 0) return; break; case 8: /* padding */ if (octstr_parse_long(&tmp, parm->value, 0, 10) == octstr_len(parm->value) && tmp >= 0 && tmp <= MAX_SHORT_INTEGER) { wsp_pack_short_integer(packed, tmp); return; } break; } pack_quoted_string(packed, parm->value); } else { /* Untyped-parameter = Token-text Untyped-value */ wsp_pack_text(packed, parm->key); /* Untyped-value = Integer-value | Text-value */ if (parm->value == NULL) { octstr_append_char(packed, 0); /* No-value */ return; } /* If we can pack as integer, do so. */ if (octstr_parse_long(&tmp, parm->value, 0, 10) == octstr_len(parm->value)) { wsp_pack_integer_value(packed, tmp); } else { pack_quoted_string(packed, parm->value); } } } void wsp_pack_parameters(Octstr *packed, List *parms) { long i; Parameter *parm; for (i = 0; i < gwlist_len(parms); i++) { parm = gwlist_get(parms, i); pack_parameter(packed, parm); } } static int pack_uri(Octstr *packed, Octstr *value) { wsp_pack_text(packed, value); return 0; } static int pack_md5(Octstr *packed, Octstr *value) { Octstr *binary; binary = octstr_duplicate(value); octstr_base64_to_binary(binary); if (octstr_len(binary) != 16) { error(0, "WSP: MD5 value not 128 bits."); return -1; } octstr_append_char(packed, 16); octstr_append(packed, binary); octstr_destroy(binary); return 0; } /* Actually packs a "Value-length Challenge" */ /* Relies on http_split_auth_value to have converted the entry to * the normal HTTP parameter format rather than the comma-separated * one used by challenge and credentials. */ static int pack_challenge(Octstr *packed, Octstr *value) { Octstr *encoding = NULL; Octstr *scheme = NULL; Octstr *basic = octstr_imm("Basic"); Octstr *realm = octstr_imm("realm"); Octstr *parmstring = NULL; List *parms = NULL; Parameter *realmparm = NULL; long realmpos = -1; Octstr *realmval = NULL; long pos; encoding = octstr_create(""); /* Get authentication scheme */ for (pos = 0; pos < octstr_len(value); pos++) { if (!is_token_char(octstr_get_char(value, pos))) break; } scheme = octstr_copy(value, 0, pos); octstr_strip_blanks(scheme); /* Skip whitespace */ while (isspace(octstr_get_char(value, pos))) pos++; if (octstr_case_compare(scheme, basic) == 0) { parmstring = octstr_copy(value, pos, octstr_len(value) - pos); realmparm = parm_parse(parmstring); octstr_append_char(encoding, BASIC_AUTHENTICATION); realmpos = octstr_len(encoding); } else { long i; wsp_pack_text(encoding, scheme); realmpos = octstr_len(encoding); /* Find the realm parameter and exclude it */ parms = wsp_strip_parameters(value); for (i = 0; i < gwlist_len(parms); i++) { Parameter *parm = gwlist_get(parms, i); if (octstr_case_compare(realm, parm->key) == 0) { realmparm = parm; gwlist_delete(parms, i, 1); break; } } wsp_pack_parameters(encoding, parms); } /* * In the WSP encoding we have to put the realm value first, but * with non-Basic challenges we don't know if it will come first * in the HTTP header. So we just start parsing parameters, and * go back and insert the realm value later. The same technique * is used for Basic authentication to simplify the code. */ if (realmparm == NULL || octstr_case_compare(realmparm->key, realm) != 0 || realmparm->value == NULL) goto error; /* Zap quote marks */ if (octstr_get_char(realmparm->value, 0) == '"' && octstr_get_char(realmparm->value, octstr_len(realmparm->value) - 1) == '"') { octstr_delete(realmparm->value, 0, 1); octstr_delete(realmparm->value, octstr_len(realmparm->value) - 1, 1); } gw_assert(realmpos >= 0); realmval = octstr_create(""); wsp_pack_text(realmval, realmparm->value); octstr_insert(encoding, realmval, realmpos); wsp_pack_value(packed, encoding); octstr_destroy(encoding); octstr_destroy(scheme); octstr_destroy(parmstring); parm_destroy(realmparm); gwlist_destroy(parms, parm_destroy_item); octstr_destroy(realmval); return 0; error: warning(0, "WSP: Cannot parse challenge."); octstr_destroy(encoding); octstr_destroy(scheme); octstr_destroy(parmstring); parm_destroy(realmparm); gwlist_destroy(parms, parm_destroy_item); octstr_destroy(realmval); return -1; } /* Actually packs a "Value-length Credentials" */ /* Relies on http_split_auth_value to have converted the entry to * the normal HTTP parameter format rather than the comma-separated * one used by challenge and credentials. */ static int pack_credentials(Octstr *packed, Octstr *value) { Octstr *encoding = NULL; Octstr *scheme = NULL; Octstr *basic = NULL; long pos; encoding = octstr_create(""); /* Get authentication scheme */ for (pos = 0; pos < octstr_len(value); pos++) { if (!is_token_char(octstr_get_char(value, pos))) break; } scheme = octstr_copy(value, 0, pos); octstr_strip_blanks(scheme); /* Skip whitespace */ while (isspace(octstr_get_char(value, pos))) pos++; basic = octstr_imm("Basic"); if (octstr_case_compare(scheme, basic) == 0) { Octstr *cookie; Octstr *userid; Octstr *password; octstr_append_char(encoding, BASIC_AUTHENTICATION); cookie = octstr_copy(value, pos, octstr_len(value) - pos); octstr_base64_to_binary(cookie); pos = octstr_search_char(cookie, ':', 0); if (pos < 0) { warning(0, "WSP: bad cookie in credentials '%s'.", octstr_get_cstr(value)); octstr_destroy(cookie); goto error; } userid = octstr_copy(cookie, 0, pos); password = octstr_copy(cookie, pos + 1, octstr_len(cookie) - pos); wsp_pack_text(encoding, userid); wsp_pack_text(encoding, password); octstr_destroy(cookie); octstr_destroy(userid); octstr_destroy(password); } else { List *parms; wsp_pack_text(encoding, scheme); parms = wsp_strip_parameters(value); wsp_pack_parameters(encoding, parms); gwlist_destroy(parms, parm_destroy_item); } wsp_pack_value(packed, encoding); octstr_destroy(encoding); octstr_destroy(scheme); return 0; error: octstr_destroy(encoding); octstr_destroy(scheme); return -1; } int wsp_pack_date(Octstr *packed, Octstr *value) { long timeval; /* If we get a negative timeval here, this means either * we're beyond the time_t 32 bit int positive border for the * timestamp or we're really handling time before epoch. */ timeval = date_parse_http(value); if (timeval == -1) { warning(0, "WSP headers: cannot decode date '%s'", octstr_get_cstr(value)); return -1; } /* We'll assume that we don't package time before epoch. */ wsp_pack_long_integer(packed, (unsigned long) timeval); return 0; } static int pack_connection(Octstr *packed, Octstr *value) { return wsp_pack_constrained_value(packed, value, wsp_string_to_connection(value)); } static int pack_encoding(Octstr *packed, Octstr *value) { return wsp_pack_constrained_value(packed, value, wsp_string_to_encoding(value)); } static int pack_field_name(Octstr *packed, Octstr *value) { /* XXX we need to obey which WSP encoding-version to use */ /* return pack_constrained_value(packed, value, wsp_string_to_header(value)); */ return wsp_pack_constrained_value(packed, value, wsp_string_to_versioned_header(value, WSP_1_2)); } static int pack_language(Octstr *packed, Octstr *value) { long language; /* Can't use pack_constrained_value here because * language does not necessarily fit in a Short-integer. */ language = wsp_string_to_language(value); if (language >= 0) wsp_pack_integer_value(packed, language); else wsp_pack_text(packed, value); return 0; } /* Encode value as Well-known-method | Token-text */ static int pack_method(Octstr *packed, Octstr *value) { /* In the future, we will need some way to refer to extended * method names negotiated for this session. */ return wsp_pack_constrained_value(packed, value, wsp_string_to_method(value)); } /* Encode value as Accept-ranges-value */ static int pack_range_unit(Octstr *packed, Octstr *value) { return wsp_pack_constrained_value(packed, value, wsp_string_to_ranges(value)); } /* Encode byte-range-spec | suffix-byte-range-spec as Range-value. * Return position after the parsed spec. */ static int pack_range_value(Octstr *packed, Octstr *value, long pos) { long first_byte_pos; long last_byte_pos; long suffix_length; Octstr *encoding; while (isspace(octstr_get_char(value, pos))) pos++; if (isdigit(octstr_get_char(value, pos))) { /* byte-range-spec */ pos = octstr_parse_long(&first_byte_pos, value, pos, 10); if (pos < 0 || first_byte_pos < 0) return -1; while (isspace(octstr_get_char(value, pos))) pos++; if (octstr_get_char(value, pos) != '-') return -1; pos++; while (isspace(octstr_get_char(value, pos))) pos++; if (isdigit(octstr_get_char(value, pos))) { pos = octstr_parse_long(&last_byte_pos, value, pos, 10); if (pos < 0 || last_byte_pos < 0) return -1; } else { last_byte_pos = -1; } encoding = octstr_create(""); octstr_append_char(encoding, BYTE_RANGE); octstr_append_uintvar(encoding, first_byte_pos); if (last_byte_pos >= 0) octstr_append_uintvar(encoding, last_byte_pos); wsp_pack_value(packed, encoding); octstr_destroy(encoding); } else if (octstr_get_char(value, pos) == '-') { /* suffix-byte-range-spec */ pos++; pos = octstr_parse_long(&suffix_length, value, pos, 10); if (pos < 0 || suffix_length < 0) return -1; encoding = octstr_create(""); octstr_append_char(encoding, SUFFIX_BYTE_RANGE); octstr_append_uintvar(encoding, suffix_length); wsp_pack_value(packed, encoding); octstr_destroy(encoding); } else return -1; return pos; } static int pack_transfer_encoding(Octstr *packed, Octstr *value) { return wsp_pack_constrained_value(packed, value, wsp_string_to_transfer_encoding(value)); } /* Also used by pack_content_type */ static int pack_accept(Octstr *packed, Octstr *value) { List *parms; long media; parms = wsp_strip_parameters(value); /* XXX we need to obey which WSP encoding-version to use */ /* media = wsp_string_to_content_type(value); */ media = wsp_string_to_versioned_content_type(value, WSP_1_2); /* See if we can fit this in a Constrained-media encoding */ if (parms == NULL && media <= MAX_SHORT_INTEGER) { wsp_pack_constrained_value(packed, value, media); } else { Octstr *encoding = octstr_create(""); if (media >= 0) wsp_pack_integer_value(encoding, media); else wsp_pack_text(encoding, value); wsp_pack_parameters(encoding, parms); wsp_pack_value(packed, encoding); octstr_destroy(encoding); } gwlist_destroy(parms, parm_destroy_item); return 0; } static int pack_accept_charset(Octstr *packed, Octstr *value) { List *parms; long charset; long qvalue; parms = wsp_strip_parameters(value); charset = wsp_string_to_charset(value); qvalue = 1000; if (parms) qvalue = get_qvalue(parms, qvalue); gwlist_destroy(parms, parm_destroy_item); /* See if we can fit this in a Constrained-charset encoding */ if (qvalue == 1000 && charset <= MAX_SHORT_INTEGER) { wsp_pack_constrained_value(packed, value, charset); } else { Octstr *encoding = octstr_create(""); if (charset >= 0) wsp_pack_integer_value(encoding, charset); else wsp_pack_text(encoding, value); if (qvalue != 1000) pack_qvalue(encoding, qvalue); wsp_pack_value(packed, encoding); octstr_destroy(encoding); } return 0; } /* WSP 8.4.2.9 does not specify any way to encode parameters for * an Accept-encoding field, but RFC2616 allows q parameters. * We'll have to simplify: qvalue > 0 means encode it, qvalue == 0 * means don't encode it. */ static int pack_accept_encoding(Octstr *packed, Octstr *value) { List *parms; int qvalue; qvalue = 1000; /* default */ parms = wsp_strip_parameters(value); if (parms) qvalue = get_qvalue(parms, qvalue); gwlist_destroy(parms, parm_destroy_item); if (qvalue > 0) { if (qvalue < 1000) warning(0, "Cannot encode q-value in Accept-Encoding."); pack_encoding(packed, value); } else { warning(0, "Cannot encode q=0 in Accept-Encoding; skipping this encoding."); return -1; } return 0; } static int pack_accept_language(Octstr *packed, Octstr *value) { List *parms; long language; long qvalue; parms = wsp_strip_parameters(value); language = wsp_string_to_language(value); qvalue = 1000; if (parms) qvalue = get_qvalue(parms, qvalue); gwlist_destroy(parms, parm_destroy_item); /* See if we can fit this in a Constrained-language encoding. */ /* Note that our language table already includes Any-language */ if (qvalue == 1000 && language <= MAX_SHORT_INTEGER) { wsp_pack_constrained_value(packed, value, language); } else { Octstr *encoding = octstr_create(""); if (language >= 0) wsp_pack_integer_value(encoding, language); else wsp_pack_text(encoding, value); if (qvalue != 1000) pack_qvalue(encoding, qvalue); wsp_pack_value(packed, encoding); octstr_destroy(encoding); } return 0; } static int pack_cache_control(Octstr *packed, Octstr *value) { Parameter *parm; long tmp; parm = parm_parse(value); if (parm->value == NULL) { wsp_pack_constrained_value(packed, value, wsp_string_to_cache_control(parm->key)); } else { Octstr *encoding = octstr_create(""); tmp = wsp_string_to_cache_control(parm->key); if (tmp < 0) { /* According to WSP 8.4.2.15, the format * is "Cache-extension Parameter", and * Cache-extension is a Token-text. * But in HTTP a cache-extension is of * the form token=value, which maps * nicely to Parameter. So what is * this extra Token-text? I decided to leave it blank. * - Richard Braakman */ wsp_pack_text(encoding, octstr_imm("")); pack_parameter(encoding, parm); } else { int done = 0; Octstr *value_encoding; List *names; Octstr *element; value_encoding = octstr_create(""); switch (tmp) { case WSP_CACHE_CONTROL_NO_CACHE: case WSP_CACHE_CONTROL_PRIVATE: if (octstr_get_char(parm->value, 0) == '"') octstr_delete(parm->value, 0, 1); if (octstr_get_char(parm->value, octstr_len(parm->value) - 1) == '"') octstr_delete(parm->value, octstr_len(parm->value) - 1, 1); names = http_header_split_value(parm->value); while ((element = gwlist_consume(names))) { pack_field_name(value_encoding, element); octstr_destroy(element); } gwlist_destroy(names, octstr_destroy_item); done = 1; break; case WSP_CACHE_CONTROL_MAX_AGE: case WSP_CACHE_CONTROL_MAX_STALE: case WSP_CACHE_CONTROL_MIN_FRESH: if (wsp_pack_integer_string(value_encoding, parm->value) >= 0) done = 1; break; } if (done) { wsp_pack_short_integer(encoding, tmp); octstr_append(encoding, value_encoding); } else { /* See note above */ pack_parameter(encoding, parm); } octstr_destroy(value_encoding); } wsp_pack_value(packed, encoding); octstr_destroy(encoding); } parm_destroy(parm); return 1; } static int pack_content_disposition(Octstr *packed, Octstr *value) { List *parms; long disposition; parms = wsp_strip_parameters(value); disposition = wsp_string_to_disposition(value); if (disposition >= 0) { Octstr *encoding = octstr_create(""); wsp_pack_short_integer(encoding, disposition); wsp_pack_parameters(encoding, parms); wsp_pack_value(packed, encoding); octstr_destroy(encoding); } else { warning(0, "WSP: Cannot encode Content-Disposition '%s'.", octstr_get_cstr(value)); goto error; } gwlist_destroy(parms, parm_destroy_item); return 0; error: gwlist_destroy(parms, parm_destroy_item); return -1; } static int pack_content_range(Octstr *packed, Octstr *value) { Octstr *bytes; long pos; long firstbyte, lastbyte, instancelen; Octstr *encoding; bytes = octstr_imm("bytes "); if (octstr_ncompare(value, bytes, octstr_len(bytes)) != 0) goto error; pos = octstr_len(bytes); while (isspace(octstr_get_char(value, pos))) pos++; if (octstr_get_char(value, pos) == '*') goto warning; pos = octstr_parse_long(&firstbyte, value, pos, 10); if (pos < 0) goto error; while (isspace(octstr_get_char(value, pos))) pos++; if (octstr_get_char(value, pos++) != '-') goto error; pos = octstr_parse_long(&lastbyte, value, pos, 10); if (pos < 0) goto error; while (isspace(octstr_get_char(value, pos))) pos++; if (octstr_get_char(value, pos++) != '/') goto error; while (isspace(octstr_get_char(value, pos))) pos++; if (octstr_get_char(value, pos) == '*') goto warning; pos = octstr_parse_long(&instancelen, value, pos, 10); if (pos < 0) goto error; /* XXX: If the range is valid but not representable, * or if it's invalid, then should we refrain from sending * anything at all? It might pollute the client's cache. */ if (lastbyte < firstbyte || instancelen < lastbyte) { warning(0, "WSP: Content-Range '%s' is invalid.", octstr_get_cstr(value)); return -1; } encoding = octstr_create(""); octstr_append_uintvar(encoding, firstbyte); octstr_append_uintvar(encoding, instancelen); wsp_pack_value(packed, encoding); octstr_destroy(encoding); return 0; error: warning(0, "WSP: Cannot parse Content-Range '%s'.", octstr_get_cstr(value)); return -1; warning: warning(0, "WSP: Cannot encode Content-Range '%s'.", octstr_get_cstr(value)); return -1; } int wsp_pack_content_type(Octstr *packed, Octstr *value) { /* The expansion of Content-type-value works out to be * equivalent to Accept-value. */ return pack_accept(packed, value); } static int pack_expires(Octstr *packed, Octstr *value) { int ret; ret = wsp_pack_date(packed, value); if (ret < 0) { /* Responses with an invalid Expires header should be treated as already expired. If we just skip this header, then the client won't know that. So we encode one with a date far in the past. */ wsp_pack_long_integer(packed, LONG_AGO_VALUE); ret = 0; } return ret; } static int pack_if_range(Octstr *packed, Octstr *value) { if (octstr_get_char(value, 0) == '"' || (octstr_get_char(value, 0) == 'W' && octstr_get_char(value, 1) == '/')) { return wsp_pack_text(packed, value); /* It's an etag */ } else { return wsp_pack_date(packed, value); } } static int pack_pragma(Octstr *packed, Octstr *value) { Octstr *nocache; nocache = octstr_imm("no-cache"); if (octstr_case_compare(value, nocache) == 0) wsp_pack_short_integer(packed, WSP_CACHE_CONTROL_NO_CACHE); else { Parameter *parm; Octstr *encoding; encoding = octstr_create(""); parm = parm_parse(value); pack_parameter(encoding, parm); wsp_pack_value(packed, encoding); octstr_destroy(encoding); parm_destroy(parm); } return 0; } static int pack_range(Octstr *packed, Octstr *value) { Octstr *bytes = octstr_imm("bytes"); long pos; if (octstr_ncompare(value, bytes, octstr_len(bytes)) != 0 || is_token_char(octstr_get_char(value, octstr_len(bytes)))) goto error; pos = octstr_len(bytes); while (isspace(octstr_get_char(value, pos))) pos++; if (octstr_get_char(value, pos) != '=') goto error; pos++; for (;;) { /* Discard the whole header if any part of it can't be * parsed. Probably a partial Range header is worse * than none at all. */ pos = pack_range_value(packed, value, pos); if (pos < 0) goto error; while (isspace(octstr_get_char(value, pos))) pos++; if (octstr_get_char(value, pos) != ',') break; pos++; wsp_pack_short_integer(packed, WSP_HEADER_RANGE); } return 0; error: warning(0, "WSP: Cannot parse 'Range: %s'.", octstr_get_cstr(value)); return -1; } /* The value is either a HTTP-date or a delta-seconds (integer). */ int wsp_pack_retry_after(Octstr *packed, Octstr *value) { Octstr *encoded = NULL; encoded = octstr_create(""); if (isdigit(octstr_get_char(value, 0))) { octstr_append_char(encoded, RELATIVE_TIME); if (wsp_pack_integer_string(encoded, value) < 0) goto error; } else { octstr_append_char(encoded, ABSOLUTE_TIME); if (wsp_pack_date(encoded, value) < 0) goto error; } wsp_pack_value(packed, encoded); octstr_destroy(encoded); return 0; error: octstr_destroy(encoded); return -1; } static int convert_rfc2616_warning_to_rfc2068(int warn_code) { int i; struct { int rfc2616code; int rfc2068code; } code_transform[] = { { 110, 10 }, /* Response is stale */ { 111, 11 }, /* Revalidation failed */ { 112, 12 }, /* Disconnected operation */ { 113, 13 }, /* Heuristic expiration */ { 199, 99 }, /* Miscellaneous warning */ { 214, 14 }, /* Transformation applied */ { 299, 99 }, /* Miscellaneous (persistent) warning */ { -1, -1 } }; for (i = 0; code_transform[i].rfc2616code >= 0; i++) { if (code_transform[i].rfc2616code == warn_code) return code_transform[i].rfc2068code; } return warn_code; /* conversion failed */ } static int pack_warning(Octstr *packed, Octstr *value) { long warn_code = -1; Octstr *warn_agent = NULL; Octstr *warn_text = NULL; long pos; long start; pos = octstr_parse_long(&warn_code, value, 0, 10); if (pos < 0 || warn_code < 0) goto error; if (warn_code > 99) { /* RFC2068 uses 2-digit codes, and RFC2616 uses 3-digit codes. * This must be an RFC2616 code. We don't have room in the * encoding for such codes, so we try to convert it back to * an RFC2068 code. */ warn_code = convert_rfc2616_warning_to_rfc2068(warn_code); } if (warn_code > MAX_SHORT_INTEGER) { warning(0, "WSP: Cannot encode warning code %ld.", warn_code); return -1; } while (isspace(octstr_get_char(value, pos))) pos++; start = pos; while (pos < octstr_len(value) && !isspace(octstr_get_char(value, pos))) pos++; if (pos > start) warn_agent = octstr_copy(value, start, pos - start); while (isspace(octstr_get_char(value, pos))) pos++; start = pos; pos += http_header_quoted_string_len(value, pos); if (pos > start) warn_text = octstr_copy(value, start, pos - start); if (warn_agent == NULL && warn_text == NULL) { /* Simple encoding */ wsp_pack_short_integer(packed, warn_code); } else { /* General encoding */ Octstr *encoding = octstr_create(""); if (warn_agent == NULL) warn_agent = octstr_create(""); if (warn_text == NULL) warn_text = octstr_create(""); wsp_pack_short_integer(encoding, warn_code); wsp_pack_text(encoding, warn_agent); wsp_pack_text(encoding, warn_text); wsp_pack_value(packed, encoding); octstr_destroy(encoding); } octstr_destroy(warn_agent); octstr_destroy(warn_text); return 0; error: warning(0, "WSP: Cannot parse 'Warning: %s'.", octstr_get_cstr(value)); octstr_destroy(warn_agent); octstr_destroy(warn_text); return -1; } void wsp_pack_separate_content_type(Octstr *packed, List *headers) { Octstr *content_type; /* Can't use http_header_get_content_type because it * does not parse all possible parameters. */ content_type = http_header_find_first(headers, "Content-Type"); if (content_type == NULL) { warning(0, "WSP: Missing Content-Type header in " "response, guessing application/octet-stream"); content_type = octstr_create("application/octet-stream"); } octstr_strip_blanks(content_type); wsp_pack_content_type(packed, content_type); octstr_destroy(content_type); } int wsp_pack_list(Octstr *packed, long fieldnum, List *elements, int i) { long startpos; Octstr *element; while ((element = gwlist_consume(elements))) { startpos = octstr_len(packed); wsp_pack_short_integer(packed, fieldnum); if (headerinfo[i].func(packed, element) < 0) { /* Remove whatever we added */ octstr_delete(packed, startpos, octstr_len(packed) - startpos); /* But continue processing elements */ } octstr_destroy(element); } return 0; } static int pack_known_header(Octstr *packed, long fieldnum, Octstr *value) { List *elements = NULL; long startpos; long i; octstr_strip_blanks(value); startpos = octstr_len(packed); for (i = 0; i < TABLE_SIZE(headerinfo); i++) { if (headerinfo[i].header == fieldnum) break; } if (i == TABLE_SIZE(headerinfo)) { error(0, "WSP: Do not know how to encode header type %ld", fieldnum); goto error; } if (headerinfo[i].allows_list == LIST) elements = http_header_split_value(value); else if (headerinfo[i].allows_list == BROKEN_LIST) elements = http_header_split_auth_value(value); else elements = NULL; if (elements != NULL) { if (wsp_pack_list(packed, fieldnum, elements, i) < 0) goto error; } else { wsp_pack_short_integer(packed, fieldnum); if (headerinfo[i].func(packed, value) < 0) goto error; } gwlist_destroy(elements, octstr_destroy_item); return 0; error: /* Remove whatever we added */ octstr_delete(packed, startpos, octstr_len(packed) - startpos); gwlist_destroy(elements, octstr_destroy_item); return -1; } int wsp_pack_application_header(Octstr *packed, Octstr *fieldname, Octstr *value) { if (!is_token(fieldname)) { warning(0, "WSP headers: `%s' is not a valid HTTP token.", octstr_get_cstr(fieldname)); return -1; } /* We have to deal specially with the X-WAP.TOD header, because it * is the only case of a text-format header defined with a non-text * field value. */ /* Normally this should be a case-insensitive comparison, but this * header will only be present if we generated it ourselves in the * application layer. */ if (octstr_str_compare(fieldname, "X-WAP.TOD") == 0) { wsp_pack_text(packed, fieldname); return wsp_pack_date(packed, value); } wsp_pack_text(packed, fieldname); wsp_pack_text(packed, value); return 0; } Octstr *wsp_headers_pack(List *headers, int separate_content_type, int wsp_version) { Octstr *packed; long i, len; int errors; packed = octstr_create(""); if (separate_content_type) wsp_pack_separate_content_type(packed, headers); len = gwlist_len(headers); for (i = 0; i < len; i++) { Octstr *fieldname; Octstr *value; long fieldnum; http_header_get(headers, i, &fieldname, &value); /* XXX we need to obey which WSP encoding-version to use */ /* fieldnum = wsp_string_to_header(fieldname); */ fieldnum = wsp_string_to_versioned_header(fieldname, wsp_version); errors = 0; if (separate_content_type && fieldnum == WSP_HEADER_CONTENT_TYPE) { /* already handled */ } else if (fieldnum < 0) { if (wsp_pack_application_header(packed, fieldname, value) < 0) errors = 1; } else { if (pack_known_header(packed, fieldnum, value) < 0) errors = 1; } if (errors) warning(0, "Skipping header: %s: %s", octstr_get_cstr(fieldname), octstr_get_cstr(value)); octstr_destroy(fieldname); octstr_destroy(value); } /* http_header_dump(headers); octstr_dump(packed, 0); */ return packed; }