--- parser3/src/main/pa_http.C 2010/10/16 22:24:20 1.48 +++ parser3/src/main/pa_http.C 2013/07/23 09:46:58 1.59 @@ -1,12 +1,10 @@ /** @file Parser: http support functions. - Copyright(c) 2001-2009 ArtLebedev Group (http://www.artlebedev.com) + Copyright (c) 2001-2012 Art. Lebedev Studio (http://www.artlebedev.com) Author: Alexandr Petrosian (http://paf.design.ru) */ -static const char * const IDENT_HTTP_C="$Date: 2010/10/16 22:24:20 $"; - #include "pa_http.h" #include "pa_common.h" #include "pa_charsets.h" @@ -15,6 +13,14 @@ static const char * const IDENT_HTTP_C=" #include "pa_vfile.h" #include "pa_random.h" +volatile const char * IDENT_PA_HTTP_C="$Id: pa_http.C,v 1.59 2013/07/23 09:46:58 moko Exp $" IDENT_PA_HTTP_H; + +#ifdef _MSC_VER +#include +#else +#define closesocket close +#endif + // defines #define HTTP_METHOD_NAME "method" @@ -22,11 +28,9 @@ static const char * const IDENT_HTTP_C=" #define HTTP_BODY_NAME "body" #define HTTP_TIMEOUT_NAME "timeout" #define HTTP_HEADERS_NAME "headers" -#define HTTP_COOKIES_NAME "cookies" #define HTTP_FORM_ENCTYPE_NAME "enctype" #define HTTP_ANY_STATUS_NAME "any-status" -#define HTTP_OMIT_POST_CHARSET_NAME "omit-post-charset" // ^file::load[...;http://...;$.form[...]$.method[post]] - // by default add charset to content-type +#define HTTP_OMIT_POST_CHARSET_NAME "omit-post-charset" // ^file::load[...;http://...;$.method[post]] by default adds charset to content-type #define HTTP_TABLES_NAME "tables" @@ -35,13 +39,30 @@ static const char * const IDENT_HTTP_C=" #define DEFAULT_USER_AGENT "parser3" -# ifndef INADDR_NONE -# define INADDR_NONE ((ulong) -1) -# endif +#ifndef INADDR_NONE +#define INADDR_NONE ((ulong) -1) +#endif #undef CRLF #define CRLF "\r\n" +// helpers + +class Cookies_table_template_columns: public ArrayString { +public: + Cookies_table_template_columns() { + *this+=new String("name"); + *this+=new String("value"); + *this+=new String("expires"); + *this+=new String("max-age"); + *this+=new String("domain"); + *this+=new String("path"); + *this+=new String("httponly"); + *this+=new String("secure"); + } +}; + + static bool set_addr(struct sockaddr_in *addr, const char* host, const short port){ memset(addr, 0, sizeof(*addr)); addr->sin_family=AF_INET; @@ -295,6 +316,19 @@ struct Http_pass_header_info { bool* content_type_url_encoded; }; #endif + +char *pa_http_safe_header_name(const char *name) { + char *result=pa_strdup(name); + char *n=result; + if(!pa_isalpha((unsigned char)*n)) + *n++ = '_'; + for(; *n; ++n) { + if (!pa_isalnum((unsigned char)*n) && *n != '-' && *n != '_') + *n = '_'; + } + return result; +} + static void http_pass_header(HashStringValue::key_type aname, HashStringValue::value_type avalue, Http_pass_header_info *info) { @@ -304,8 +338,8 @@ static void http_pass_header(HashStringV if(strcasecmp(name_cstr, HTTP_CONTENT_LENGTH)==0) return; - String name=String(capitalize(name_cstr), String::L_URI); - String value=attributed_meaning_to_string(*avalue, String::L_URI, true); + String name=String(pa_http_safe_header_name(capitalize(name_cstr)), String::L_AS_IS); + String value=attributed_meaning_to_string(*avalue, String::L_HTTP_HEADER, true); *info->request << name << ": " << value << CRLF; @@ -512,6 +546,82 @@ static void find_headers_end(char* p, headers_end_at=0; } +// Set-Cookie: name=value; Domain=docs.foo.com; Path=/accounts; Expires=Wed, 13-Jan-2021 22:23:01 GMT; Secure; HttpOnly +static ArrayString* parse_cookie(Request& r, const String& cookie) { + char *current=strdup(cookie.cstr()); + + const String* name=0; + const String* value=&String::Empty; + const String* expires=&String::Empty; + const String* max_age=&String::Empty; + const String* path=&String::Empty; + const String* domain=&String::Empty; + const String* httponly=&String::Empty; + const String* secure=&String::Empty; + + bool first_pair=true; + + do { + if(char *meaning=search_stop(current, ';')) + if(char *attribute=search_stop(meaning, '=')) { + const String* sname=new String(unescape_chars(attribute, strlen(attribute), &r.charsets.source(), true/*don't convert '"' to space*/), String::L_TAINTED); + const String* smeaning=0; + if(meaning) + smeaning=new String(unescape_chars(meaning, strlen(meaning), &r.charsets.source(), true/*don't convert '"' to space*/), String::L_TAINTED); + + if(first_pair) { + // name + value + name=sname; + value=smeaning; + first_pair=false; + } else { + const String& slower=sname->change_case(r.charsets.source(), String::CC_LOWER); + + if(slower == "expires") + expires=smeaning; + else if(slower == "max-age") + max_age=smeaning; + else if(slower == "domain") + domain=smeaning; + else if(slower == "path") + path=smeaning; + else if(slower == "httponly") + httponly=new String("1", String::L_CLEAN); + else if(slower == "secure") + secure=new String("1", String::L_CLEAN); + else { + // todo@ ? + } + } + } + } while(current); + + if(!name) + return 0; + + ArrayString* result=new ArrayString(8); + *result+=name; + *result+=value; + *result+=expires; + *result+=max_age; + *result+=domain; + *result+=path; + *result+=httponly; + *result+=secure; + + return result; +} + +Table* parse_cookies(Request& r, Table *cookies){ + Table& result=*new Table(new Cookies_table_template_columns); + + for(Array_iterator i(*cookies); i.has_next(); ) + if(ArrayString* row=parse_cookie(r, *i.next()->get(0))) + result+=row; + + return &result; +} + /// @todo build .cookies field. use ^file.tables.SET-COOKIES.menu{ for now File_read_http_result pa_internal_file_read_http(Request& r, const String& file_spec, @@ -521,7 +631,7 @@ File_read_http_result pa_internal_file_r File_read_http_result result; char host[MAX_STRING]; const char* uri; - short port; + short port=80; const char* method="GET"; bool method_is_get=true; HashStringValue* form=0; @@ -532,6 +642,7 @@ File_read_http_result pa_internal_file_r Value* vcookies=0; Value* vbody=0; Charset *asked_remote_charset=0; + Charset* real_remote_charset=0; const char* user_cstr=0; const char* password_cstr=0; const char* encode=0; @@ -575,8 +686,10 @@ File_read_http_result pa_internal_file_r omit_post_charset=vomit_post_charset->as_bool(); } if(Value* vcharset_name=options->get(PA_CHARSET_NAME)) { - asked_remote_charset=&charsets.get(vcharset_name->as_string(). - change_case(r.charsets.source(), String::CC_UPPER)); + asked_remote_charset=&charsets.get(vcharset_name->as_string().change_case(r.charsets.source(), String::CC_UPPER)); + } + if(Value* vresponse_charset_name=options->get(PA_RESPONSE_CHARSET_NAME)) { + real_remote_charset=&charsets.get(vresponse_charset_name->as_string().change_case(r.charsets.source(), String::CC_UPPER)); } if(Value* vuser=options->get(HTTP_USER)) { valid_options++; @@ -641,8 +754,13 @@ File_read_http_result pa_internal_file_r char* host_uri=lsplit(host, '/'); uri=host_uri?current+(host_uri-1-host):"/"; char* port_cstr=lsplit(host, ':'); - char* error_pos=0; - port=port_cstr?(short)strtol(port_cstr, &error_pos, 0):80; + + if (port_cstr){ + char* error_pos=0; + port=(short)strtol(port_cstr, &error_pos, 10); + if(port==0 || *error_pos) + throw Exception(PARSER_RUNTIME, &connect_string, "invalid port number '%s'", port_cstr); + } // making request head String head; @@ -650,7 +768,10 @@ File_read_http_result pa_internal_file_r if(method_is_get && form) head << (strchr(uri, '?')!=0?"&":"?") << pa_form2string(*form, r.charsets); - head <<" HTTP/1.0" CRLF "Host: "<< host << CRLF; + head <<" HTTP/1.0" CRLF "Host: "<< host; + if (port != 80) + head << ":" << port_cstr; + head << CRLF; char* boundary=0; @@ -782,7 +903,6 @@ File_read_http_result pa_internal_file_r result.headers=new HashStringValue; VHash* vtables=new VHash; result.headers->put(HTTP_TABLES_NAME, vtables); - Charset* real_remote_charset=0; // undetected, yet if(headers_end_at) { *headers_end_at=0; @@ -805,7 +925,7 @@ File_read_http_result pa_internal_file_r "bad response from host - bad header \"%s\"", line.cstr()); const String::Body HEADER_NAME=line.mid(0, pos).change_case(r.charsets.source(), String::CC_UPPER); const String& HEADER_VALUE=line.mid(pos+1, line.length()).trim(String::TRIM_BOTH, " \t\r"); - if(as_text && HEADER_NAME==HTTP_CONTENT_TYPE_UPPER) + if(as_text && HEADER_NAME==HTTP_CONTENT_TYPE_UPPER && !real_remote_charset) real_remote_charset=detect_charset(HEADER_VALUE.cstr()); // tables @@ -833,6 +953,10 @@ File_read_http_result pa_internal_file_r result.headers->put(HEADER_NAME, new VString(HEADER_VALUE)); } + + // filling $.cookies + if(Value *vcookies=(Value *)tables.get("SET-COOKIE")) + result.headers->put(HTTP_COOKIES_NAME, new VTable(parse_cookies(r, vcookies->get_table()))); } if(as_text && raw_body_size>=3 && strncmp(raw_body, "\xEF\xBB\xBF", 3)==0){