add ca bundle loading back to CURL

This commit is contained in:
Konstantin Käfer
2015-02-06 16:13:25 +01:00
committed by Konstantin Käfer
parent fe0baffe0d
commit b29bce0936
8 changed files with 150 additions and 24 deletions

View File

@@ -165,6 +165,9 @@ Makefile/android-%: config/android-%.gypi
android-lib-%: Makefile/android-%
@$(ENV_android-$*) ./scripts/flock.py -v build/Android.lock $(MAKE) -C build/android-$* BUILDTYPE=$(BUILDTYPE) androidapp
# Builds the selected/default Android library
android-lib: android-lib-$(ANDROID_ABI)
# Builds the selected/default Android library
android: android-lib-$(ANDROID_ABI)
cd android/java && ./gradlew --parallel-threads=$(JOBS) build

View File

@@ -214,7 +214,7 @@ public class MainActivity extends ActionBarActivity {
switch(position) {
// Bright
case 0:
mMapFragment.getMap().setStyleUrl("asset://styles/styles/bright-v7.json");
mMapFragment.getMap().setStyleUrl("asset://styles/bright-v7.json");
mMapFragment.getMap().removeAllClasses();
mClassSpinner.setVisibility(View.INVISIBLE);
mClassSpinner.setAdapter(null);
@@ -223,7 +223,7 @@ public class MainActivity extends ActionBarActivity {
// Basic
case 1:
mMapFragment.getMap().setStyleUrl("asset://styles/styles/basic-v7.json");
mMapFragment.getMap().setStyleUrl("asset://styles/basic-v7.json");
mMapFragment.getMap().removeAllClasses();
mClassSpinner.setVisibility(View.INVISIBLE);
mClassSpinner.setAdapter(null);
@@ -232,7 +232,7 @@ public class MainActivity extends ActionBarActivity {
// Outdoors
case 2:
mMapFragment.getMap().setStyleUrl("asset://styles/styles/outdoors-v7.json");
mMapFragment.getMap().setStyleUrl("asset://styles/outdoors-v7.json");
mMapFragment.getMap().removeAllClasses();
mClassSpinner.setVisibility(View.VISIBLE);
mClassSpinner.setAdapter(mOutdoorsClassAdapter);
@@ -241,7 +241,7 @@ public class MainActivity extends ActionBarActivity {
// Satellite
case 3:
mMapFragment.getMap().setStyleUrl("asset://styles/styles/satellite-v7.json");
mMapFragment.getMap().setStyleUrl("asset://styles/satellite-v7.json");
mMapFragment.getMap().removeAllClasses();
mClassSpinner.setVisibility(View.VISIBLE);
mClassSpinner.setAdapter(mSatelliteClassAdapter);
@@ -250,7 +250,7 @@ public class MainActivity extends ActionBarActivity {
// Pencil
case 4:
mMapFragment.getMap().setStyleUrl("asset://styles/styles/pencil-v7.json");
mMapFragment.getMap().setStyleUrl("asset://styles/pencil-v7.json");
mMapFragment.getMap().removeAllClasses();
mClassSpinner.setVisibility(View.INVISIBLE);
mClassSpinner.setAdapter(null);
@@ -259,7 +259,7 @@ public class MainActivity extends ActionBarActivity {
// Empty
case 5:
mMapFragment.getMap().setStyleUrl("asset://styles/styles/empty-v7.json");
mMapFragment.getMap().setStyleUrl("asset://styles/empty-v7.json");
mMapFragment.getMap().removeAllClasses();
mClassSpinner.setVisibility(View.INVISIBLE);
mClassSpinner.setAdapter(null);

View File

@@ -14,6 +14,24 @@
'../include',
],
'conditions': [
['host == "android"', {
'variables': {
# Android uses libzip and openssl to set CURL's CA bundle.
'cflags_cc': [ '<@(zip_cflags)', '<@(openssl_cflags)' ],
'ldflags': [ '<@(zip_ldflags)', ],
'libraries': [ '<@(zip_static_libs)', ],
},
}],
['OS == "mac"', {
'xcode_settings': {
'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
},
}, {
'cflags_cc': [ '<@(cflags_cc)' ],
}],
],
'variables': {
'cflags_cc': [
'<@(uv_cflags)',
@@ -30,16 +48,6 @@
],
},
'conditions': [
['OS == "mac"', {
'xcode_settings': {
'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
},
}, {
'cflags_cc': [ '<@(cflags_cc)' ],
}],
],
'link_settings': {
'conditions': [
['OS == "mac"', {

View File

@@ -1,11 +1,18 @@
#include <mbgl/storage/default/http_request.hpp>
#include <mbgl/storage/default/http_context.hpp>
#include <mbgl/storage/response.hpp>
#include <mbgl/platform/log.hpp>
#include <mbgl/util/time.hpp>
#include <curl/curl.h>
#ifdef __ANDROID__
#include <mbgl/android/jni.hpp>
#include <zip.h>
#include <openssl/ssl.h>
#endif
#include <queue>
#include <map>
#include <cassert>
@@ -125,6 +132,8 @@ private:
int attempts = 0;
static const int maxAttempts = 4;
char error[CURL_ERROR_SIZE];
};
@@ -317,6 +326,104 @@ int HTTPCURLContext::startTimeout(CURLM * /* multi */, long timeout_ms, void *us
// -------------------------------------------------------------------------------------------------
#ifdef __ANDROID__
// This function is called to load the CA bundle
// from http://curl.haxx.se/libcurl/c/cacertinmem.html¯
static CURLcode sslctx_function(CURL * /* curl */, void *sslctx, void * /* parm */) {
int error = 0;
struct zip *apk = zip_open(mbgl::android::apkPath.c_str(), 0, &error);
if (apk == nullptr) {
return CURLE_SSL_CACERT_BADFILE;
}
struct zip_file *apkFile = zip_fopen(apk, "assets/ca-bundle.crt", ZIP_FL_NOCASE);
if (apkFile == nullptr) {
zip_close(apk);
apk = nullptr;
return CURLE_SSL_CACERT_BADFILE;
}
struct zip_stat stat;
if (zip_stat(apk, "assets/ca-bundle.crt", ZIP_FL_NOCASE, &stat) != 0) {
zip_fclose(apkFile);
apkFile = nullptr;
zip_close(apk);
apk = nullptr;
return CURLE_SSL_CACERT_BADFILE;
}
if (stat.size > std::numeric_limits<int>::max()) {
zip_fclose(apkFile);
apkFile = nullptr;
zip_close(apk);
apk = nullptr;
return CURLE_SSL_CACERT_BADFILE;
}
const auto pem = util::make_unique<char[]>(stat.size);
if (static_cast<zip_uint64_t>(zip_fread(apkFile, reinterpret_cast<void *>(pem.get()), stat.size)) != stat.size) {
zip_fclose(apkFile);
apkFile = nullptr;
zip_close(apk);
apk = nullptr;
return CURLE_SSL_CACERT_BADFILE;
}
// get a pointer to the X509 certificate store (which may be empty!)
X509_STORE *store = SSL_CTX_get_cert_store((SSL_CTX *)sslctx);
if (store == nullptr) {
return CURLE_SSL_CACERT_BADFILE;
}
// get a BIO
BIO *bio = BIO_new_mem_buf(pem.get(), static_cast<int>(stat.size));
if (bio == nullptr) {
store = nullptr;
return CURLE_SSL_CACERT_BADFILE;
}
// use it to read the PEM formatted certificate from memory into an X509
// structure that SSL can use
X509 *cert = nullptr;
while (PEM_read_bio_X509(bio, &cert, 0, nullptr) != nullptr) {
if (cert == nullptr) {
BIO_free(bio);
bio = nullptr;
store = nullptr;
return CURLE_SSL_CACERT_BADFILE;
}
// add our certificate to this store
if (X509_STORE_add_cert(store, cert) == 0) {
X509_free(cert);
cert = nullptr;
BIO_free(bio);
bio = nullptr;
store = nullptr;
return CURLE_SSL_CACERT_BADFILE;
}
X509_free(cert);
cert = nullptr;
}
// decrease reference counts
BIO_free(bio);
bio = nullptr;
zip_fclose(apkFile);
apkFile = nullptr;
zip_close(apk);
apk = nullptr;
// all set to go
return CURLE_OK;
}
#endif
HTTPRequestImpl::HTTPRequestImpl(HTTPRequest *request_, uv_loop_t *loop, std::unique_ptr<Response> response_)
: context(HTTPCURLContext::Get(loop)),
request(request_),
@@ -325,6 +432,9 @@ HTTPRequestImpl::HTTPRequestImpl(HTTPRequest *request_, uv_loop_t *loop, std::un
assert(request);
context->addRequest(request);
// Zero out the error buffer.
memset(error, 0, sizeof(error));
// If there's already a response, set the correct etags/modified headers to make sure we are
// getting a 304 response if possible. This avoids redownloading unchanged data.
if (existingResponse) {
@@ -343,7 +453,13 @@ HTTPRequestImpl::HTTPRequestImpl(HTTPRequest *request_, uv_loop_t *loop, std::un
}
handleError(curl_easy_setopt(handle, CURLOPT_PRIVATE, this));
handleError(curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, error));
#ifdef __ANDROID__
handleError(curl_easy_setopt(handle, CURLOPT_SSLCERTTYPE, "PEM"));
handleError(curl_easy_setopt(handle, CURLOPT_SSL_CTX_FUNCTION, sslctx_function));
#else
handleError(curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt"));
#endif
handleError(curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1));
handleError(curl_easy_setopt(handle, CURLOPT_URL, request->resource.url.c_str()));
handleError(curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, writeCallback));
@@ -550,21 +666,19 @@ void HTTPRequestImpl::handleResult(CURLcode code) {
// Add human-readable error code
if (code != CURLE_OK) {
response->message = curl_easy_strerror(code);
response->status = Response::Error;
response->message = std::string { curl_easy_strerror(code) } + ": " + error;
switch (code) {
case CURLE_COULDNT_RESOLVE_PROXY:
case CURLE_COULDNT_RESOLVE_HOST:
case CURLE_COULDNT_CONNECT:
response->status = Response::Error;
return finish(ResponseStatus::ConnectionError);
case CURLE_OPERATION_TIMEDOUT:
response->status = Response::Error;
return finish(ResponseStatus::TemporaryError);
default:
response->status = Response::Error;
return finish(ResponseStatus::PermanentError);
}
} else {

View File

@@ -43,7 +43,7 @@ void Source::load(Map& map, FileSource& fileSource) {
const std::string url = util::mapbox::normalizeSourceURL(info.url, map.getAccessToken());
fileSource.request({ Resource::Kind::JSON, url }, **map.loop, [source, &map](const Response &res) {
if (res.status != Response::Successful) {
Log::Warning(Event::General, "failed to load source TileJSON");
Log::Warning(Event::General, "Failed to load source TileJSON: %s", res.message.c_str());
return;
}
@@ -51,7 +51,7 @@ void Source::load(Map& map, FileSource& fileSource) {
d.Parse<0>(res.data.c_str());
if (d.HasParseError()) {
Log::Warning(Event::General, "invalid source TileJSON");
Log::Warning(Event::General, "Invalid source TileJSON; Parse Error at %d: %s", d.GetErrorOffset(), d.GetParseError());
return;
}

View File

@@ -82,7 +82,7 @@ void Sprite::load(FileSource& fileSource) {
sprite->parseImage();
sprite->complete();
} else {
Log::Warning(Event::Sprite, "Failed to load sprite image: Error %s", res.message.c_str());
Log::Warning(Event::Sprite, "Failed to load sprite image: %s", res.message.c_str());
if (!sprite->future.valid()) {
sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res.message)));
}

View File

@@ -10,6 +10,7 @@
#include <mbgl/util/util.hpp>
#include <mbgl/util/variant.hpp>
#include <mbgl/platform/log.hpp>
#pragma GCC diagnostic push
#ifndef __clang__

View File

@@ -44,7 +44,7 @@ TEST_F(Storage, HTTPError) {
EXPECT_LT(1.5, duration) << "Resource wasn't retried the correct number of times";
EXPECT_GT(1.7, duration) << "Resource wasn't retried the correct number of times";
EXPECT_EQ(Response::Error, res.status);
EXPECT_TRUE(res.message == "Couldn't connect to server" || res.message == "The operation couldn’t be completed. (NSURLErrorDomain error -1004.)");
EXPECT_TRUE(res.message == "Couldn't connect to server: couldn't connect to host" || res.message == "The operation couldn’t be completed. (NSURLErrorDomain error -1004.)") << res.message;
EXPECT_EQ("", res.data);
EXPECT_EQ(0, res.expires);
EXPECT_EQ(0, res.modified);