// Check authentication type.
static int is_auth_httprequest_required(request_rec* rec)
{
  const apr_array_header_t* requires = ap_requires(rec);
  struct require_line* rl = (struct require_line*)requires->elts;
  int it;

  AP_LOG_DEBUG(rec, "  Core::AuthType=%s", ap_auth_type(rec));
  AP_LOG_DEBUG(rec, "  Core::AuthName=%s", ap_auth_name(rec));

  if(strcasecmp(ap_auth_type(rec), HR_AUTH)!=0) return FALSE; // Type is not match.
  for(it=0; it<requires->nelts; it++) {
    AP_LOG_DEBUG(rec, "  Core::Requires[%d]=%s", it, rl[it].requirement);
    if(strcasecmp(rl[it].requirement, "valid-request")==0) return TRUE;  // Found.
  }
  return FALSE;
}
// Multicast packet.
static apr_status_t multicast_packet(request_rec* rec, multicast_conf* conf, const char* _msg)
{
  int r, s = socket(AF_INET, SOCK_DGRAM, 0);
  char* msg = unescape_specialchars(rec->pool, _msg);

  AP_LOG_DEBUG(rec, "unescaped string: %s", msg);

  r = setsockopt(s, SOL_IP, IP_MULTICAST_IF, conf->multicast_interface.addr, conf->multicast_interface.len);
  if(r!=0) {
    r = errno;
    AP_LOG_ERR(rec, "setsockopt() failed - %s(%d)", strerror(r), r);
    goto FINALLY;
  }

  r = connect(s, conf->multicast_address.addr, conf->multicast_address.len);
  if(r!=0) {
    r = errno;
    AP_LOG_ERR(rec, "connect() failed - %s(%d)", strerror(r), r);
    goto FINALLY;
  }

  r = send(s, msg, strlen(msg), 0);
  r = (r>=0)? APR_SUCCESS: errno;

FINALLY:
  if(s>=0) {
    shutdown(s, SHUT_RDWR);
    close(s);
  }
  return r;
}
// apr_table_do() callback proc: Copy headers from apache-request to curl-request.
static int each_headers_proc(void* rec, const char* key, const char* value)
{
  if(!is_bypass_header(key)) {
    context* c = (context*)rec;
    char* h = apr_psprintf(c->rec->pool, "%s: %s", key, value);
    AP_LOG_DEBUG(c->rec, "++ %s", h);
    c->headers = curl_slist_append(c->headers, h);
  }
  return TRUE;
}
// CURLOPT_HEADERFUNCTION callback proc: Copy headers from curl-response to apache-response.
static size_t curl_header_proc(const void* _ptr, size_t size, size_t nmemb, void* _info)
{
  context* c = (context*)_info;
  const char* ptr = (const char*)_ptr;

  if(strncmp(ptr, "HTTP/1.", sizeof("HTTP/1.") - 1)==0) {
    int mv, s;
    if(sscanf(ptr, "HTTP/1.%d %d ", &mv, &s)==2) c->status = s;
    return nmemb;
  }
  
  if(is_break_status(c->status)) {
    char* v = strchr(ptr, ':');
    if(v && v[1] && (!is_bypass_header(ptr))) {
      char* h = apr_pstrdup(c->rec->pool, ptr);
      char* k, *v, *next;
      k = apr_strtok(h, ":", &next);
      v = apr_strtok(next, "\r\n", &next);
      AP_LOG_DEBUG(c->rec, "%s => %s", k, v);
      apr_table_set(c->rec->err_headers_out, k, v);
    }
  }
  return nmemb;
}
// Break request.
static int break_request_handler(request_rec *rec)
{
  AP_LOG_DEBUG(rec, "Incomming %s", __FUNCTION__);
  if(!is_auth_httprequest_required(rec))  return DECLINED;  // Not required.
  return is_break_status(rec->status)? rec->status: OK;
}
//
// Main functions.
//
static int check_auth_handler(request_rec *rec)
{
  auth_conf*  conf = (auth_conf*)ap_get_module_config(rec->per_dir_config, &auth_httprequest_module);
  context     ctx;
  CURLcode    ret;
  const char* secret, *url;
  int threaded_mpm;
  int code=0;

  AP_LOG_DEBUG(rec, "Incomming %s URI=%s", __FUNCTION__, conf->url);

  // Check requires.
  if(!is_auth_httprequest_required(rec))  return DECLINED;  // Not required.
  if(!conf->url[0]) return OK;  // URL is empty.

  AP_LOG_DEBUG(rec, "  %s %s", rec->method, rec->uri);

  // Enable to dump authorize result.
  if(conf->dump==ENABLED) {
    ap_add_output_filter(DUMP_AUTH_RESULT, apr_pmemdup(rec->pool, &ctx, sizeof(ctx)), rec, rec->connection);
  }

  // Skip nested request.
  secret = apr_table_get(rec->headers_in, SECRET);
  if(secret) {
    AP_LOG_DEBUG(rec, "  %s: %s", SECRET, secret);
    if(strstr(secret, conf->secret)) {
      AP_LOG_DEBUG(rec, "Check config. Nested request.");
      rec->user = apr_pstrdup(rec->pool, conf->url);  
      return OK;
    }
  }

  // Initialize callback-context.
  ctx.curl = curl_easy_init();
  ctx.rec  = rec;
  ctx.headers = NULL;
  ctx.status = 0;

  // Initialize libcurl for MPM.
  ap_mpm_query(AP_MPMQ_IS_THREADED, &threaded_mpm);
  curl_easy_setopt(ctx.curl, CURLOPT_NOSIGNAL, threaded_mpm);

  // Bypass request headers, set 'X-Auth-HttpRequest-URI'.
  apr_table_do(each_headers_proc, &ctx, rec->headers_in, NULL);
  each_headers_proc(&ctx, X_AUTH_HTTPREQUEST_URL, rec->uri);
  each_headers_proc(&ctx, X_AUTH_HTTPREQUEST_METHOD, rec->method);
  if(conf->secret[0]) each_headers_proc(&ctx, SECRET, conf->secret);

  // Setup URL to bypass response headers and body.
  curl_easy_setopt(ctx.curl, CURLOPT_URL, url = apr_psprintf(rec->pool, conf->url, rec->uri));
  curl_easy_setopt(ctx.curl, CURLOPT_CUSTOMREQUEST, "HEAD");
  curl_easy_setopt(ctx.curl, CURLOPT_HTTPHEADER, ctx.headers);
  curl_easy_setopt(ctx.curl, CURLOPT_USERAGENT, apr_psprintf(rec->pool, "%s %s", VERSION, curl_version()));
  curl_easy_setopt(ctx.curl, CURLOPT_WRITEHEADER, &ctx);
  curl_easy_setopt(ctx.curl, CURLOPT_HEADERFUNCTION, curl_header_proc);

  // Request.
  ret = curl_easy_perform(ctx.curl);
  curl_easy_getinfo(ctx.curl, CURLINFO_RESPONSE_CODE, &code);
  AP_LOG_DEBUG(rec, "curl result(%d) %d", ret, code);

  // Cleanup.
  curl_slist_free_all(ctx.headers);
  curl_easy_cleanup(ctx.curl);

  // Result.
  if(ret==0) {
    if(is_break_status(code)) {
      // Break request, set custom response.
      if(conf->errdoc) {
        char* msg = apr_psprintf(rec->pool, conf->errdoc, code);
        AP_LOG_DEBUG(rec, "Custom response: %s", msg);
        ap_custom_response(rec, code, msg);
      }
      return code;
    }

    if(is_authorized_status(code)) {
      // Set 'REMOTE_USER' and 'AUTH_TYPE'.
      rec->user = apr_pstrdup(rec->pool, conf->url); // => ENV['REMOTE_USER']
      AP_LOG_DEBUG(rec, "== AUTHORIZED(%s, %s)", rec->ap_auth_type, rec->user);
    } else {
      AP_LOG_DEBUG(rec, "== PATH THRU");
    }
  } else {
    static const char* const ce[8] = {
      "", "E_PROTOCOL", "E_INIT", "E_MALFORMAT",
      "E_MALFORMAT_USER", "E_PROXY", "E_HOST", "E_CONNECT",
    };
    AP_LOG_WARN(rec, "Check config. http resuest failed [%s] %s(CURLcode = %d)", url, ((ret<8)? ce[ret]: ce[0]), ret);
    
  }

  return OK;
}
//
// Output filter.
//
static apr_status_t resize_output_filter(ap_filter_t* f, apr_bucket_brigade* in_bb)
{
  request_rec* rec =f->r;
  resize_conf* conf = (resize_conf*)ap_get_module_config(rec->per_dir_config, &resizeimage_module);
  const char* content_type, *target_type = "JPEG";
  const char* image_url, *resize_param, *image_hash=NULL;
  Magick::Blob blob;
  char* vlob = NULL;
  size_t vlob_length = 0;
  int cache_hit = FALSE;

  AP_LOG_VERBOSE(rec, "Incoming %s.", __FUNCTION__);

  // Pass thru by request types.
  if(rec->status!=HTTP_OK || rec->main!=NULL || rec->header_only
    || (rec->handler!= NULL && strcmp(rec->handler, "default-handler") == 0)) goto PASS_THRU;

  AP_LOG_VERBOSE(rec, "-- Checking responce headers.");

  // Obtain and erase x-resize-image header or pass through.
  image_url = get_and_unset_header(rec->headers_out, X_RESIZE);
  if(image_url== NULL || image_url[0]=='\0') {
    image_url = get_and_unset_header(rec->err_headers_out, X_RESIZE);
  }
  if(image_url==NULL || image_url[0]=='\0') goto PASS_THRU;

  // Check content-type
  content_type = rec->content_type;
  if(content_type) {
    if(strcasecmp(content_type, "image/jpeg")==0) {
      target_type = "JPEG";
    } else
    if(strcasecmp(content_type, "image/png")==0) {
      target_type = "PNG";
    } else
    if(strcasecmp(content_type, "image/gif")==0) {
      target_type = "GIF";
    } else goto PASS_THRU;
  }

  // Resize parameter
  resize_param = get_and_unset_header(rec->headers_out, X_RESIZE_PARAM);
  if(resize_param==NULL || resize_param[0]=='\0') {
    resize_param = get_and_unset_header(rec->err_headers_out, X_RESIZE_PARAM);
  }
  if(resize_param[0]=='\0') resize_param = NULL;

  // Image hash
  image_hash = get_and_unset_header(rec->headers_out, X_RESIZE_HASH);
  if(image_hash==NULL || image_hash[0]=='\0') {
    image_hash = get_and_unset_header(rec->err_headers_out, X_RESIZE_HASH);
  }
 
  // Open image and resize.
  AP_LOG_INFO(rec, "URL: %s, %s => %s (%s)", image_url, content_type, resize_param, image_hash);

  if(image_hash) {
    // Try memcached...
    image_hash = apr_psprintf(rec->pool, "%s:%s:%s", image_hash, target_type, resize_param);
    memcached_return r;
    uint32_t flags;
    vlob = memcached_get(conf->memc, image_hash, strlen(image_hash), &vlob_length, &flags, &r);
    if(r==MEMCACHED_SUCCESS) {
      AP_LOG_DEBUG(rec, "Restored from memcached: %s, len=%d", image_hash, vlob_length);
      cache_hit = TRUE;
      goto WRITE_DATA;
    } else {
      AP_LOG_DEBUG(rec, "Can't restore from memcached: %s - %s(%d)", image_hash, memcached_strerror(conf->memc, r), r);
    }
  }

  // Reszize
  try {
    Magick::Image image;

    image.read(image_url);
    if(resize_param) image.zoom(resize_param);
    image.magick(target_type);
    image.quality(conf->jpeg_quality);
    image.write(&blob);
    vlob = (char*)blob.data();
    vlob_length = blob.length();
  }
  catch(Magick::Exception& err) {
    AP_LOG_ERR(rec, __FILE__ ": Magick failed: %s", err.what());
    goto PASS_THRU;
  }

  if(image_hash) {
    // Store to memcached...
    memcached_return r = memcached_set(conf->memc, image_hash, strlen(image_hash), vlob, vlob_length, conf->expire, 0);
    if(r==MEMCACHED_SUCCESS) {
      AP_LOG_DEBUG(rec, "Stored to memcached: %s(len=%d)", image_hash, vlob_length);
    } else {
      AP_LOG_DEBUG(rec, "Can't store from memcached: %s(len=%d) - %s(%d)", image_hash, vlob_length,memcached_strerror(conf->memc, r), r);
    }
  }

WRITE_DATA:
  AP_LOG_VERBOSE(rec, "-- Creating resize buckets.");

  // Drop all content and headers related.
  while(!APR_BRIGADE_EMPTY(in_bb)) {
    apr_bucket* b = APR_BRIGADE_FIRST(in_bb);
    apr_bucket_delete(b);
  }
  rec->eos_sent = 0;
  rec->clength = 0;
  unset_header(rec, "Content-Length");
  unset_header(rec, "Content-Encoding");
  unset_header(rec, "Last-Modified");
  unset_header(rec, "ETag");

  // Start resize bucket.
  {
    apr_off_t remain = vlob_length, offset = 0;
    while(remain>0) {
      apr_off_t bs = (remain<AP_MAX_SENDFILE)? remain: AP_MAX_SENDFILE;
      char* heap = (char*)malloc(bs);
      memcpy(heap, vlob+offset, bs);
      apr_bucket* b = apr_bucket_heap_create(heap, bs, free, in_bb-> bucket_alloc);
      APR_BRIGADE_INSERT_TAIL(in_bb, b);
      remain -= bs;
      offset += bs;
    }
    APR_BRIGADE_INSERT_TAIL(in_bb, apr_bucket_eos_create(in_bb->bucket_alloc));
    ap_set_content_length(rec, vlob_length);
    if(cache_hit) free(vlob);
  }
  AP_LOG_VERBOSE(rec, "-- Create done.");
 
PASS_THRU:
  AP_LOG_VERBOSE(rec, "-- Filter done.");
  ap_remove_output_filter(f);
  return ap_pass_brigade(f->next, in_bb);
}