| /* |
| * Copyright 2008 Hans Leidekker for CodeWeavers |
| * |
| * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
| */ |
| |
| #include "config.h" |
| #include "wine/port.h" |
| #include "wine/debug.h" |
| |
| #include <stdarg.h> |
| |
| #include "windef.h" |
| #include "winbase.h" |
| #include "winnls.h" |
| #include "winhttp.h" |
| |
| #include "winhttp_private.h" |
| |
| WINE_DEFAULT_DEBUG_CHANNEL(winhttp); |
| |
| static void free_header( header_t *header ) |
| { |
| heap_free( header->field ); |
| heap_free( header->value ); |
| heap_free( header ); |
| } |
| |
| static BOOL valid_token_char( WCHAR c ) |
| { |
| if (c < 32 || c == 127) return FALSE; |
| switch (c) |
| { |
| case '(': case ')': |
| case '<': case '>': |
| case '@': case ',': |
| case ';': case ':': |
| case '\\': case '\"': |
| case '/': case '[': |
| case ']': case '?': |
| case '=': case '{': |
| case '}': case ' ': |
| case '\t': |
| return FALSE; |
| default: |
| return TRUE; |
| } |
| } |
| |
| static header_t *parse_header( LPCWSTR string ) |
| { |
| const WCHAR *p, *q; |
| header_t *header; |
| int len; |
| |
| p = string; |
| if (!(q = strchrW( p, ':' ))) |
| { |
| WARN("no ':' in line %s\n", debugstr_w(string)); |
| return NULL; |
| } |
| if (q == string) |
| { |
| WARN("empty field name in line %s\n", debugstr_w(string)); |
| return NULL; |
| } |
| while (*p != ':') |
| { |
| if (!valid_token_char( *p )) |
| { |
| WARN("invalid character in field name %s\n", debugstr_w(string)); |
| return NULL; |
| } |
| p++; |
| } |
| len = q - string; |
| if (!(header = heap_alloc_zero( sizeof(header_t) ))) return NULL; |
| if (!(header->field = heap_alloc( (len + 1) * sizeof(WCHAR) ))) |
| { |
| heap_free( header ); |
| return NULL; |
| } |
| memcpy( header->field, string, len * sizeof(WCHAR) ); |
| header->field[len] = 0; |
| |
| q++; /* skip past colon */ |
| while (*q == ' ') q++; |
| if (!*q) |
| { |
| WARN("no value in line %s\n", debugstr_w(string)); |
| return header; |
| } |
| len = strlenW( q ); |
| if (!(header->value = heap_alloc( (len + 1) * sizeof(WCHAR) ))) |
| { |
| free_header( header ); |
| return NULL; |
| } |
| memcpy( header->value, q, len * sizeof(WCHAR) ); |
| header->value[len] = 0; |
| |
| return header; |
| } |
| |
| static int get_header_index( request_t *request, LPCWSTR field, int requested_index, BOOL request_only ) |
| { |
| int index; |
| |
| TRACE("%s\n", debugstr_w(field)); |
| |
| for (index = 0; index < request->num_headers; index++) |
| { |
| if (strcmpiW( request->headers[index].field, field )) continue; |
| if (request_only && !request->headers[index].is_request) continue; |
| if (!request_only && request->headers[index].is_request) continue; |
| |
| if (!requested_index) break; |
| requested_index--; |
| } |
| if (index >= request->num_headers) index = -1; |
| TRACE("returning %d\n", index); |
| return index; |
| } |
| |
| static BOOL insert_header( request_t *request, header_t *header ) |
| { |
| DWORD count; |
| header_t *hdrs; |
| |
| TRACE("inserting %s: %s\n", debugstr_w(header->field), debugstr_w(header->value)); |
| |
| count = request->num_headers + 1; |
| if (count > 1) |
| hdrs = heap_realloc_zero( request->headers, sizeof(header_t) * count ); |
| else |
| hdrs = heap_alloc_zero( sizeof(header_t) * count ); |
| |
| if (hdrs) |
| { |
| request->headers = hdrs; |
| request->headers[count - 1].field = strdupW( header->field ); |
| request->headers[count - 1].value = strdupW( header->value ); |
| request->headers[count - 1].is_request = header->is_request; |
| request->num_headers++; |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| static BOOL delete_header( request_t *request, DWORD index ) |
| { |
| if (!request->num_headers) return FALSE; |
| if (index >= request->num_headers) return FALSE; |
| request->num_headers--; |
| |
| heap_free( request->headers[index].field ); |
| heap_free( request->headers[index].value ); |
| |
| memmove( &request->headers[index], &request->headers[index + 1], (request->num_headers - index) * sizeof(header_t) ); |
| memset( &request->headers[request->num_headers], 0, sizeof(header_t) ); |
| return TRUE; |
| } |
| |
| static BOOL process_header( request_t *request, LPCWSTR field, LPCWSTR value, DWORD flags, BOOL request_only ) |
| { |
| int index; |
| header_t *header; |
| |
| TRACE("%s: %s 0x%08x\n", debugstr_w(field), debugstr_w(value), flags); |
| |
| /* replace wins out over add */ |
| if (flags & WINHTTP_ADDREQ_FLAG_REPLACE) flags &= ~WINHTTP_ADDREQ_FLAG_ADD; |
| |
| if (flags & WINHTTP_ADDREQ_FLAG_ADD) index = -1; |
| else |
| index = get_header_index( request, field, 0, request_only ); |
| |
| if (index >= 0) |
| { |
| if (flags & WINHTTP_ADDREQ_FLAG_ADD_IF_NEW) return FALSE; |
| header = &request->headers[index]; |
| } |
| else if (value) |
| { |
| header_t hdr; |
| |
| hdr.field = (LPWSTR)field; |
| hdr.value = (LPWSTR)value; |
| hdr.is_request = request_only; |
| |
| return insert_header( request, &hdr ); |
| } |
| /* no value to delete */ |
| else return TRUE; |
| |
| if (flags & WINHTTP_ADDREQ_FLAG_REPLACE) |
| { |
| delete_header( request, index ); |
| if (value) |
| { |
| header_t hdr; |
| |
| hdr.field = (LPWSTR)field; |
| hdr.value = (LPWSTR)value; |
| hdr.is_request = request_only; |
| |
| return insert_header( request, &hdr ); |
| } |
| return TRUE; |
| } |
| else if (flags & (WINHTTP_ADDREQ_FLAG_COALESCE_WITH_COMMA | WINHTTP_ADDREQ_FLAG_COALESCE_WITH_SEMICOLON)) |
| { |
| WCHAR sep, *tmp; |
| int len, orig_len, value_len; |
| |
| orig_len = strlenW( header->value ); |
| value_len = strlenW( value ); |
| |
| if (flags & WINHTTP_ADDREQ_FLAG_COALESCE_WITH_COMMA) sep = ','; |
| else sep = ';'; |
| |
| len = orig_len + value_len + 2; |
| if ((tmp = heap_realloc( header->value, (len + 1) * sizeof(WCHAR) ))) |
| { |
| header->value = tmp; |
| |
| header->value[orig_len] = sep; |
| orig_len++; |
| header->value[orig_len] = ' '; |
| orig_len++; |
| |
| memcpy( &header->value[orig_len], value, value_len * sizeof(WCHAR) ); |
| header->value[len] = 0; |
| return TRUE; |
| } |
| } |
| return TRUE; |
| } |
| |
| static BOOL add_request_headers( request_t *request, LPCWSTR headers, DWORD len, DWORD flags ) |
| { |
| BOOL ret = FALSE; |
| WCHAR *buffer, *p, *q; |
| header_t *header; |
| |
| if (len == ~0UL) len = strlenW( headers ); |
| if (!(buffer = heap_alloc( (len + 1) * sizeof(WCHAR) ))) return FALSE; |
| strcpyW( buffer, headers ); |
| |
| p = buffer; |
| do |
| { |
| q = p; |
| while (*q) |
| { |
| if (q[0] == '\r' && q[1] == '\n') break; |
| q++; |
| } |
| if (!*p) break; |
| if (*q == '\r') |
| { |
| *q = 0; |
| q += 2; /* jump over \r\n */ |
| } |
| if ((header = parse_header( p ))) |
| { |
| ret = process_header( request, header->field, header->value, flags, TRUE ); |
| free_header( header ); |
| } |
| p = q; |
| } while (ret); |
| |
| heap_free( buffer ); |
| return ret; |
| } |
| |
| /*********************************************************************** |
| * WinHttpAddRequestHeaders (winhttp.@) |
| */ |
| BOOL WINAPI WinHttpAddRequestHeaders( HINTERNET hrequest, LPCWSTR headers, DWORD len, DWORD flags ) |
| { |
| BOOL ret; |
| request_t *request; |
| |
| TRACE("%p, %s, 0x%x, 0x%08x\n", hrequest, debugstr_w(headers), len, flags); |
| |
| if (!headers) |
| { |
| set_last_error( ERROR_INVALID_PARAMETER ); |
| return FALSE; |
| } |
| if (!(request = (request_t *)grab_object( hrequest ))) |
| { |
| set_last_error( ERROR_INVALID_HANDLE ); |
| return FALSE; |
| } |
| if (request->hdr.type != WINHTTP_HANDLE_TYPE_REQUEST) |
| { |
| release_object( &request->hdr ); |
| set_last_error( ERROR_WINHTTP_INCORRECT_HANDLE_TYPE ); |
| return FALSE; |
| } |
| |
| ret = add_request_headers( request, headers, len, flags ); |
| |
| release_object( &request->hdr ); |
| return ret; |
| } |
| |
| static WCHAR *build_request_string( request_t *request, LPCWSTR verb, LPCWSTR path, LPCWSTR version ) |
| { |
| static const WCHAR space[] = {' ',0}; |
| static const WCHAR crlf[] = {'\r','\n',0}; |
| static const WCHAR colon[] = {':',' ',0}; |
| static const WCHAR twocrlf[] = {'\r','\n','\r','\n',0}; |
| |
| WCHAR *ret; |
| const WCHAR **headers, **p; |
| unsigned int len, i = 0, j; |
| |
| /* allocate space for an array of all the string pointers to be added */ |
| len = request->num_headers * 4 + 7; |
| if (!(headers = heap_alloc( len * sizeof(LPCWSTR) ))) return NULL; |
| |
| headers[i++] = verb; |
| headers[i++] = space; |
| headers[i++] = path; |
| headers[i++] = space; |
| headers[i++] = version; |
| |
| for (j = 0; j < request->num_headers; j++) |
| { |
| if (request->headers[j].is_request) |
| { |
| headers[i++] = crlf; |
| headers[i++] = request->headers[j].field; |
| headers[i++] = colon; |
| headers[i++] = request->headers[j].value; |
| |
| TRACE("adding header %s (%s)\n", debugstr_w(request->headers[j].field), |
| debugstr_w(request->headers[j].value)); |
| } |
| } |
| headers[i++] = twocrlf; |
| headers[i] = NULL; |
| |
| len = 0; |
| for (p = headers; *p; p++) len += strlenW( *p ); |
| len++; |
| |
| if (!(ret = heap_alloc( len * sizeof(WCHAR) ))) |
| { |
| heap_free( headers ); |
| return NULL; |
| } |
| *ret = 0; |
| for (p = headers; *p; p++) strcatW( ret, *p ); |
| |
| heap_free( headers ); |
| return ret; |
| } |
| |
| #define QUERY_MODIFIER_MASK (WINHTTP_QUERY_FLAG_REQUEST_HEADERS | WINHTTP_QUERY_FLAG_SYSTEMTIME | WINHTTP_QUERY_FLAG_NUMBER) |
| |
| static BOOL query_headers( request_t *request, DWORD level, LPCWSTR name, LPVOID buffer, LPDWORD buflen, LPDWORD index ) |
| { |
| header_t *header = NULL; |
| BOOL request_only, ret = FALSE; |
| int requested_index, header_index = -1; |
| DWORD attribute; |
| |
| request_only = level & WINHTTP_QUERY_FLAG_REQUEST_HEADERS; |
| requested_index = index ? *index : 0; |
| |
| attribute = level & ~QUERY_MODIFIER_MASK; |
| switch (attribute) |
| { |
| case WINHTTP_QUERY_CUSTOM: |
| { |
| header_index = get_header_index( request, name, requested_index, request_only ); |
| break; |
| } |
| case WINHTTP_QUERY_RAW_HEADERS_CRLF: |
| { |
| WCHAR *headers; |
| DWORD len; |
| |
| if (request_only) |
| headers = build_request_string( request, request->verb, request->path, request->version ); |
| else |
| headers = request->raw_headers; |
| |
| len = strlenW( headers ) * sizeof(WCHAR); |
| if (len + sizeof(WCHAR) > *buflen) |
| { |
| len += sizeof(WCHAR); |
| set_last_error( ERROR_INSUFFICIENT_BUFFER ); |
| } |
| else if (buffer) |
| { |
| memcpy( buffer, headers, len + sizeof(WCHAR) ); |
| TRACE("returning data: %s\n", debugstr_wn(buffer, len / sizeof(WCHAR))); |
| ret = TRUE; |
| } |
| *buflen = len; |
| if (request_only) heap_free( headers ); |
| return ret; |
| } |
| default: |
| { |
| FIXME("attribute %u not implemented\n", attribute); |
| return FALSE; |
| } |
| } |
| |
| if (header_index >= 0) |
| { |
| header = &request->headers[header_index]; |
| } |
| if (!header || (request_only && !header->is_request)) |
| { |
| set_last_error( ERROR_WINHTTP_HEADER_NOT_FOUND ); |
| return FALSE; |
| } |
| if (index) *index += 1; |
| if (level & WINHTTP_QUERY_FLAG_NUMBER) |
| { |
| int *number = buffer; |
| if (sizeof(int) > *buflen) |
| { |
| set_last_error( ERROR_INSUFFICIENT_BUFFER ); |
| } |
| else if (number) |
| { |
| *number = atoiW( header->value ); |
| TRACE("returning number: %d\n", *number); |
| ret = TRUE; |
| } |
| *buflen = sizeof(int); |
| } |
| else if (level & WINHTTP_QUERY_FLAG_SYSTEMTIME) |
| { |
| SYSTEMTIME *st = buffer; |
| if (sizeof(SYSTEMTIME) > *buflen) |
| { |
| set_last_error( ERROR_INSUFFICIENT_BUFFER ); |
| } |
| else if (st && (ret = WinHttpTimeToSystemTime( header->value, st ))) |
| { |
| TRACE("returning time: %04d/%02d/%02d - %d - %02d:%02d:%02d.%02d\n", |
| st->wYear, st->wMonth, st->wDay, st->wDayOfWeek, |
| st->wHour, st->wMinute, st->wSecond, st->wMilliseconds); |
| } |
| *buflen = sizeof(SYSTEMTIME); |
| } |
| else if (header->value) |
| { |
| WCHAR *string = buffer; |
| DWORD len = (strlenW( header->value ) + 1) * sizeof(WCHAR); |
| if (len > *buflen) |
| { |
| set_last_error( ERROR_INSUFFICIENT_BUFFER ); |
| *buflen = len; |
| return FALSE; |
| } |
| else if (string) |
| { |
| strcpyW( string, header->value ); |
| TRACE("returning string: %s\n", debugstr_w(string)); |
| ret = TRUE; |
| } |
| *buflen = len - sizeof(WCHAR); |
| } |
| return ret; |
| } |
| |
| /*********************************************************************** |
| * WinHttpQueryHeaders (winhttp.@) |
| */ |
| BOOL WINAPI WinHttpQueryHeaders( HINTERNET hrequest, DWORD level, LPCWSTR name, LPVOID buffer, LPDWORD buflen, LPDWORD index ) |
| { |
| BOOL ret; |
| request_t *request; |
| |
| TRACE("%p, 0x%08x, %s, %p, %p, %p\n", hrequest, level, debugstr_w(name), buffer, buflen, index); |
| |
| if (!(request = (request_t *)grab_object( hrequest ))) |
| { |
| set_last_error( ERROR_INVALID_HANDLE ); |
| return FALSE; |
| } |
| if (request->hdr.type != WINHTTP_HANDLE_TYPE_REQUEST) |
| { |
| release_object( &request->hdr ); |
| set_last_error( ERROR_WINHTTP_INCORRECT_HANDLE_TYPE ); |
| return FALSE; |
| } |
| |
| ret = query_headers( request, level, name, buffer, buflen, index ); |
| |
| release_object( &request->hdr ); |
| return ret; |
| } |