static ngx_int_t
ngx_http_google_request_generater(ngx_http_request_t    * r,
                                  ngx_http_google_ctx_t * ctx)
{
  ngx_uint_t i;
  ngx_table_elt_t *  tb;
  ngx_table_elt_t ** ptr;
  
  ngx_str_t * cookie = ngx_http_google_implode_kv(r, ctx->cookies, "; ");
  if (!cookie) return NGX_ERROR;
  
  if (!r->headers_in.cookies.nelts)
  {
    tb = ngx_list_push(&r->headers_in.headers);
    if (!tb) return NGX_ERROR;
    
    ngx_str_set(&tb->key, "Cookie");
    tb->value = *cookie;
    tb->hash  = ngx_hash_key_lc(tb->key.data, tb->key.len);
    
  } else {
    
    ptr = r->headers_in.cookies.elts;
    for (i = 0; i < r->headers_in.cookies.nelts; i++) {
      ptr[i]->value = *cookie;
    }
    
  }
  
  ngx_str_t * arg = ngx_http_google_implode_kv(r, ctx->args, "&");
  if (!arg) return NGX_ERROR;
  ctx->arg = arg;
  
  if (!ctx->arg->len)
  {
    r->unparsed_uri = *ctx->uri;
    
  } else {
    
    ngx_str_t uri;
    uri.len  = ctx->uri->len + 1 + ctx->arg->len;
    uri.data = ngx_pcalloc(r->pool, uri.len);
    
    if (!uri.data) return NGX_ERROR;
    ngx_snprintf(uri.data, uri.len, "%V?%V", ctx->uri, ctx->arg);
    
    r->unparsed_uri = uri;
  }
  
  return NGX_OK;
}
static ngx_int_t
ngx_http_google_response_header_set_cookie(ngx_http_request_t    * r,
                                           ngx_http_google_ctx_t * ctx,
                                           ngx_table_elt_t       * tb)
{
  ngx_array_t * kvs = ngx_http_google_explode_kv(r, &tb->value, ";");
  if (!kvs) return NGX_ERROR;
  
  ngx_uint_t i;
  ngx_keyval_t * kv, * hd = kvs->elts;
  
  for (i = 0; i < kvs->nelts; i++)
  {
    kv = hd + i;
    
    if (!ngx_strncasecmp(kv->key.data, ctx->conf->data, ctx->conf->len))
    {
      if (ngx_http_google_response_header_set_cookie_conf(r, ctx, &kv->value)) {
        return NGX_ERROR;
      }
    }
    
    if (!ngx_strncasecmp(kv->key.data, (u_char *)"GOOGLE_ABUSE_EXEMPTION", 22))
    {
      if (ngx_http_google_response_header_set_cookie_exempt(r, ctx, kvs)) {
        return NGX_ERROR;
      }
    }
    
    if (!ngx_strncasecmp(kv->key.data, (u_char *)"domain", 6))
    {
      kv->value = *ctx->domain;
    }
    
    if (!ngx_strncasecmp(kv->key.data, (u_char *)"path", 4)) {
      ngx_str_set(&kv->value, "/");
    }
  }
  
  // unset this key
  if (!kvs->nelts) {
    tb->hash = 0; return NGX_OK;
  }
  
  ngx_str_t * set_cookie = ngx_http_google_implode_kv(r, kvs, "; ");
  if (!set_cookie) return NGX_ERROR;
  
  // reset set cookie
  tb->value = *set_cookie;
  
  return NGX_OK;
}
static ngx_int_t
ngx_http_google_response_header_set_cookie_conf(ngx_http_request_t    * r,
                                                ngx_http_google_ctx_t * ctx,
                                                ngx_str_t             * v)
{
  if (ctx->type != ngx_http_google_type_main &&
      ctx->type != ngx_http_google_type_scholar) return NGX_OK;
  if (ctx->ncr)                                  return NGX_OK;
  
  ngx_uint_t i;
  ngx_array_t * kvs  = ngx_http_google_explode_kv(r, v, ":");
  if (!kvs) return NGX_ERROR;
  
  ngx_int_t nw = 0;
  ngx_keyval_t * kv, * hd;
  
  hd = kvs->elts;
  for (i = 0; i < kvs->nelts; i++) {
    kv = hd + i;
    if (!ngx_strncasecmp(kv->key.data, (u_char *)"LD", 2)) {
      if (ctx->lang->len) kv->value = *ctx->lang;
      else {
        ngx_str_set(&kv->value, "zh-CN");
      }
    }
    if (!ngx_strncasecmp(kv->key.data, (u_char *)"NW", 2)) nw = 1;
  }
  
  if (!nw) {
    kv = ngx_array_push(kvs);
    if (!kv) return NGX_ERROR;
    ngx_str_set(&kv->key,   "NW");
    ngx_str_set(&kv->value, "1");
  }

  // sort with length
  ngx_sort(kvs->elts, kvs->nelts, sizeof(ngx_keyval_t),
           ngx_http_google_response_header_sort_cookie_conf);
  
  ngx_str_t * nv = ngx_http_google_implode_kv(r, kvs, ":");
  if (!nv) return NGX_ERROR;
  
  *v = *nv;
  
  return NGX_OK;
}