Exemple #1
0
/** \brief parse option from configuration file.
*
* \param[in,out] as Pointer to session handle
* \param[in] opt name of option to set (left of =)
* \param[in] param name of value for option (right of =)
* \param type type for param, currently unused
* \return 0 if parsing was successful, -1 if an error occured.  currently
* always returns 0
*/
PRIVATE int set_option(auto_handle *as, const char *opt, const char *param, option_type type) {
  int32_t numval;
  dbg_printf(P_INFO2, "%s=%s (type: %d)", opt, param, type);

  assert(as != NULL);
  if(!strcmp(opt, "url")) {
    getFeeds(&as->feeds, param);
  } else if(!strcmp(opt, "feed")) {
    parseFeed(&as->feeds, param);
  } else if(!strcmp(opt, "download-folder")) {
    set_path(param, &as->download_folder);
  } else if(!strcmp(opt, "statefile")) {
    set_path(param, &as->statefile);
  } else if(!strcmp(opt, "interval")) {
    numval = parseUInt(param);
    if(numval > 0) {
      as->check_interval = numval;
    } else if(numval != -1) {
      dbg_printf(P_ERROR, "Interval must be 1 minute or more, reverting to default (%dmin)\n\t%s=%s", AM_DEFAULT_INTERVAL, opt, param);
    } else {
      dbg_printf(P_ERROR, "Unknown parameter: %s=%s", opt, param);
    }
  } else if(!strcmp(opt, "patterns")) {
    addPatterns_old(&as->filters, param);
  } else if(!strcmp(opt, "filter")) {
    parseFilter(&as->filters, param);
  } else if(!strcmp(opt, "prowl-apikey")) {
    as->prowl_key = am_strdup(param);
  } else if(!strcmp(opt, "download-done-script")) {
    as->download_done_script = am_strdup(param);
  } else {
    dbg_printf(P_ERROR, "Unknown option: %s", opt);
  }
  return 0;
}
Exemple #2
0
auto_handle* session_init(void) {
  char path[MAXPATHLEN];
  char *home;

  auto_handle *ses = am_malloc(sizeof(auto_handle));

  /* numbers */
  ses->max_bucket_items     = AM_DEFAULT_MAXBUCKET;
  ses->bucket_changed       = 0;
  ses->check_interval       = AM_DEFAULT_INTERVAL;

  /* strings */
  ses->download_folder        = get_temp_folder();
  home = get_home_folder();
  sprintf(path, "%s/%s", home, AM_DEFAULT_STATEFILE);
  am_free(home);
  ses->statefile             = am_strdup(path);
  ses->prowl_key             = NULL;
  ses->prowl_key_valid       = 0;
  ses->download_done_script  = NULL;
  ses->match_only            = 0;

  /* lists */
  ses->filters               = NULL;
  ses->feeds                 = NULL;
  ses->downloads             = NULL;

  return ses;
}
Exemple #3
0
/** \brief add new item to bucket list
 *
 * \param[in] guid Unique identifier for a bucket item (e.g. a URL)
 * \param[in,out] head pointer to the bucket list
 * \param[in] maxBucketItems number of maximum items in bucket list
 * \return always returns 0
 *
 * The size of the provided bucket list is kept to maxBucketItems.
 * If it gets larger than the specified value, the oldest element is removed from the list.
 */
