blob: e819f609de66e509fa6928618ecdc141198474fc [file] [log] [blame]
// Copyright 2015 The Android Open Source Project
//
// This software is licensed under the terms of the GNU General Public
// License version 2, as published by the Free Software Foundation, and
// may be copied, distributed, and modified under those terms.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
#include "android/curl-support.h"
#include "android/openssl-support.h"
#include "android/utils/debug.h"
#include "android/utils/system.h"
#include <curl/curl.h>
#include <stdlib.h>
#include <string.h>
static int initCount = 0;
static char* cached_ca_info = NULL;
bool curl_init(const char* ca_info) {
if (initCount == 0) {
// first time - try to initialize the library
const CURLcode res = curl_global_init(CURL_GLOBAL_ALL);
if (res != CURLE_OK) {
dwarning("CURL: global init failed with code %d (%s)", res,
curl_easy_strerror(res));
return false;
}
if (!android_openssl_init()) {
return false;
}
}
free(cached_ca_info);
cached_ca_info = NULL;
if (ca_info != NULL) {
cached_ca_info = strdup(ca_info);
}
++initCount;
return true;
}
void curl_cleanup() {
if (initCount == 0) {
return;
} else if (--initCount == 0) {
free(cached_ca_info);
cached_ca_info = NULL;
// We know we're leaking memory by not calling curl_global_cleanup.
// We can not guarantee that no threads exist when the program exits
// (e.g. android::base::async has unknown lifetime).
//
// Ditto for android_openssl_finish.
}
}
void* curl_easy_default_init(char** error) {
// |curl_easy_init| will try to initialize libcurl globally if it isn't
// already initialized. This behaviour is dangerous in multi-threaded
// environment.
if (!initCount) {
*error = strdup("libcurl is not initialized. Bailing.");
return NULL;
}
CURL* curl = curl_easy_init();
CURLcode curlRes;
if (!curl) {
*error = strdup("Failed to initialize libcurl");
return NULL;
}
curlRes = curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
if (curlRes != CURLE_OK) {
asprintf(error, "Could not disable signals: %s",
curl_easy_strerror(curlRes));
curl_easy_cleanup(curl);
return NULL;
}
if (cached_ca_info != NULL) {
curlRes = curl_easy_setopt(curl, CURLOPT_CAINFO, cached_ca_info);
if (curlRes != CURLE_OK) {
asprintf(error, "Could not set CURLOPT_CAINFO: %s",
curl_easy_strerror(curlRes));
curl_easy_cleanup(curl);
return NULL;
}
}
return curl;
}
// A CurlWriteCallback that drops any downloaded data.
// Using it avoids dumping the content to stdout, the default CURL behaviour.
static size_t null_write_callback(char* ptr,
size_t size,
size_t nmemb,
void* userdata) {
return size * nmemb;
}
// This function can block forever. We do not set any timeout for
// curl_easy_perform. Since we disable signals, the DNS lookup timeout is ignore
// by libcurl.
// TODO: build using c-ares, and set timeout for curl_easy_perform.
static bool curl_download_internal(const char* url,
const char* post_fields,
CurlWriteCallback callback_func,
void* callback_userdata,
bool allow_404,
char** error) {
CURL* curl = curl_easy_default_init(error);
if (!curl) {
return false;
}
bool result = false;
curl_easy_setopt(curl, CURLOPT_URL, url);
if (callback_func) {
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callback_func);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, callback_userdata);
} else {
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, null_write_callback);
}
if (post_fields) {
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_fields);
}
const CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
asprintf(error, "%s", curl_easy_strerror(res));
} else {
// toolbar returns a 404 by design.
long http_response = 0;
int curlRes = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_response);
if (curlRes == CURLE_OK) {
if (http_response == 200) {
result = true;
} else if (http_response == 404 && allow_404) {
result = true;
} else {
asprintf(error, "%s", curl_easy_strerror(curlRes));
}
} else {
asprintf(error, "Unexpected error while checking http response: %s",
curl_easy_strerror(curlRes));
}
}
curl_easy_cleanup(curl);
return result;
}
bool curl_download(const char* url,
const char* post_fields,
CurlWriteCallback callback_func,
void* callback_userdata,
char** error) {
return curl_download_internal(url, post_fields, callback_func, callback_userdata, false, error);
}
extern bool curl_download_null(const char* url,
const char* post_fields,
bool allow_404,
char** error) {
return curl_download_internal(url, post_fields, NULL, NULL, allow_404, error);
}