static void string_decoding_in_place(dAT) { char *s1 = apr_palloc(p,4096); char *s2 = apr_palloc(p,4096); char *s3; strcpy(s1, "bend it like beckham"); strcpy(s2, "dandy %3Edons"); AT_str_eq(s1,"bend it like beckham"); apreq_unescape(s1); AT_str_eq(s1, "bend it like beckham"); s3 = apreq_escape(p, s1, 20); AT_str_eq(s3, "bend+it+like+beckham"); apreq_unescape(s3); AT_str_eq(s3,"bend it like beckham"); AT_str_eq(s2,"dandy %3Edons"); apreq_unescape(s2); AT_str_eq(s2,"dandy >dons"); s3 = apreq_escape(p, s2, 11); AT_str_eq(s3,"dandy+%3Edons"); apreq_unescape(s3); AT_str_eq(s3,"dandy >dons"); }
// Handles a simple parameter value, simply concats the name and value to the output // stream apr_status_t porter_handle_parameter(porter_upload_request_t *ur, apreq_param_t *param) { const char *new_parameter_value; apr_status_t rv; new_parameter_value = apr_pstrcat(ur->pool, apreq_escape(ur->pool, param->v.name, strlen(param->v.name)), "=", apreq_escape(ur->pool, param->v.data, strlen(param->v.data)), "&", NULL); PORTER_LOG("Writing plain parameter"); PORTER_LOG(param->v.name); PORTER_LOG(new_parameter_value); PORTER_HANDLE_ERROR(apr_brigade_write(ur->bucket_brigade, NULL, NULL, new_parameter_value, strlen(new_parameter_value))); return APR_SUCCESS; }
// Handles an upload parameter. If the user didn't select a file, it calls the // regular porter_handle_parameter function. For uploads it appends several // sub-parameters: // // * filename: File name that the user has uploaded. // * content_type: The content type the browser has provided (sometimes not present). // * path: The location of the tempfile where the contents were copied to. // * signature: A base64 encoded SHA1 hash of the filename and the PorterSharedSecret. apr_status_t porter_handle_upload(porter_upload_request_t *ur, apreq_param_t *p) { const char *content_disposition; const char *file_name; const char *content_type; const char *signature; apr_size_t size; apr_status_t rv; apr_finfo_t finfo; apr_pool_t *pool = ur->pool; char *escaped_key = apreq_escape(pool, p->v.name, strlen(p->v.name)); apr_table_t *info = p->info; PORTER_LOG("Handling Upload"); PORTER_LOG(p->v.name); porter_server_conf *config = (porter_server_conf *)ap_get_module_config(ur->raw_request->server->module_config, &porter_module); content_disposition = apr_table_get(info, "content-disposition"); apreq_header_attribute(content_disposition, "filename", 8, &file_name, &size); if (size == 0) { // There was no file, or at least it had no name, so let's // just skip it. PORTER_LOG("Appears there was no file, skipping the parameter"); return APR_SUCCESS; } PORTER_LOG("Appears there was a file, continuing"); // We know we have a file so push the param name into the array // of parameter names so it can be added to the request header. *(const char**)apr_array_push(ur->param_names) = p->v.name; PORTER_HANDLE_ERROR(porter_append_sub_parameter(pool, ur->bucket_brigade, escaped_key, "filename", file_name, size)); // content type is optional, and safari doesn't send it if it's unsure. content_type = apr_table_get(info, "content-type"); if (content_type) { PORTER_HANDLE_ERROR(porter_append_sub_parameter(pool, ur->bucket_brigade, escaped_key, "content_type", content_type, strlen(content_type))); } // Write the actual upload to disk PORTER_HANDLE_ERROR(porter_stream_file_to_disk(pool, p, &finfo, config->directory)); // Set appropriate tempfile permissions PORTER_HANDLE_ERROR(apr_file_perms_set(finfo.fname, config->permission)); PORTER_HANDLE_ERROR(porter_append_sub_parameter(pool, ur->bucket_brigade, escaped_key, "path", finfo.fname, strlen(finfo.fname))); signature = porter_sign_filename(ur, &finfo); PORTER_HANDLE_ERROR(porter_append_sub_parameter(pool, ur->bucket_brigade, escaped_key, "signature", signature, strlen(signature))); return APR_SUCCESS; }
// If 'redirect' is foo/bar, then redirect to it. If it is // foo/bar/%s, then replace the %s with r->uri. static void compose_and_set_redirect(request_rec *r, const char* redirect) { char* composed_redirect = NULL; char* encoded_uri = NULL; if (ap_strstr_c(redirect, "%s")) { encoded_uri = apreq_escape(r->pool, r->unparsed_uri, strlen(r->unparsed_uri)); composed_redirect = apr_psprintf(r->pool, redirect, r->unparsed_uri); } apr_table_setn(r->headers_out, "Location", composed_redirect ? composed_redirect : redirect); }
// Appends the sub parameter to the bucket brigade. Sub parameters are given // 'rails style' with parent_param[sub_param_name]=sub_param_value apr_status_t porter_append_sub_parameter(apr_pool_t *pool, apr_bucket_brigade *bb, const char *parent_param, const char *sub_param, const char *sub_param_value, apr_size_t length) { const char *escaped_value = apreq_escape(pool, sub_param_value, length); const char *encoded_value = apr_pstrcat(pool, parent_param, "%5B", sub_param, "%5D=", escaped_value, "&", NULL); PORTER_LOG("appending sub param"); PORTER_LOG(sub_param); PORTER_LOG(escaped_value); return apr_brigade_write(bb, NULL, NULL, encoded_value, strlen(encoded_value)); }
// See here for the structure of request_rec: // http://ci.apache.org/projects/httpd/trunk/doxygen/structrequest__rec.html static int hook(request_rec *r) { settings_rec *cfg = ap_get_module_config( r->per_dir_config, &querystring2cookie_module ); /* Do not run in subrequests, don't run if not enabled */ if( !(cfg->enabled || r->main) ) { return DECLINED; } /* No query string? nothing to do here */ if( !(r->args) || strlen( r->args ) < 1 ) { return DECLINED; } /* skip if dnt headers are present? */ if( !(cfg->enabled_if_dnt) && apr_table_get( r->headers_in, "DNT" ) ) { _DEBUG && fprintf( stderr, "DNT header sent: declined\n" ); return DECLINED; } _DEBUG && fprintf( stderr, "Query string: '%s'\n", r->args ); // *********************************** // Calculate expiry time // *********************************** // The expiry time. We can't use max-age because IE6 - IE8 do not // support it :( char *expires = ""; if( cfg->cookie_expires > 0 ) { apr_time_exp_t tms; apr_time_exp_gmt( &tms, r->request_time + apr_time_from_sec( cfg->cookie_expires ) ); expires = apr_psprintf( r->pool, "expires=%s, %.2d-%s-%.2d %.2d:%.2d:%.2d GMT", apr_day_snames[tms.tm_wday], tms.tm_mday, apr_month_snames[tms.tm_mon], tms.tm_year % 100, tms.tm_hour, tms.tm_min, tms.tm_sec ); } // *********************************** // Find key/value pairs // *********************************** // keep track of how much data we've been writing - there's a limit to how // much a browser will store per domain (usually 4k) so we want to make sure // it's not getting flooded. int total_pair_size = 0; // This holds the final cookie we'll send back - make sure to initialize // or it can point at garbage! char *cookie = ""; // string to use as the cookie name (together with the prefix) - make sure to // initialize or it can point at garbage! char *cookie_name = ""; // Iterate over the key/value pairs char *last_pair; char *pair = apr_strtok( apr_pstrdup( r->pool, r->args ), "&", &last_pair ); _DEBUG && fprintf( stderr, "about to parse query string for pairs\n" ); _DEBUG && fprintf( stderr, "looking for cookie name in %s\n", cfg->cookie_name_from ); while( pair != NULL ) { // length of the substr before the = sign (or index of the = sign) int contains_equals_at = strcspn( pair, "=" ); // Does not contains a =, or starts with a =, meaning it's garbage if( !strstr(pair, "=") || contains_equals_at < 1 ) { _DEBUG && fprintf( stderr, "invalid pair: %s\n", pair ); // And get the next pair -- has to be done at every break pair = apr_strtok( NULL, "&", &last_pair ); continue; } _DEBUG && fprintf( stderr, "pair looks valid: %s - = sign at pos: %i\n", pair, contains_equals_at ); // So this IS a key value pair. Let's get the key and the value. // first, get the key - everything up to the first = char *key = apr_pstrndup( r->pool, pair, contains_equals_at ); // now get the value, everything AFTER the = sign. We do that by // moving the pointer past the = sign. char *value = apr_pstrdup( r->pool, pair ); value += contains_equals_at + 1; _DEBUG && fprintf( stderr, "pair=%s, key=%s, value=%s\n", pair, key, value ); // you want us to use a name from the query string? // This might be that name. if( cfg->cookie_name_from && !(strlen(cookie_name)) && strcasecmp( key, cfg->cookie_name_from ) == 0 ) { // get everything after the = sign -- that's our name. cookie_name = apr_pstrcat( r->pool, cfg->cookie_prefix, value, NULL ); _DEBUG && fprintf( stderr, "using %s as the cookie name\n", cookie_name ); // And get the next pair -- has to be done at every break pair = apr_strtok( NULL, "&", &last_pair ); continue; // may be on the ignore list } else { // may have to continue the outer loop, use this as a marker int do_continue = 0; // you might have blacklisted this key; let's check // Following tutorial code here again: // http://dev.ariel-networks.com/apr/apr-tutorial/html/apr-tutorial-19.html int i; for( i = 0; i < cfg->qs_ignore->nelts; i++ ) { char *ignore = ((char **)cfg->qs_ignore->elts)[i]; _DEBUG && fprintf( stderr, "processing ignore %s against pair %s\n", ignore, pair ); // it's indeed on the ignore list; move on // do this by comparing the string length first - if the length of // the ignore key and the key are identical AND the first N characters // of the string are the same if( strcasecmp( key, ignore ) == 0 ) { _DEBUG && fprintf( stderr, "pair %s is on the ignore list: %s\n", pair, ignore ); // signal to continue the outer loop; we found an ignore match do_continue = 1; break; } } // ignore match found, move on if( do_continue ) { // And get the next pair -- has to be done at every break pair = apr_strtok( NULL, "&", &last_pair ); continue; } } // looks like a valid key=value declaration _DEBUG && fprintf( stderr, "valid key/value pair: %s\n", pair ); // Now, the key may contain URL unsafe characters, which are also // not allowed in Cookies. See here: // http://tools.ietf.org/html/rfc2068, section 2.2 on 'tspecials' // // So instead, we url encode the key. The size of the key is max // 3 times old key size (any char gets encoded into %xx), so allow // for that space. See the documentation here: // http://httpd.apache.org/apreq/docs/libapreq2/apreq__util_8h.html#785be2ceae273b0a7b2ffda223b2ebae char *escaped_key = apreq_escape( r->pool, key, strlen(key) ); char *escaped_value = apreq_escape( r->pool, value, strlen(value) ); _DEBUG && fprintf( stderr, "Original key: %s - Escaped key: %s\n", key, escaped_key ); _DEBUG && fprintf( stderr, "Original value: %s - Escaped value: %s\n", value, escaped_value ); // Now, let's do some transposing: The '=' sign needs to be replaced // with whatever the separator is. It can't be a '=' sign, as that's // illegal in cookies. The string may be larger than a single char, // so split the string and do the magix. // This makes key[delim]value - redefining pair here is safe, we're // just using it for printing now. char *key_value = apr_pstrcat( r->pool, escaped_key, cfg->cookie_key_value_delimiter, escaped_value, NULL ); int this_pair_size = strlen( key_value ); // Make sure the individual pair, as well as the whole thing doesn't // get too long _DEBUG && fprintf( stderr, "this pair size: %i, total pair size: %i, max size: %i\n", this_pair_size, total_pair_size, cfg->cookie_max_size ); if( (this_pair_size <= cfg->cookie_max_size) && (total_pair_size + this_pair_size <= cfg->cookie_max_size) ) { cookie = apr_pstrcat( r->pool, cookie, // the cookie so far // If we already have pairs in here, we need the // delimiter, otherwise we don't. (strlen(cookie) ? cfg->cookie_pair_delimiter : ""), key_value, // the next pair. NULL ); // update the book keeping - this is the new size including delims total_pair_size = strlen(cookie); _DEBUG && fprintf( stderr, "this pair size: %i, total pair size: %i\n", this_pair_size, total_pair_size ); } else { _DEBUG && fprintf( stderr, "Pair size too long to add: %s (this: %i total: %i max: %i)\n", key_value, this_pair_size, total_pair_size, cfg->cookie_max_size ); } // and move the pointer pair = apr_strtok( NULL, "&", &last_pair ); } // So you told us we should use a cookie name from the query string, // but we never found it in there. That's a problem. if( cfg->cookie_name_from && !strlen(cookie_name) ) { // r->err_headers_out also honors non-2xx responses and // internal redirects. See the patch here: // http://svn.apache.org/viewvc?view=revision&revision=1154620 apr_table_addn( r->err_headers_out, "X-QS2Cookie", apr_pstrcat( r->pool, "ERROR: Did not detect cookie name - missing QS argument: ", cfg->cookie_name_from, NULL ) ); // Let's return the output } else { // we got here without a cookie name? We can use the default. if( !strlen(cookie_name) ) { _DEBUG && fprintf( stderr, "explicitly setting cookie name to: %s\n", cfg->cookie_name ); cookie_name = apr_pstrcat( r->pool, cfg->cookie_prefix, cfg->cookie_name, NULL ); } _DEBUG && fprintf( stderr, "cookie name: %s\n", cookie_name ); // XXX use a sprintf format for more flexibility? if( cfg->encode_in_key ) { _DEBUG && fprintf( stderr, "%s: encoding in the key\n", cookie_name ); cookie = apr_pstrcat( r->pool, // cookie data cookie_name, cfg->cookie_pair_delimiter, cookie, "=", // The format is different on 32 (%ld) vs 64bit (%lld), so // use the constant for it instead. You can find this in apr.h apr_psprintf( r->pool, "%" APR_OFF_T_FMT, apr_time_sec(apr_time_now()) ), NULL ); } else { _DEBUG && fprintf( stderr, "%s: encoding in the value\n", cookie_name ); cookie = apr_pstrcat( r->pool, cookie_name, "=", cookie, NULL ); } // And now add the meta data to the cookie cookie = apr_pstrcat( r->pool, cookie, "; ", "path=/; ", cfg->cookie_domain, expires, NULL ); _DEBUG && fprintf( stderr, "cookie: %s\n", cookie ); // r->err_headers_out also honors non-2xx responses and // internal redirects. See the patch here: // http://svn.apache.org/viewvc?view=revision&revision=1154620 apr_table_addn( r->err_headers_out, "Set-Cookie", cookie ); } return OK; }