int addToBucket(const char* guid, NODE **head, const int maxBucketItems) {
	addToHead(am_strdup(guid), head);
	if(maxBucketItems > 0 && listCount(*head) > (uint32_t)maxBucketItems) {
		dbg_printf(P_INFO2, "[add_to_bucket] bucket gets too large, deleting last item...\n");
		removeLast(*head, NULL);
	}

	return 0;
}
Exemple #4
0
PRIVATE void callDownloadDoneScript(const char* scriptname, const char* filename)
{
  if (scriptname && *scriptname && filename && *filename)
  {
    int i;
    char * cmd[] = { am_strdup(scriptname), am_strdup(filename), NULL };
    signal(SIGCHLD, onSigCHLD);

    if (!fork ())
    {
      if (execvp (scriptname, cmd) == -1)
        dbg_printf(P_ERROR, "error executing script \"%s\": %d", cmd[0], errno);

      _exit (0);
    }

    for (i=0; cmd[i]; ++i) am_free (cmd[i]);
  }
}
Exemple #5
0
PRIVATE char* shorten(const char *str) {

  int tmp_pos;
  char c;
  char *retStr;
  char *tmp = (char*)am_malloc(MAX_PARAM_LEN+1);
  uint32_t line_pos = 0, i;
  uint32_t len = strlen(str);

  if(!tmp) {
    dbg_printf(P_ERROR, "[shorten] calloc(MAX_PARAM_LEN) failed!");
    return NULL;
  }

  memset(tmp, 0, MAX_PARAM_LEN+1);

  while (isspace(str[line_pos])) {
    ++line_pos;
  }
  tmp_pos = 0;
  while(line_pos < len) {
    /* case 1: quoted strings */
    if(tmp_pos != 0) {
      for(i = 0; i < strlen(AM_DELIMITER); ++i)
        tmp[tmp_pos++] = AM_DELIMITER[i];
    }
    if (str[line_pos] == '"' || str[line_pos] == '\'') {
      c = str[line_pos];
      ++line_pos;  /* skip quote */
      while(str[line_pos] != c && line_pos < len && str[line_pos] != '\n' && str[line_pos] != '\0') {
        tmp[tmp_pos++] = str[line_pos++];
      }
      if(str[line_pos] == c) {
        line_pos++; /* skip the closing quote */
      }
    } else {
      while(line_pos < len && str[line_pos] != '\n' && str[line_pos] != '\0') {
        tmp[tmp_pos++] = str[line_pos++];
      }
    }
    while (isspace(str[line_pos])) {
      ++line_pos;
    }
  }
  tmp[tmp_pos] = '\0';
  assert(strlen(tmp) < MAX_PARAM_LEN);
  retStr = am_strdup(tmp);
  am_free(tmp);
  return retStr;
}
Exemple #6
0
PRIVATE void set_path(const char *src, char **dst) {
  char *tmp;

  if(src && strlen(src) < MAXPATHLEN) {
    tmp = resolve_path(src);
    if(tmp) {
      if ( *dst != NULL ) {
        am_free(*dst);
      }
      *dst = am_strdup(tmp);
      am_free(tmp);
    }
  }
}
Exemple #7
0
auto_handle* session_init(void) {
  char path[MAXPATHLEN];
  char *home;

  am_session_t *ses = am_malloc(sizeof(am_session_t));

  /* numbers */
  ses->rpc_version          = AM_DEFAULT_RPC_VERSION;
  ses->max_bucket_items     = AM_DEFAULT_MAXBUCKET;
  ses->bucket_changed       = 0;
  ses->check_interval       = AM_DEFAULT_INTERVAL;
  ses->use_transmission     = AM_DEFAULT_USETRANSMISSION;
  ses->start_torrent        = AM_DEFAULT_STARTTORRENTS;
  ses->transmission_version = AM_TRANSMISSION_1_3;
  ses->rpc_port             = AM_DEFAULT_RPCPORT;

  /* strings */
  ses->transmission_path     = get_tr_folder();
  ses->torrent_folder        = get_temp_folder();
  ses->host                  = NULL;
  ses->auth                  = NULL;
  home = get_home_folder();
  sprintf(path, "%s/%s", home, AM_DEFAULT_STATEFILE);
  am_free(home);
  ses->statefile             = am_strdup(path);
  ses->prowl_key             = NULL;
  ses->toasty_key            = NULL;
  ses->pushalot_key          = NULL;
  ses->pushover_key          = NULL;
  ses->prowl_key_valid       = 0;
  ses->match_only            = 0;
  ses->transmission_external = NULL;

  /* lists */
  ses->filters              = NULL;
  ses->feeds                 = NULL;
  ses->downloads             = NULL;
  ses->upspeed               = -1;

  return ses;
}
Exemple #8
0
/** \brief Create a new WebData object
*
* \param[in] url URL for a WebData object
*
* The parameter \a url is optional. You may provide \c NULL if no URL is required or not known yet.
*/
PRIVATE struct WebData* WebData_new(const char *url) {
  WebData *data = NULL;

  data = am_malloc(sizeof(WebData));
  if(!data)
    return NULL;

  data->url = NULL;
  data->content_filename = NULL;
  data->content_length = -1;
  data->response = NULL;

  if(url) {
    data->url = am_strdup((char*)url);
  }

  data->response = HTTPData_new();
  if(!data->response) {
    WebData_free(data);
    return NULL;
  }
  return data;
}
Exemple #9
0
PRIVATE int parseSubOption(char* line, char **option, char **param) {
  const char *subopt_delim = "=>";
  uint32_t i = 0;

  *option = NULL;
  *param  = NULL;

  assert(line && *line);

  while(line[i] != '\0') {
    if(line[i]   == subopt_delim[0] &&
       line[i+1] == subopt_delim[1]) {
      *option = am_strndup(line, i-1);
      *param  = am_strdup(line + i + strlen(subopt_delim));
      break;
    }
    i++;
  }

  if(*option && *param)
    return 0;
  else
    return -1;
}
Exemple #10
0
int main(int argc, char **argv) {
  auto_handle *session = NULL;
  char *config_file = NULL;
  char *logfile = NULL;
  char *xmlfile = NULL;
  char erbuf[100];
  NODE *current = NULL;
  uint32_t count = 0;
  uint8_t first_run = 1;
  uint8_t once = 0;
  uint8_t verbose = AM_DEFAULT_VERBOSE;
  uint8_t append_log = 0;
  uint8_t match_only = 0;

  /* this sets the log level to the default before anything else is done.
  ** This way, if any outputting happens in readargs(), it'll be printed
  ** to stderr.
  */
  log_init(NULL, verbose, 0);

  readargs(argc, argv, &config_file, &logfile, &xmlfile, &nofork, &verbose, &once, &append_log, &match_only);

  /* reinitialize the logging with the values from the command line */
  log_init(logfile, verbose, append_log);

  if(!config_file) {
    config_file = am_strdup(AM_DEFAULT_CONFIGFILE);
  }
  strncpy(AutoConfigFile, config_file, strlen(config_file));

  session = session_init();
  session->match_only = match_only;

  if(parse_config_file(session, AutoConfigFile) != 0) {
    if(errno == ENOENT) {
      snprintf(erbuf, sizeof(erbuf), "Cannot find file '%s'", config_file);
    } else {
      snprintf(erbuf, sizeof(erbuf), "Unknown error");
    }
    fprintf(stderr, "Error parsing config file: %s\n", erbuf);
    shutdown_daemon(session);
  }

  setup_signals();

  if(!nofork) {

    /* start daemon */
    if(daemonize() != 0) {
      dbg_printf(P_ERROR, "Error: Daemonize failed. Aborting...");
      shutdown_daemon(session);
    }

    dbg_printft( P_MSG, "Daemon started");
  }

  filter_printList(session->filters);

  dbg_printf(P_MSG, "Trailermatic version: %s", LONG_VERSION_STRING);
  dbg_printf(P_INFO, "verbose level: %d", verbose);
  dbg_printf(P_INFO, "foreground mode: %s", nofork == 1 ? "yes" : "no");
  dbg_printf(P_INFO, "config file: %s", AutoConfigFile);
  dbg_printf(P_INFO, "check interval: %d min", session->check_interval);
  dbg_printf(P_INFO, "download folder: %s", session->download_folder);
  dbg_printf(P_INFO, "state file: %s", session->statefile);
  dbg_printf(P_MSG,  "%d feed URLs", listCount(session->feeds));
  dbg_printf(P_MSG,  "Read %d filters from config file", listCount(session->filters));

  if(session->prowl_key) {
    dbg_printf(P_INFO, "Prowl API key: %s", session->prowl_key);
  }

  if(listCount(session->feeds) == 0) {
    dbg_printf(P_ERROR, "No feed URL specified in trailermatic.conf!\n");
    shutdown_daemon(session);
  }

  if(listCount(session->filters) == 0) {
    dbg_printf(P_ERROR, "No filters specified in trailermatic.conf!\n");
    shutdown_daemon(session);
  }

  /* check if Prowl API key is given, and if it is valid */
  if(session->prowl_key && verifyProwlAPIKey(session->prowl_key) ) {
    session->prowl_key_valid = 1;
  }

  load_state(session->statefile, &session->downloads);
  while(!closing) {
    dbg_printft( P_INFO, "------ Checking for new trailers ------");
     if(xmlfile && *xmlfile) {
       processFile(session, xmlfile);
       once = 1;
    } else {
      current = session->feeds;
      count = 0;
      while(current && current->data) {
        ++count;
        dbg_printf(P_INFO2, "Checking feed %d ...", count);
        processFeed(session, current->data, first_run);
        current = current->next;
      }
      if(first_run) {
        dbg_printf(P_INFO2, "New bucket size: %d", session->max_bucket_items);
      }
      first_run = 0;
    }
    /* leave loop when program is only supposed to run once */
    if(once) {
      break;
    }
    sleep(session->check_interval * 60);
  }
  shutdown_daemon(session);
  return 0;
}
/** \brief parse option from configuration file.
*
* \param[in,out] as Pointer to session handle
* \param[in] opt name of option to set (left of =)
* \param[in] param name of value for option (right of =)
* \param type type for param, currently unused
* \return 0 if parsing was successful, -1 if an error occured.  currently
* always returns 0
*/
PRIVATE int set_option(auto_handle *as, const char *opt, const char *param, option_type type) {
  int32_t numval;
  int32_t result = SUCCESS;

  dbg_printf(P_INFO2, "[config] %s=%s (type: %d)", opt, param, type);

  assert(as != NULL);
  if(!strcmp(opt, "url")) {
    dbg_printf(P_ERROR, "the 'url' option is not supported any more, please use the 'feed' option instead!");
    result = FAILURE;
  } else if(!strcmp(opt, "feed")) {
    result = parseFeed(&as->feeds, param);
  } else if(!strcmp(opt, "transmission-home")) {
    set_path(param, &as->transmission_path);
  } else if(!strcmp(opt, "prowl-apikey")) {
    as->prowl_key = am_strdup(param);
  } else if(!strcmp(opt, "toasty-deviceid")) {
    as->toasty_key = am_strdup(param);
  } else if(!strcmp(opt, "pushalot-token")) {
    as->pushalot_key = am_strdup(param);
  } else if(!strcmp(opt, "transmission-version")) {
    if (!strcmp(param, "external")) {
      /* we should probably only set this when transmission-external is set */
      as->transmission_version = AM_TRANSMISSION_EXTERNAL;
    } else if(param[0] == '1' && param[1] == '.' && param[2] == '2') {
      as->transmission_version = AM_TRANSMISSION_1_2;
    } else if(param[0] == '1' && param[1] == '.' && param[2] == '3') {
      as->transmission_version = AM_TRANSMISSION_1_3;
    } else {
      dbg_printf(P_ERROR, "Unknown parameter: %s=%s", opt, param);
    }
  } else if (!strcmp(opt, "transmission-external")) {
    set_path(param, &as->transmission_external);
    as->transmission_version = AM_TRANSMISSION_EXTERNAL;
  } else if(!strcmp(opt, "torrent-folder")) {
    set_path(param, &as->torrent_folder);
  } else if(!strcmp(opt, "statefile")) {
    set_path(param, &as->statefile);
  } else if(!strcmp(opt, "rpc-host")) {
    as->host = am_strdup(param);
  } else if(!strcmp(opt, "rpc-auth")) {
    as->auth = am_strdup(param);
  } else if(!strcmp(opt, "upload-limit")) {
    numval = parseUInt(param);
    if(numval > 0) {
      as->upspeed = (uint16_t)numval;
    } else {
      dbg_printf(P_ERROR, "Unknown parameter: %s=%s", opt, param);
    }
  } else if(!strcmp(opt, "rpc-port")) {
    numval = parseUInt(param);
    if (numval > 1024 && numval < 65535) {
      as->rpc_port = numval;
    } else if(numval != -1) {
      dbg_printf(P_ERROR, "RPC port must be an integer between 1025 and 65535, reverting to default (%d)\n\t%s=%s", AM_DEFAULT_RPCPORT, opt, param);
    } else {
      dbg_printf(P_ERROR, "Unknown parameter: %s=%s", opt, param);
    }
  } else if(!strcmp(opt, "interval")) {
    numval = parseUInt(param);
    if(numval > 0) {
      as->check_interval = numval;
    } else if(numval != -1) {
      dbg_printf(P_ERROR, "Interval must be 1 minute or more, reverting to default (%dmin)\n\t%s=%s", AM_DEFAULT_INTERVAL, opt, param);
    } else {
      dbg_printf(P_ERROR, "Unknown parameter: %s=%s", opt, param);
    }
  } else if(!strcmp(opt, "use-transmission")) {
    if(!strncmp(param, "0", 1) || !strncmp(param, "no", 2)) {
      as->use_transmission = 0;
    } else if(!strncmp(param, "1", 1) || !strncmp(param, "yes", 3)) {
      as->use_transmission = 1;
    } else {
      dbg_printf(P_ERROR, "Unknown parameter: %s=%s", opt, param);
    }
  } else if(!strcmp(opt, "start-torrents")) {
    if(!strncmp(param, "0", 1) || !strncmp(param, "no", 2)) {
      as->start_torrent = 0;
    } else if(!strncmp(param, "1", 1) || !strncmp(param, "yes", 3)) {
      as->start_torrent = 1;
    } else {
      dbg_printf(P_ERROR, "Unknown parameter for option '%s': '%s'", opt, param);
    }
  } else if(!strcmp(opt, "patterns")) {
    dbg_printf(P_ERROR, "the 'patterns' option is not supported any more, please use the 'filter' option instead!");
    result = FAILURE;
  } else if(!strcmp(opt, "filter")) {
    result = parseFilter(&as->filters, param);
  } else {
    dbg_printf(P_ERROR, "Unknown option: %s", opt);
  }

  return result;
}
Exemple #12
0
int main(int argc, char **argv) {
  auto_handle * ses = NULL;
  char *config_file = NULL;
  char *logfile = NULL;
  char *pidfile = NULL;
  char *xmlfile = NULL;
  char erbuf[100];
  NODE *current = NULL;
  uint32_t count = 0;
  uint8_t first_run = 1;
  uint8_t once = 0;
  uint8_t verbose = AM_DEFAULT_VERBOSE;
  uint8_t append_log = 0;
  uint8_t match_only = 0;

  /* this sets the log level to the default before anything else is done.
  ** This way, if any outputting happens in readargs(), it'll be printed
  ** to stderr.
  */
  log_init(NULL, verbose, 0);

  readargs(argc, argv, &config_file, &logfile, &pidfile, &xmlfile, &nofork, &verbose, &once, &append_log, &match_only);

  /* reinitialize the logging with the values from the command line */
  log_init(logfile, verbose, append_log);

  dbg_printf(P_MSG, "Automatic version: %s", LONG_VERSION_STRING);

  if(!config_file) {
    config_file = am_strdup(AM_DEFAULT_CONFIGFILE);
  }

  strncpy(AutoConfigFile, config_file, strlen(config_file));

  ses = session_init();
  ses->match_only = match_only;

  if(parse_config_file(ses, AutoConfigFile) != 0) {
     if(errno == ENOENT) {
        snprintf(erbuf, sizeof(erbuf), "Cannot find file '%s'", config_file);
     } else {
        snprintf(erbuf, sizeof(erbuf), "Unknown error");
     }

     fprintf(stderr, "Error parsing config file: %s\n", erbuf);
     shutdown_daemon(ses);
  }

  if(!setupSession(ses)) {
     shutdown_daemon(ses);
  }

  setup_signals();

  if(!nofork) {
    /* start daemon */
    if(daemonize(pidfile) != 0) {
      dbg_printf(P_ERROR, "Error: Daemonize failed. Aborting...");
      shutdown_daemon(mySession);
    }
    dbg_printft( P_MSG, "Daemon started");
  }

  dbg_printf(P_INFO, "verbose level: %d", verbose);
  dbg_printf(P_INFO, "foreground mode: %s", nofork == true ? "yes" : "no");

  while(!closing) {
    isRunning = true;
    dbg_printft( P_INFO, "------ Checking for new episodes ------");
    if(xmlfile && *xmlfile) {
      processFile(mySession, xmlfile);
      once = 1;
    } else {
      current = mySession->feeds;
      count = 0;
      while(current && current->data) {
        ++count;
        processFeed(mySession, current->data, first_run);
        current = current->next;
      }
      if(first_run) {
        dbg_printf(P_INFO2, "New bucket size: %d", mySession->max_bucket_items);
      }
      first_run = 0;
    }

    /* leave loop when program is only supposed to run once */
    if(once) {
      break;
    }

    isRunning = false;

    if(seenHUP) {
      signal_handler(SIGHUP);
    }

    sleep(mySession->check_interval * 60);
  }

  shutdown_daemon(mySession);
  return 0;
}
Exemple #13
0
/** \brief Upload data to a specified URL.
*
* \param url Path to where data shall be uploaded
* \param auth (Optional) authentication information in the form of "user:password"
* \param data Data that shall be uploaded
* \param data_size size of the data
* \return Web server response
*/
PUBLIC HTTPResponse* sendHTTPData(const char *url, const char* auth, const void *data, uint32_t data_size) {
  CURL *curl_handle = NULL;
  CURLcode res;
  long rc, tries = 2, len;
  WebData* response_data = NULL;
  HTTPResponse* resp = NULL;
  char sessionKey[MAXLEN];

  struct curl_slist * headers = NULL;

  if( !url || !data ) {
    return NULL;
  }

  response_data = WebData_new(url);

  do {
    --tries;
    WebData_clear(response_data);

    if( curl_handle == NULL) {
      if(gbGlobalInitDone == FALSE) {
        curl_global_init(CURL_GLOBAL_ALL);
        gbGlobalInitDone = TRUE;
      }
      if( ( curl_handle = am_curl_init(auth, TRUE) ) ) {
        curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, response_data);
        curl_easy_setopt(curl_handle, CURLOPT_WRITEHEADER, response_data);
        //Transmission-specific options for HTTP POST
        if(strstr(response_data->url, "transmission") != NULL) {
          curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, parse_Transmission_response );
          headers = curl_slist_append(headers, "Content-Type: application/json");

          if( gSessionID ) {
            if((len = snprintf(sessionKey, MAXLEN, "X-Transmission-Session-Id: %s", gSessionID)) > 0) {
              sessionKey[len] = '\0';
            }

            headers = curl_slist_append(headers, sessionKey);
          }
          curl_easy_setopt( curl_handle, CURLOPT_HTTPHEADER, headers );
        }
        curl_easy_setopt(curl_handle, CURLOPT_URL, response_data->url);
      } else {
        dbg_printf(P_ERROR, "am_curl_init() failed");
        break;
      }
    }

    curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, data);
    curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, data_size);

    if( ( res = curl_easy_perform(curl_handle) ) ) {
      dbg_printf(P_ERROR, "Upload to '%s' failed: %s", url, curl_easy_strerror(res));
      break;
    } else {
      curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &rc);
      dbg_printf(P_INFO2, "response code: %ld", rc);
      if(rc == 409) {
        if(gSessionID) {
          dbg_printf(P_DBG, "Error code 409, session ID: %s", gSessionID);
        } else {
          dbg_printf(P_ERROR, "Error code 409, no session ID");
        }

        closeCURLSession( curl_handle );
        curl_slist_free_all( headers );
        headers = NULL;
        curl_handle = NULL;
      } else {
        resp = HTTPResponse_new();
        curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &resp->responseCode);

        //copy data if present
        if(response_data->response->data) {
          resp->size = response_data->response->buffer_pos;
          resp->data = am_strndup(response_data->response->data, resp->size);
        }
        //copy filename if present
        if(response_data->content_filename) {
          resp->content_filename = am_strdup(response_data->content_filename);
        }
        break;
      }
    }
  } while(tries > 0);

  /* cleanup */
  closeCURLSession(curl_handle);

  if(headers) {
    curl_slist_free_all(headers);
  }

  WebData_free(response_data);

  return resp;
}
Exemple #14
0
PUBLIC HTTPResponse* getHTTPData(const char *url, const char *cookies, CURL ** curl_session) {
  CURLcode      res;
  CURL         *curl_handle = NULL;
  CURL         *session = *curl_session;
  char         *escaped_url = NULL;
  WebData      *data = NULL;
  HTTPResponse *resp = NULL;
  long          responseCode = -1;

  if(!url) {
    return NULL;
  }

  data = WebData_new(url);

  if(!data) {
    return NULL;
  }

  dbg_printf(P_INFO2, "[getHTTPData] url=%s, curl_session=%p", url, (void*)session);
  if(session == NULL) {
    if(gbGlobalInitDone == FALSE) {
      curl_global_init(CURL_GLOBAL_ALL);
      gbGlobalInitDone = TRUE;
    }
    session = am_curl_init(NULL, FALSE);
    *curl_session = session;
  }

  curl_handle = session;

  if(curl_handle) {
    escaped_url = url_encode_whitespace(url);
    assert(escaped_url);
    curl_easy_setopt(curl_handle, CURLOPT_URL, escaped_url);
    curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, data);
    curl_easy_setopt(curl_handle, CURLOPT_WRITEHEADER, data);

    if(cookies && *cookies) {
      /* if there's an explicit cookie string, use it */
      curl_easy_setopt(curl_handle, CURLOPT_COOKIE, cookies);
    } else {
      /* otherwise, enable cookie-handling since there might be cookies defined within the URL */
      curl_easy_setopt(curl_handle, CURLOPT_COOKIEFILE, "");
    }

    res = curl_easy_perform(curl_handle);
    /* curl_easy_cleanup(curl_handle); */
    curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &responseCode);
    dbg_printf(P_INFO2, "[getHTTPData] response code: %d", responseCode);
    if(res != 0) {
        dbg_printf(P_ERROR, "[getHTTPData] '%s': %s (retval: %d)", url, curl_easy_strerror(res), res);
    } else {
      /* Only the very first connection attempt (where curl_session == NULL) should store the session,
      ** and only the last one should close the session.
      */
      resp = HTTPResponse_new();
      resp->responseCode = responseCode;
      //copy data if present
      if(data->response->data) {
        resp->size = data->response->buffer_pos;
        resp->data = am_strndup(data->response->data, resp->size);
      }
      //copy filename if present
      if(data->content_filename) {
        resp->content_filename = am_strdup(data->content_filename);
      }
    }
    am_free(escaped_url);
  } else {
    dbg_printf(P_ERROR, "curl_handle is uninitialized!");
    resp = NULL;
  }
  WebData_free(data);
  return resp;
}