Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mapsme/omim.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--android/jni/Android.mk1
-rw-r--r--android/jni/com/mapswithme/core/ScopedEnv.hpp40
-rw-r--r--android/jni/com/mapswithme/util/HttpClient.cpp269
-rw-r--r--android/src/com/mapswithme/util/HttpClient.java55
-rw-r--r--editor/config_loader.cpp4
-rw-r--r--editor/osm_auth.cpp132
-rw-r--r--editor/user_stats.cpp10
-rw-r--r--platform/http_client.cpp165
-rw-r--r--platform/http_client.hpp236
-rw-r--r--platform/http_client_apple.mm173
-rw-r--r--platform/http_client_curl.cpp57
-rw-r--r--platform/platform.pro1
-rw-r--r--platform/platform_mac.mm3
-rw-r--r--routing/online_cross_fetcher.cpp6
-rw-r--r--xcode/platform/platform.xcodeproj/project.pbxproj4
15 files changed, 591 insertions, 565 deletions
diff --git a/android/jni/Android.mk b/android/jni/Android.mk
index 4c31824c50..aed7643bfd 100644
--- a/android/jni/Android.mk
+++ b/android/jni/Android.mk
@@ -62,6 +62,7 @@ LOCAL_HEADER_FILES := \
../../private.h \
com/mapswithme/core/jni_helper.hpp \
com/mapswithme/core/logging.hpp \
+ com/mapswithme/core/ScopedEnv.hpp \
com/mapswithme/core/ScopedLocalRef.hpp \
com/mapswithme/maps/Framework.hpp \
com/mapswithme/opengl/android_gl_utils.hpp \
diff --git a/android/jni/com/mapswithme/core/ScopedEnv.hpp b/android/jni/com/mapswithme/core/ScopedEnv.hpp
new file mode 100644
index 0000000000..d87b51419f
--- /dev/null
+++ b/android/jni/com/mapswithme/core/ScopedEnv.hpp
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <jni.h>
+
+// Scoped environment which can attach to any thread and automatically detach
+class ScopedEnv final
+{
+public:
+ ScopedEnv(JavaVM * vm)
+ {
+ JNIEnv * env;
+ auto result = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
+ if (result == JNI_EDETACHED)
+ {
+ result = vm->AttachCurrentThread(&env, nullptr);
+ m_needToDetach = (result == JNI_OK);
+ }
+
+ if (result == JNI_OK)
+ {
+ m_env = env;
+ m_vm = vm;
+ }
+ }
+
+ ~ScopedEnv()
+ {
+ if (m_vm != nullptr && m_needToDetach)
+ m_vm->DetachCurrentThread();
+ }
+
+ JNIEnv * operator->() { return m_env; }
+ operator bool() const { return m_env != nullptr; }
+ JNIEnv * get() { return m_env; }
+
+private:
+ bool m_needToDetach = false;
+ JNIEnv * m_env = nullptr;
+ JavaVM * m_vm = nullptr;
+};
diff --git a/android/jni/com/mapswithme/util/HttpClient.cpp b/android/jni/com/mapswithme/util/HttpClient.cpp
index 42b90697de..ccb2c74977 100644
--- a/android/jni/com/mapswithme/util/HttpClient.cpp
+++ b/android/jni/com/mapswithme/util/HttpClient.cpp
@@ -24,67 +24,97 @@ SOFTWARE.
#include <jni.h>
#include "../core/jni_helper.hpp"
+#include "../core/ScopedEnv.hpp"
+#include "../core/ScopedLocalRef.hpp"
#include "platform/http_client.hpp"
-#include "base/logging.hpp"
#include "base/assert.hpp"
+#include "base/exception.hpp"
+#include "base/logging.hpp"
#include "std/string.hpp"
+#include "std/unordered_map.hpp"
+
+DECLARE_EXCEPTION(JniException, RootException);
+
+#define CLEAR_AND_RETURN_FALSE_ON_EXCEPTION \
+ if (env->ExceptionCheck()) \
+ { \
+ env->ExceptionDescribe(); \
+ env->ExceptionClear(); \
+ return false; \
+ }
namespace
{
-template <typename T, typename D>
-unique_ptr<T, D> MakeScopedPointer(T * ptr, D deleter)
+void RethrowOnJniException(ScopedEnv & env)
+{
+ if (!env->ExceptionCheck())
+ return;
+
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ MYTHROW(JniException, ());
+}
+
+jfieldID GetHttpParamsFieldId(ScopedEnv & env, const char * name)
{
- return unique_ptr<T, D>(ptr, deleter);
+ return env->GetFieldID(g_httpParamsClazz, name, "Ljava/lang/String;");
}
-// Scoped environment which can attach to any thread and automatically detach
-class ScopedEnv final
+// Set string value to HttpClient.Params object, throws JniException and
+void SetString(ScopedEnv & env, jobject params, jfieldID const fieldId, string const & value)
+{
+ if (value.empty())
+ return;
+
+ jni::ScopedLocalRef<jstring> const wrappedValue(env.get(), jni::ToJavaString(env.get(), value));
+ RethrowOnJniException(env);
+
+ env->SetObjectField(params, fieldId, wrappedValue.get());
+ RethrowOnJniException(env);
+}
+
+// Get string value from HttpClient.Params object, throws JniException.
+void GetString(ScopedEnv & env, jobject const params, jfieldID const fieldId, string & result)
+{
+ jni::ScopedLocalRef<jstring> const wrappedValue(
+ env.get(), static_cast<jstring>(env->GetObjectField(params, fieldId)));
+ RethrowOnJniException(env);
+ if (wrappedValue)
+ result = jni::ToNativeString(env.get(), wrappedValue.get());
+}
+
+class Ids
{
public:
- ScopedEnv(JavaVM * vm)
+ explicit Ids(ScopedEnv & env)
{
- JNIEnv * env;
- auto result = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
- if (result == JNI_EDETACHED)
- {
- result = vm->AttachCurrentThread(&env, nullptr);
- m_needToDetach = (result == JNI_OK);
- }
-
- if (result == JNI_OK)
- {
- m_env = env;
- m_vm = vm;
- }
+ m_fieldIds =
+ {{"httpMethod", GetHttpParamsFieldId(env, "httpMethod")},
+ {"contentType", GetHttpParamsFieldId(env, "contentType")},
+ {"contentEncoding", GetHttpParamsFieldId(env, "contentEncoding")},
+ {"userAgent", GetHttpParamsFieldId(env, "userAgent")},
+ {"inputFilePath", GetHttpParamsFieldId(env, "inputFilePath")},
+ {"basicAuthUser", GetHttpParamsFieldId(env, "basicAuthUser")},
+ {"basicAuthPassword", GetHttpParamsFieldId(env, "basicAuthPassword")},
+ {"cookies", GetHttpParamsFieldId(env, "cookies")},
+ {"receivedUrl", GetHttpParamsFieldId(env, "receivedUrl")}};
}
- ~ScopedEnv()
+ jfieldID GetId(string const & fieldName) const
{
- if (m_vm != nullptr && m_needToDetach)
- m_vm->DetachCurrentThread();
+ auto const it = m_fieldIds.find(fieldName);
+ CHECK(it != m_fieldIds.end(), ("Incorrect field name:", fieldName));
+ return it->second;
}
- JNIEnv * operator->() { return m_env; }
- operator bool() const { return m_env != nullptr; }
- JNIEnv * get() { return m_env; }
-
private:
- bool m_needToDetach = false;
- JNIEnv * m_env = nullptr;
- JavaVM * m_vm = nullptr;
+ unordered_map<string, jfieldID> m_fieldIds;
};
} // namespace
-#define CLEAR_AND_RETURN_FALSE_ON_EXCEPTION \
- if (env->ExceptionCheck()) { \
- env->ExceptionDescribe(); \
- env->ExceptionClear(); \
- return false; \
- }
-
//***********************************************************************
// Exported functions implementation
//***********************************************************************
@@ -97,24 +127,26 @@ bool HttpClient::RunHttpRequest()
if (!env)
return false;
- auto const deleter = [&env](jobject o) { env->DeleteLocalRef(o); };
+ static Ids ids(env);
// Create and fill request params.
- auto const jniUrl = MakeScopedPointer(jni::ToJavaString(env.get(), m_urlRequested), deleter);
+ jni::ScopedLocalRef<jstring> const jniUrl(env.get(),
+ jni::ToJavaString(env.get(), m_urlRequested));
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
static jmethodID const httpParamsConstructor =
env->GetMethodID(g_httpParamsClazz, "<init>", "(Ljava/lang/String;)V");
- auto const httpParamsObject =
- MakeScopedPointer(env->NewObject(g_httpParamsClazz, httpParamsConstructor, jniUrl.get()), deleter);
+ jni::ScopedLocalRef<jobject> const httpParamsObject(
+ env.get(), env->NewObject(g_httpParamsClazz, httpParamsConstructor, jniUrl.get()));
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
// Cache it on the first call.
static jfieldID const dataField = env->GetFieldID(g_httpParamsClazz, "data", "[B");
if (!m_bodyData.empty())
{
- auto const jniPostData = MakeScopedPointer(env->NewByteArray(m_bodyData.size()), deleter);
+ jni::ScopedLocalRef<jbyteArray> const jniPostData(env.get(),
+ env->NewByteArray(m_bodyData.size()));
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
env->SetByteArrayRegion(jniPostData.get(), 0, m_bodyData.size(),
@@ -126,108 +158,22 @@ bool HttpClient::RunHttpRequest()
}
ASSERT(!m_httpMethod.empty(), ("Http method type can not be empty."));
- static jfieldID const httpMethodField =
- env->GetFieldID(g_httpParamsClazz, "httpMethod", "Ljava/lang/String;");
- {
- const auto jniHttpMethod = MakeScopedPointer(jni::ToJavaString(env.get(), m_httpMethod), deleter);
- CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
-
- env->SetObjectField(httpParamsObject.get(), httpMethodField, jniHttpMethod.get());
- CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
- }
- static jfieldID const contentTypeField =
- env->GetFieldID(g_httpParamsClazz, "contentType", "Ljava/lang/String;");
- if (!m_contentType.empty())
+ try
{
- auto const jniContentType = MakeScopedPointer(jni::ToJavaString(env.get(), m_contentType), deleter);
- CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
-
- env->SetObjectField(httpParamsObject.get(), contentTypeField, jniContentType.get());
- CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
- }
-
- static jfieldID const contentEncodingField =
- env->GetFieldID(g_httpParamsClazz, "contentEncoding", "Ljava/lang/String;");
- if (!m_contentEncoding.empty())
- {
- auto const jniContentEncoding = MakeScopedPointer(jni::ToJavaString(env.get(), m_contentEncoding), deleter);
- CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
-
- env->SetObjectField(httpParamsObject.get(), contentEncodingField, jniContentEncoding.get());
- CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
+ SetString(env, httpParamsObject.get(), ids.GetId("httpMethod"), m_httpMethod);
+ SetString(env, httpParamsObject.get(), ids.GetId("contentType"), m_contentType);
+ SetString(env, httpParamsObject.get(), ids.GetId("contentEncoding"), m_contentEncoding);
+ SetString(env, httpParamsObject.get(), ids.GetId("userAgent"), m_userAgent);
+ SetString(env, httpParamsObject.get(), ids.GetId("inputFilePath"), m_inputFile);
+ SetString(env, httpParamsObject.get(), ids.GetId("outputFilePath"), m_outputFile);
+ SetString(env, httpParamsObject.get(), ids.GetId("basicAuthUser"), m_basicAuthUser);
+ SetString(env, httpParamsObject.get(), ids.GetId("basicAuthPassword"), m_basicAuthPassword);
+ SetString(env, httpParamsObject.get(), ids.GetId("cookies"), m_cookies);
}
-
- if (!m_userAgent.empty())
+ catch (JniException const & ex)
{
- static jfieldID const userAgentField =
- env->GetFieldID(g_httpParamsClazz, "userAgent", "Ljava/lang/String;");
-
- auto const jniUserAgent = MakeScopedPointer(jni::ToJavaString(env.get(), m_userAgent), deleter);
- CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
-
- env->SetObjectField(httpParamsObject.get(), userAgentField, jniUserAgent.get());
- CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
- }
-
- if (!m_bodyFile.empty())
- {
- static jfieldID const inputFilePathField =
- env->GetFieldID(g_httpParamsClazz, "inputFilePath", "Ljava/lang/String;");
-
- auto const jniInputFilePath = MakeScopedPointer(jni::ToJavaString(env.get(), m_bodyFile), deleter);
- CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
-
- env->SetObjectField(httpParamsObject.get(), inputFilePathField, jniInputFilePath.get());
- CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
- }
-
- if (!m_receivedFile.empty())
- {
- static jfieldID const outputFilePathField =
- env->GetFieldID(g_httpParamsClazz, "outputFilePath", "Ljava/lang/String;");
-
- auto const jniOutputFilePath = MakeScopedPointer(jni::ToJavaString(env.get(), m_receivedFile), deleter);
- CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
-
- env->SetObjectField(httpParamsObject.get(), outputFilePathField, jniOutputFilePath.get());
- CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
- }
-
- if (!m_basicAuthUser.empty())
- {
- static jfieldID const basicAuthUserField =
- env->GetFieldID(g_httpParamsClazz, "basicAuthUser", "Ljava/lang/String;");
-
- auto const jniBasicAuthUser = MakeScopedPointer(jni::ToJavaString(env.get(), m_basicAuthUser), deleter);
- CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
-
- env->SetObjectField(httpParamsObject.get(), basicAuthUserField, jniBasicAuthUser.get());
- CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
- }
-
- if (!m_basicAuthPassword.empty())
- {
- static jfieldID const basicAuthPasswordField =
- env->GetFieldID(g_httpParamsClazz, "basicAuthPassword", "Ljava/lang/String;");
-
- auto const jniBasicAuthPassword =
- MakeScopedPointer(jni::ToJavaString(env.get(), m_basicAuthPassword), deleter);
- CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
-
- env->SetObjectField(httpParamsObject.get(), basicAuthPasswordField, jniBasicAuthPassword.get());
- CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
- }
-
- static jfieldID const cookiesField =
- env->GetFieldID(g_httpParamsClazz, "cookies", "Ljava/lang/String;");
- if (!m_cookies.empty())
- {
- const auto jniCookies = MakeScopedPointer(jni::ToJavaString(env.get(), m_cookies), deleter);
- CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
-
- env->SetObjectField(httpParamsObject.get(), cookiesField, jniCookies.get());
- CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
+ return false;
}
static jfieldID const debugModeField = env->GetFieldID(g_httpParamsClazz, "debugMode", "Z");
@@ -254,40 +200,31 @@ bool HttpClient::RunHttpRequest()
m_errorCode = env->GetIntField(response, httpResponseCodeField);
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
- static jfieldID const receivedUrlField =
- env->GetFieldID(g_httpParamsClazz, "receivedUrl", "Ljava/lang/String;");
- auto const jniReceivedUrl =
- MakeScopedPointer(static_cast<jstring>(env->GetObjectField(response, receivedUrlField)), deleter);
- CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
- if (jniReceivedUrl)
- m_urlReceived = jni::ToNativeString(env.get(), jniReceivedUrl.get());
-
- // contentTypeField is already cached above.
- auto const jniContentType =
- MakeScopedPointer(static_cast<jstring>(env->GetObjectField(response, contentTypeField)), deleter);
- CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
- if (jniContentType)
- m_contentTypeReceived = jni::ToNativeString(env.get(), jniContentType.get());
-
- // contentEncodingField is already cached above.
- auto const jniContentEncoding =
- MakeScopedPointer(static_cast<jstring>(env->GetObjectField(response, contentEncodingField)), deleter);
- CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
- if (jniContentEncoding)
- m_contentEncodingReceived = jni::ToNativeString(env.get(), jniContentEncoding.get());
+ try
+ {
+ GetString(env, response, ids.GetId("receivedUrl"), m_urlReceived);
+ GetString(env, response, ids.GetId("contentType"), m_contentTypeReceived);
+ GetString(env, response, ids.GetId("contentEncoding"), m_contentEncodingReceived);
+ }
+ catch (JniException const & ex)
+ {
+ return false;
+ }
// Note: cookies field is used not only to send Cookie header, but also to receive back
// Server-Cookie header. CookiesField is already cached above.
- auto const jniServerCookies =
- MakeScopedPointer(static_cast<jstring>(env->GetObjectField(response, cookiesField)), deleter);
+ jni::ScopedLocalRef<jstring> const jniServerCookies(
+ env.get(), static_cast<jstring>(env->GetObjectField(response, ids.GetId("cookies"))));
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
if (jniServerCookies)
+ {
m_serverCookies =
- normalize_server_cookies(std::move(jni::ToNativeString(env.get(), jniServerCookies.get())));
+ NormalizeServerCookies(jni::ToNativeString(env.get(), jniServerCookies.get()));
+ }
// dataField is already cached above.
- auto const jniData =
- MakeScopedPointer(static_cast<jbyteArray>(env->GetObjectField(response, dataField)), deleter);
+ jni::ScopedLocalRef<jbyteArray> const jniData(
+ env.get(), static_cast<jbyteArray>(env->GetObjectField(response, dataField)));
CLEAR_AND_RETURN_FALSE_ON_EXCEPTION
if (jniData)
{
diff --git a/android/src/com/mapswithme/util/HttpClient.java b/android/src/com/mapswithme/util/HttpClient.java
index ed7d5833a4..3f3510ad97 100644
--- a/android/src/com/mapswithme/util/HttpClient.java
+++ b/android/src/com/mapswithme/util/HttpClient.java
@@ -24,6 +24,8 @@
package com.mapswithme.util;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
@@ -48,10 +50,10 @@ public final class HttpClient
// Globally accessible for faster unit-testing
public static int TIMEOUT_IN_MILLISECONDS = 30000;
- public static Params run(final Params p) throws IOException, NullPointerException
+ public static Params run(@NonNull final Params p) throws IOException, NullPointerException
{
- if (p.httpMethod == null)
- throw new NullPointerException("Please set valid HTTP method for request at Params.httpMethod field.");
+ if (TextUtils.isEmpty(p.httpMethod))
+ throw new IllegalArgumentException("Please set valid HTTP method for request at Params.httpMethod field.");
HttpURLConnection connection = null;
if (p.debugMode)
@@ -79,21 +81,21 @@ public final class HttpClient
connection.setReadTimeout(TIMEOUT_IN_MILLISECONDS);
connection.setUseCaches(false);
connection.setRequestMethod(p.httpMethod);
- if (p.basicAuthUser != null)
+ if (!TextUtils.isEmpty(p.basicAuthUser))
{
final String encoded = Base64.encodeToString((p.basicAuthUser + ":" + p.basicAuthPassword).getBytes(), Base64.NO_WRAP);
connection.setRequestProperty("Authorization", "Basic " + encoded);
}
- if (p.userAgent != null)
+ if (!TextUtils.isEmpty(p.userAgent))
connection.setRequestProperty("User-Agent", p.userAgent);
- if (p.cookies != null)
+ if (!TextUtils.isEmpty(p.cookies))
connection.setRequestProperty("Cookie", p.cookies);
- if (p.inputFilePath != null || p.data != null)
+ if (!TextUtils.isEmpty(p.inputFilePath) || p.data != null)
{
// Send (POST, PUT...) data to the server.
- if (p.contentType == null)
+ if (TextUtils.isEmpty(p.contentType))
throw new NullPointerException("Please set Content-Type for request.");
// Work-around for situation when more than one consequent POST requests can lead to stable
@@ -101,7 +103,7 @@ public final class HttpClient
// The only found reference to this bug is http://stackoverflow.com/a/24303115/1209392
connection.setRequestProperty("Connection", "close");
connection.setRequestProperty("Content-Type", p.contentType);
- if (p.contentEncoding != null)
+ if (!TextUtils.isEmpty(p.contentEncoding))
connection.setRequestProperty("Content-Encoding", p.contentEncoding);
connection.setDoOutput(true);
@@ -153,19 +155,14 @@ public final class HttpClient
final Map<String, List<String>> headers = connection.getHeaderFields();
if (headers != null && headers.containsKey("Set-Cookie"))
{
- p.cookies = "";
- for (final String value : headers.get("Set-Cookie"))
- {
- // Multiple Set-Cookie headers are normalized in C++ code.
- if (value != null)
- p.cookies += value + ", ";
- }
+ // Multiple Set-Cookie headers are normalized in C++ code.
+ android.text.TextUtils.join(", ", headers.get("Set-Cookie"));
}
// This implementation receives any data only if we have HTTP::OK (200).
if (p.httpResponseCode == HttpURLConnection.HTTP_OK)
{
OutputStream ostream;
- if (p.outputFilePath != null)
+ if (!TextUtils.isEmpty(p.outputFilePath))
ostream = new BufferedOutputStream(new FileOutputStream(p.outputFilePath), STREAM_BUFFER_SIZE);
else
ostream = new ByteArrayOutputStream(STREAM_BUFFER_SIZE);
@@ -196,26 +193,26 @@ public final class HttpClient
public static class Params
{
- public String url = null;
+ public String url;
// Can be different from url in case of redirects.
- public String receivedUrl = null;
- public String httpMethod = null;
+ public String receivedUrl;
+ public String httpMethod;
// Should be specified for any request whose method allows non-empty body.
// On return, contains received Content-Type or null.
- public String contentType = null;
+ public String contentType;
// Can be specified for any request whose method allows non-empty body.
// On return, contains received Content-Encoding or null.
- public String contentEncoding = null;
- public byte[] data = null;
+ public String contentEncoding;
+ public byte[] data;
// Send from input file if specified instead of data.
- public String inputFilePath = null;
+ public String inputFilePath;
// Received data is stored here if not null or in data otherwise.
- public String outputFilePath = null;
+ public String outputFilePath;
// Optionally client can override default HTTP User-Agent.
- public String userAgent = null;
- public String basicAuthUser = null;
- public String basicAuthPassword = null;
- public String cookies = null;
+ public String userAgent;
+ public String basicAuthUser;
+ public String basicAuthPassword;
+ public String cookies;
public int httpResponseCode = -1;
public boolean debugMode = false;
public boolean followRedirects = true;
diff --git a/editor/config_loader.cpp b/editor/config_loader.cpp
index 6bd94f3e58..b5ef7241a4 100644
--- a/editor/config_loader.cpp
+++ b/editor/config_loader.cpp
@@ -39,9 +39,9 @@ string RunSimpleHttpRequest(string const & url)
LOG(LWARNING, ("Exception from HttpClient::RunHttpRequest, message: ", ex.what()));
}
- if (result && !request.was_redirected() && request.error_code() == 200) // 200 - http status OK
+ if (result && !request.WasRedirected() && request.ErrorCode() == 200) // 200 - http status OK
{
- return request.server_response();
+ return request.ServerResponse();
}
return {};
diff --git a/editor/osm_auth.cpp b/editor/osm_auth.cpp
index b85ee31929..b6a27d10b0 100644
--- a/editor/osm_auth.cpp
+++ b/editor/osm_auth.cpp
@@ -53,18 +53,6 @@ string BuildPostRequest(map<string, string> const & params)
}
return result;
}
-
-// TODO(AlexZ): DebugPrint doesn't detect this overload. Fix it.
-string DP(HttpClient const & request)
-{
- string str = "HTTP " + strings::to_string(request.error_code()) + " url [" + request.url_requested() + "]";
- if (request.was_redirected())
- str += " was redirected to [" + request.url_received() + "]";
- if (!request.server_response().empty())
- str += " response: " + request.server_response();
- return str;
-}
-
} // namespace
// static
@@ -136,25 +124,25 @@ OsmOAuth::SessionID OsmOAuth::FetchSessionId(string const & subUrl) const
HttpClient request(url);
if (!request.RunHttpRequest())
MYTHROW(NetworkError, ("FetchSessionId Network error while connecting to", url));
- if (request.was_redirected())
- MYTHROW(UnexpectedRedirect, ("Redirected to", request.url_received(), "from", url));
- if (request.error_code() != HTTP::OK)
- MYTHROW(FetchSessionIdError, (DP(request)));
+ if (request.WasRedirected())
+ MYTHROW(UnexpectedRedirect, ("Redirected to", request.UrlReceived(), "from", url));
+ if (request.ErrorCode() != HTTP::OK)
+ MYTHROW(FetchSessionIdError, (DebugPrint(request)));
- SessionID const sid = { request.combined_cookies(), FindAuthenticityToken(request.server_response()) };
+ SessionID const sid = { request.CombinedCookies(), FindAuthenticityToken(request.ServerResponse()) };
if (sid.m_cookies.empty() || sid.m_token.empty())
- MYTHROW(FetchSessionIdError, ("Cookies and/or token are empty for request", DP(request)));
+ MYTHROW(FetchSessionIdError, ("Cookies and/or token are empty for request", DebugPrint(request)));
return sid;
}
void OsmOAuth::LogoutUser(SessionID const & sid) const
{
HttpClient request(m_baseUrl + "/logout");
- request.set_cookies(sid.m_cookies);
+ request.SetCookies(sid.m_cookies);
if (!request.RunHttpRequest())
- MYTHROW(NetworkError, ("LogoutUser Network error while connecting to", request.url_requested()));
- if (request.error_code() != HTTP::OK)
- MYTHROW(LogoutUserError, (DP(request)));
+ MYTHROW(NetworkError, ("LogoutUser Network error while connecting to", request.UrlRequested()));
+ if (request.ErrorCode() != HTTP::OK)
+ MYTHROW(LogoutUserError, (DebugPrint(request)));
}
bool OsmOAuth::LoginUserPassword(string const & login, string const & password, SessionID const & sid) const
@@ -168,50 +156,50 @@ bool OsmOAuth::LoginUserPassword(string const & login, string const & password,
{"authenticity_token", sid.m_token}
};
HttpClient request(m_baseUrl + "/login");
- request.set_body_data(BuildPostRequest(params), "application/x-www-form-urlencoded")
- .set_cookies(sid.m_cookies)
- .set_handle_redirects(false);
+ request.SetBodyData(BuildPostRequest(params), "application/x-www-form-urlencoded")
+ .SetCookies(sid.m_cookies)
+ .SetHandleRedirects(false);
if (!request.RunHttpRequest())
- MYTHROW(NetworkError, ("LoginUserPassword Network error while connecting to", request.url_requested()));
+ MYTHROW(NetworkError, ("LoginUserPassword Network error while connecting to", request.UrlRequested()));
// At the moment, automatic redirects handling is buggy on Androids < 4.4.
// set_handle_redirects(false) works only for Android code, iOS one (and curl) still automatically follow all redirects.
- if (request.error_code() != HTTP::OK && request.error_code() != HTTP::Found)
- MYTHROW(LoginUserPasswordServerError, (DP(request)));
+ if (request.ErrorCode() != HTTP::OK && request.ErrorCode() != HTTP::Found)
+ MYTHROW(LoginUserPasswordServerError, (DebugPrint(request)));
// Not redirected page is a 100% signal that login and/or password are invalid.
- if (!request.was_redirected())
+ if (!request.WasRedirected())
return false;
// Check if we were redirected to some 3rd party site.
- if (request.url_received().find(m_baseUrl) != 0)
- MYTHROW(UnexpectedRedirect, (DP(request)));
+ if (request.UrlReceived().find(m_baseUrl) != 0)
+ MYTHROW(UnexpectedRedirect, (DebugPrint(request)));
// m_baseUrl + "/login" means login and/or password are invalid.
- return request.server_response().find("/login") == string::npos;
+ return request.ServerResponse().find("/login") == string::npos;
}
bool OsmOAuth::LoginSocial(string const & callbackPart, string const & socialToken, SessionID const & sid) const
{
string const url = m_baseUrl + callbackPart + socialToken;
HttpClient request(url);
- request.set_cookies(sid.m_cookies)
- .set_handle_redirects(false);
+ request.SetCookies(sid.m_cookies)
+ .SetHandleRedirects(false);
if (!request.RunHttpRequest())
- MYTHROW(NetworkError, ("LoginSocial Network error while connecting to", request.url_requested()));
- if (request.error_code() != HTTP::OK && request.error_code() != HTTP::Found)
- MYTHROW(LoginSocialServerError, (DP(request)));
+ MYTHROW(NetworkError, ("LoginSocial Network error while connecting to", request.UrlRequested()));
+ if (request.ErrorCode() != HTTP::OK && request.ErrorCode() != HTTP::Found)
+ MYTHROW(LoginSocialServerError, (DebugPrint(request)));
// Not redirected page is a 100% signal that social login has failed.
- if (!request.was_redirected())
+ if (!request.WasRedirected())
return false;
// Check if we were redirected to some 3rd party site.
- if (request.url_received().find(m_baseUrl) != 0)
- MYTHROW(UnexpectedRedirect, (DP(request)));
+ if (request.UrlReceived().find(m_baseUrl) != 0)
+ MYTHROW(UnexpectedRedirect, (DebugPrint(request)));
// m_baseUrl + "/login" means login and/or password are invalid.
- return request.server_response().find("/login") == string::npos;
+ return request.ServerResponse().find("/login") == string::npos;
}
// Fakes a buttons press to automatically accept requested permissions.
@@ -229,17 +217,17 @@ string OsmOAuth::SendAuthRequest(string const & requestTokenKey, SessionID const
{"commit", "Save changes"}
};
HttpClient request(m_baseUrl + "/oauth/authorize");
- request.set_body_data(BuildPostRequest(params), "application/x-www-form-urlencoded")
- .set_cookies(sid.m_cookies)
- .set_handle_redirects(false);
+ request.SetBodyData(BuildPostRequest(params), "application/x-www-form-urlencoded")
+ .SetCookies(sid.m_cookies)
+ .SetHandleRedirects(false);
if (!request.RunHttpRequest())
- MYTHROW(NetworkError, ("SendAuthRequest Network error while connecting to", request.url_requested()));
+ MYTHROW(NetworkError, ("SendAuthRequest Network error while connecting to", request.UrlRequested()));
- string const callbackURL = request.url_received();
+ string const callbackURL = request.UrlReceived();
string const vKey = "oauth_verifier=";
auto const pos = callbackURL.find(vKey);
if (pos == string::npos)
- MYTHROW(SendAuthRequestError, ("oauth_verifier is not found", DP(request)));
+ MYTHROW(SendAuthRequestError, ("oauth_verifier is not found", DebugPrint(request)));
auto const end = callbackURL.find("&", pos);
return callbackURL.substr(pos + vKey.length(), end == string::npos ? end : end - pos - vKey.length());
@@ -253,14 +241,14 @@ TRequestToken OsmOAuth::FetchRequestToken() const
string const requestTokenQuery = oauth.getURLQueryString(OAuth::Http::Get, requestTokenUrl + "?oauth_callback=oob");
HttpClient request(requestTokenUrl + "?" + requestTokenQuery);
if (!request.RunHttpRequest())
- MYTHROW(NetworkError, ("FetchRequestToken Network error while connecting to", request.url_requested()));
- if (request.error_code() != HTTP::OK)
- MYTHROW(FetchRequestTokenServerError, (DP(request)));
- if (request.was_redirected())
- MYTHROW(UnexpectedRedirect, ("Redirected to", request.url_received(), "from", request.url_requested()));
+ MYTHROW(NetworkError, ("FetchRequestToken Network error while connecting to", request.UrlRequested()));
+ if (request.ErrorCode() != HTTP::OK)
+ MYTHROW(FetchRequestTokenServerError, (DebugPrint(request)));
+ if (request.WasRedirected())
+ MYTHROW(UnexpectedRedirect, ("Redirected to", request.UrlReceived(), "from", request.UrlRequested()));
// Throws std::runtime_error.
- OAuth::Token const reqToken = OAuth::Token::extract(request.server_response());
+ OAuth::Token const reqToken = OAuth::Token::extract(request.ServerResponse());
return { reqToken.key(), reqToken.secret() };
}
@@ -273,13 +261,13 @@ TKeySecret OsmOAuth::FinishAuthorization(TRequestToken const & requestToken, str
string const queryString = oauth.getURLQueryString(OAuth::Http::Get, accessTokenUrl, "", true);
HttpClient request(accessTokenUrl + "?" + queryString);
if (!request.RunHttpRequest())
- MYTHROW(NetworkError, ("FinishAuthorization Network error while connecting to", request.url_requested()));
- if (request.error_code() != HTTP::OK)
- MYTHROW(FinishAuthorizationServerError, (DP(request)));
- if (request.was_redirected())
- MYTHROW(UnexpectedRedirect, ("Redirected to", request.url_received(), "from", request.url_requested()));
+ MYTHROW(NetworkError, ("FinishAuthorization Network error while connecting to", request.UrlRequested()));
+ if (request.ErrorCode() != HTTP::OK)
+ MYTHROW(FinishAuthorizationServerError, (DebugPrint(request)));
+ if (request.WasRedirected())
+ MYTHROW(UnexpectedRedirect, ("Redirected to", request.UrlReceived(), "from", request.UrlRequested()));
- OAuth::KeyValuePairs const responseData = OAuth::ParseKeyValuePairs(request.server_response());
+ OAuth::KeyValuePairs const responseData = OAuth::ParseKeyValuePairs(request.ServerResponse());
// Throws std::runtime_error.
OAuth::Token const accessToken = OAuth::Token::extract(responseData);
return { accessToken.key(), accessToken.secret() };
@@ -352,15 +340,15 @@ bool OsmOAuth::ResetPassword(string const & email) const
{"commit", "Reset password"}
};
HttpClient request(m_baseUrl + kForgotPasswordUrlPart);
- request.set_body_data(BuildPostRequest(params), "application/x-www-form-urlencoded");
- request.set_cookies(sid.m_cookies);
+ request.SetBodyData(BuildPostRequest(params), "application/x-www-form-urlencoded");
+ request.SetCookies(sid.m_cookies);
if (!request.RunHttpRequest())
- MYTHROW(NetworkError, ("ResetPassword Network error while connecting to", request.url_requested()));
- if (request.error_code() != HTTP::OK)
- MYTHROW(ResetPasswordServerError, (DP(request)));
+ MYTHROW(NetworkError, ("ResetPassword Network error while connecting to", request.UrlRequested()));
+ if (request.ErrorCode() != HTTP::OK)
+ MYTHROW(ResetPasswordServerError, (DebugPrint(request)));
- if (request.was_redirected() && request.url_received().find(m_baseUrl) != string::npos)
+ if (request.WasRedirected() && request.UrlReceived().find(m_baseUrl) != string::npos)
return true;
return false;
}
@@ -394,13 +382,13 @@ OsmOAuth::Response OsmOAuth::Request(string const & method, string const & httpM
HttpClient request(url + "?" + query);
if (httpMethod != "GET")
- request.set_body_data(body, "application/xml", httpMethod);
+ request.SetBodyData(body, "application/xml", httpMethod);
if (!request.RunHttpRequest())
MYTHROW(NetworkError, ("Request Network error while connecting to", url));
- if (request.was_redirected())
- MYTHROW(UnexpectedRedirect, ("Redirected to", request.url_received(), "from", url));
+ if (request.WasRedirected())
+ MYTHROW(UnexpectedRedirect, ("Redirected to", request.UrlReceived(), "from", url));
- return Response(request.error_code(), request.server_response());
+ return Response(request.ErrorCode(), request.ServerResponse());
}
OsmOAuth::Response OsmOAuth::DirectRequest(string const & method, bool api) const
@@ -409,10 +397,10 @@ OsmOAuth::Response OsmOAuth::DirectRequest(string const & method, bool api) cons
HttpClient request(url);
if (!request.RunHttpRequest())
MYTHROW(NetworkError, ("DirectRequest Network error while connecting to", url));
- if (request.was_redirected())
- MYTHROW(UnexpectedRedirect, ("Redirected to", request.url_received(), "from", url));
+ if (request.WasRedirected())
+ MYTHROW(UnexpectedRedirect, ("Redirected to", request.UrlReceived(), "from", url));
- return Response(request.error_code(), request.server_response());
+ return Response(request.ErrorCode(), request.ServerResponse());
}
string DebugPrint(OsmOAuth::Response const & code)
diff --git a/editor/user_stats.cpp b/editor/user_stats.cpp
index 059a3b35df..3a71ea9884 100644
--- a/editor/user_stats.cpp
+++ b/editor/user_stats.cpp
@@ -12,8 +12,6 @@
#include "3party/pugixml/src/pugixml.hpp"
-using TRequest = platform::HttpClient;
-
namespace
{
string const kUserStatsUrl = "https://editor-api.maps.me/user?format=xml";
@@ -91,7 +89,7 @@ bool UserStatsLoader::Update(string const & userName)
}
auto const url = kUserStatsUrl + "&name=" + UrlEncode(userName);
- TRequest request(url);
+ platform::HttpClient request(url);
if (!request.RunHttpRequest())
{
@@ -99,13 +97,13 @@ bool UserStatsLoader::Update(string const & userName)
return false;
}
- if (request.error_code() != 200)
+ if (request.ErrorCode() != 200)
{
- LOG(LWARNING, ("Server returned", request.error_code(), "for url", url));
+ LOG(LWARNING, ("Server returned", request.ErrorCode(), "for url", url));
return false;
}
- auto const response = request.server_response();
+ auto const response = request.ServerResponse();
pugi::xml_document document;
if (!document.load_buffer(response.data(), response.size()))
diff --git a/platform/http_client.cpp b/platform/http_client.cpp
new file mode 100644
index 0000000000..dcd1727b69
--- /dev/null
+++ b/platform/http_client.cpp
@@ -0,0 +1,165 @@
+#include "platform/http_client.hpp"
+
+#include "base/string_utils.hpp"
+
+#include "std/sstream.hpp"
+
+namespace platform
+{
+HttpClient & HttpClient::SetDebugMode(bool debug_mode)
+{
+ m_debugMode = debug_mode;
+ return *this;
+}
+
+HttpClient & HttpClient::SetUrlRequested(string const & url)
+{
+ m_urlRequested = url;
+ return *this;
+}
+
+HttpClient & HttpClient::SetHttpMethod(string const & method)
+{
+ m_httpMethod = method;
+ return *this;
+}
+
+HttpClient & HttpClient::SetBodyFile(string const & body_file, string const & content_type,
+ string const & http_method /* = "POST" */,
+ string const & content_encoding /* = "" */)
+{
+ m_inputFile = body_file;
+ m_bodyData.clear();
+ m_contentType = content_type;
+ m_httpMethod = http_method;
+ m_contentEncoding = content_encoding;
+ return *this;
+}
+
+HttpClient & HttpClient::SetReceivedFile(string const & received_file)
+{
+ m_outputFile = received_file;
+ return *this;
+}
+
+HttpClient & HttpClient::SetUserAgent(string const & user_agent)
+{
+ m_userAgent = user_agent;
+ return *this;
+}
+
+HttpClient & HttpClient::SetUserAndPassword(string const & user, string const & password)
+{
+ m_basicAuthUser = user;
+ m_basicAuthPassword = password;
+ return *this;
+}
+
+HttpClient & HttpClient::SetCookies(string const & cookies)
+{
+ m_cookies = cookies;
+ return *this;
+}
+
+HttpClient & HttpClient::SetHandleRedirects(bool handle_redirects)
+{
+ m_handleRedirects = handle_redirects;
+ return *this;
+}
+
+string const & HttpClient::UrlRequested() const
+{
+ return m_urlRequested;
+}
+
+string const & HttpClient::UrlReceived() const
+{
+ return m_urlReceived;
+}
+
+bool HttpClient::WasRedirected() const
+{
+ return m_urlRequested != m_urlReceived;
+}
+
+int HttpClient::ErrorCode() const
+{
+ return m_errorCode;
+}
+
+string const & HttpClient::ServerResponse() const
+{
+ return m_serverResponse;
+}
+
+string const & HttpClient::HttpMethod() const
+{
+ return m_httpMethod;
+}
+
+string HttpClient::CombinedCookies() const
+{
+ if (m_serverCookies.empty())
+ return m_cookies;
+
+ if (m_cookies.empty())
+ return m_serverCookies;
+
+ return m_serverCookies + "; " + m_cookies;
+}
+
+string HttpClient::CookieByName(string name) const
+{
+ string const str = CombinedCookies();
+ name += "=";
+ auto const cookie = str.find(name);
+ auto const eq = cookie + name.size();
+ if (cookie != string::npos && str.size() > eq)
+ return str.substr(eq, str.find(';', eq) - eq);
+
+ return {};
+}
+
+// static
+string HttpClient::NormalizeServerCookies(string && cookies)
+{
+ istringstream is(cookies);
+ string str, result;
+
+ // Split by ", ". Can have invalid tokens here, expires= can also contain a comma.
+ while (getline(is, str, ','))
+ {
+ size_t const leading = str.find_first_not_of(" ");
+ if (leading != string::npos)
+ str.substr(leading).swap(str);
+
+ // In the good case, we have '=' and it goes before any ' '.
+ auto const eq = str.find('=');
+ if (eq == string::npos)
+ continue; // It's not a cookie: no valid key value pair.
+
+ auto const sp = str.find(' ');
+ if (sp != string::npos && eq > sp)
+ continue; // It's not a cookie: comma in expires date.
+
+ // Insert delimiter.
+ if (!result.empty())
+ result.append("; ");
+
+ // Read cookie itself.
+ result.append(str, 0, str.find(";"));
+ }
+ return result;
+}
+
+string DebugPrint(HttpClient const & request)
+{
+ ostringstream ostr;
+ ostr << "HTTP " << request.ErrorCode() << " url [" << request.UrlRequested() << "]";
+ if (request.WasRedirected())
+ ostr << " was redirected to [" << request.UrlReceived() << "]";
+ if (!request.ServerResponse().empty())
+ ostr << " response: " << request.ServerResponse();
+ return ostr.str();
+}
+}
diff --git a/platform/http_client.hpp b/platform/http_client.hpp
index ad21a0409e..11f77084d4 100644
--- a/platform/http_client.hpp
+++ b/platform/http_client.hpp
@@ -25,212 +25,106 @@ SOFTWARE.
#include "base/macros.hpp"
-#include "std/sstream.hpp"
#include "std/string.hpp"
namespace platform
{
class HttpClient
{
- public:
- enum { kNotInitialized = -1 };
+public:
+ static auto constexpr kNoError = -1;
HttpClient() = default;
HttpClient(string const & url) : m_urlRequested(url) {}
- HttpClient & set_debug_mode(bool debug_mode)
- {
- m_debugMode = debug_mode;
- return *this;
- }
- HttpClient & set_url_requested(string const & url)
- {
- m_urlRequested = url;
- return *this;
- }
- HttpClient & set_http_method(string const & method)
- {
- m_httpMethod = method;
- return *this;
- }
+
+ // Synchronous (blocking) call, should be implemented for each platform
+ // @returns true if connection was made and server returned something (200, 404, etc.).
+ // @note Implementations should transparently support all needed HTTP redirects.
+ // Implemented for each platform.
+ bool RunHttpRequest();
+
+ // Shared methods for all platforms, implemented at http_client.cpp
+ HttpClient & SetDebugMode(bool debug_mode);
+ HttpClient & SetUrlRequested(string const & url);
+ HttpClient & SetHttpMethod(string const & method);
// This method is mutually exclusive with set_body_data().
- HttpClient & set_body_file(string const & body_file,
- string const & content_type,
- string const & http_method = "POST",
- string const & content_encoding = "")
- {
- m_bodyFile = body_file;
- m_bodyData.clear();
- m_contentType = content_type;
- m_httpMethod = http_method;
- m_contentEncoding = content_encoding;
- return *this;
- }
+ HttpClient & SetBodyFile(string const & body_file, string const & content_type,
+ string const & http_method = "POST",
+ string const & content_encoding = "");
// If set, stores server reply in file specified.
- HttpClient & set_received_file(string const & received_file)
- {
- m_receivedFile = received_file;
- return *this;
- }
- HttpClient & set_user_agent(string const & user_agent)
- {
- m_userAgent = user_agent;
- return *this;
- }
- // This method is mutually exclusive with set_body_file().
- HttpClient & set_body_data(string const & body_data,
- string const & content_type,
- string const & http_method = "POST",
- string const & content_encoding = "")
- {
- m_bodyData = body_data;
- m_bodyFile.clear();
- m_contentType = content_type;
- m_httpMethod = http_method;
- m_contentEncoding = content_encoding;
- return *this;
- }
- // Move version to avoid string copying.
+ HttpClient & SetReceivedFile(string const & received_file);
+ HttpClient & SetUserAgent(string const & user_agent);
// This method is mutually exclusive with set_body_file().
- HttpClient & set_body_data(string && body_data,
- string const & content_type,
- string const & http_method = "POST",
- string const & content_encoding = "")
+ template <typename StringT>
+ HttpClient & SetBodyData(StringT && body_data, string const & content_type,
+ string const & http_method = "POST",
+ string const & content_encoding = "")
{
- m_bodyData = move(body_data);
- m_bodyFile.clear();
+ m_bodyData = forward<StringT>(body_data);
+ m_inputFile.clear();
m_contentType = content_type;
m_httpMethod = http_method;
m_contentEncoding = content_encoding;
return *this;
}
// HTTP Basic Auth.
- HttpClient & set_user_and_password(string const & user, string const & password)
- {
- m_basicAuthUser = user;
- m_basicAuthPassword = password;
- return *this;
- }
+ HttpClient & SetUserAndPassword(string const & user, string const & password);
// Set HTTP Cookie header.
- HttpClient & set_cookies(string const & cookies)
- {
- m_cookies = cookies;
- return *this;
- }
+ HttpClient & SetCookies(string const & cookies);
// When set to true (default), clients never get 3XX codes from servers, redirects are handled automatically.
// TODO: "false" is now supported on Android only.
- HttpClient & set_handle_redirects(bool handle_redirects)
- {
- m_handleRedirects = handle_redirects;
- return *this;
- }
+ HttpClient & SetHandleRedirects(bool handle_redirects);
- // Synchronous (blocking) call, should be implemented for each platform
- // @returns true if connection was made and server returned something (200, 404, etc.).
- // @note Implementations should transparently support all needed HTTP redirects
- bool RunHttpRequest();
-
- string const & url_requested() const { return m_urlRequested; }
+ string const & UrlRequested() const;
// @returns empty string in the case of error
- string const & url_received() const { return m_urlReceived; }
- bool was_redirected() const { return m_urlRequested != m_urlReceived; }
+ string const & UrlReceived() const;
+ bool WasRedirected() const;
// Mix of HTTP errors (in case of successful connection) and system-dependent error codes,
// in the simplest success case use 'if (200 == client.error_code())' // 200 means OK in HTTP
- int error_code() const { return m_errorCode; }
- string const & server_response() const { return m_serverResponse; }
- string const & http_method() const { return m_httpMethod; }
+ int ErrorCode() const;
+ string const & ServerResponse() const;
+ string const & HttpMethod() const;
// Pass this getter's value to the set_cookies() method for easier cookies support in the next request.
- string combined_cookies() const
- {
- if (m_serverCookies.empty())
- {
- return m_cookies;
- }
- if (m_cookies.empty())
- {
- return m_serverCookies;
- }
- return m_serverCookies + "; " + m_cookies;
- }
+ string CombinedCookies() const;
// Returns cookie value or empty string if it's not present.
- string cookie_by_name(string name) const
- {
- string const str = combined_cookies();
- name += "=";
- auto const cookie = str.find(name);
- auto const eq = cookie + name.size();
- if (cookie != string::npos && str.size() > eq)
- {
- return str.substr(eq, str.find(';', eq) - eq);
- }
- return string();
- }
+ string CookieByName(string name) const;
private:
// Internal helper to convert cookies like this:
// "first=value1; expires=Mon, 26-Dec-2016 12:12:32 GMT; path=/, second=value2; path=/, third=value3; "
// into this:
// "first=value1; second=value2; third=value3"
- static string normalize_server_cookies(string && cookies)
- {
- istringstream is(cookies);
- string str, result;
- // Split by ", ". Can have invalid tokens here, expires= can also contain a comma.
- while (getline(is, str, ','))
- {
- size_t const leading = str.find_first_not_of(" ");
- if (leading != string::npos)
- {
- str.substr(leading).swap(str);
- }
- // In the good case, we have '=' and it goes before any ' '.
- auto const eq = str.find('=');
- if (eq == string::npos)
- {
- continue; // It's not a cookie: no valid key value pair.
- }
- auto const sp = str.find(' ');
- if (sp != string::npos && eq > sp)
- {
- continue; // It's not a cookie: comma in expires date.
- }
- // Insert delimiter.
- if (!result.empty())
- {
- result.append("; ");
- }
- // Read cookie itself.
- result.append(str, 0, str.find(";"));
- }
- return result;
- }
+ static string NormalizeServerCookies(string && cookies);
- string m_urlRequested;
- // Contains final content's url taking redirects (if any) into an account.
- string m_urlReceived;
- int m_errorCode = kNotInitialized;
- string m_bodyFile;
- // Used instead of server_reply_ if set.
- string m_receivedFile;
- // Data we received from the server if output_file_ wasn't initialized.
- string m_serverResponse;
- string m_contentType;
- string m_contentTypeReceived;
- string m_contentEncoding;
- string m_contentEncodingReceived;
- string m_userAgent;
- string m_bodyData;
- string m_httpMethod = "GET";
- string m_basicAuthUser;
- string m_basicAuthPassword;
- // All Set-Cookie values from server response combined in a Cookie format:
- // cookie1=value1; cookie2=value2
- // TODO(AlexZ): Support encoding and expiration/path/domains etc.
- string m_serverCookies;
- // Cookies set by the client before request is run.
- string m_cookies;
- bool m_debugMode = false;
- bool m_handleRedirects = true;
+ string m_urlRequested;
+ // Contains final content's url taking redirects (if any) into an account.
+ string m_urlReceived;
+ int m_errorCode = kNoError;
+ string m_inputFile;
+ // Used instead of server_reply_ if set.
+ string m_outputFile;
+ // Data we received from the server if output_file_ wasn't initialized.
+ string m_serverResponse;
+ string m_contentType;
+ string m_contentTypeReceived;
+ string m_contentEncoding;
+ string m_contentEncodingReceived;
+ string m_userAgent;
+ string m_bodyData;
+ string m_httpMethod = "GET";
+ string m_basicAuthUser;
+ string m_basicAuthPassword;
+ // All Set-Cookie values from server response combined in a Cookie format:
+ // cookie1=value1; cookie2=value2
+ // TODO(AlexZ): Support encoding and expiration/path/domains etc.
+ string m_serverCookies;
+ // Cookies set by the client before request is run.
+ string m_cookies;
+ bool m_debugMode = false;
+ bool m_handleRedirects = true;
- DISALLOW_COPY_AND_MOVE(HttpClient);
+ DISALLOW_COPY_AND_MOVE(HttpClient);
};
+
+string DebugPrint(HttpClient const & request);
} // namespace platform
diff --git a/platform/http_client_apple.mm b/platform/http_client_apple.mm
index 9f06f6d5af..4d063c08c2 100644
--- a/platform/http_client_apple.mm
+++ b/platform/http_client_apple.mm
@@ -54,112 +54,105 @@ static const double kTimeoutInSeconds = 24.0;
// TODO(AlexZ): Rewrite to use async implementation for better redirects handling and ability to cancel request from destructor.
bool HttpClient::RunHttpRequest()
{
- @autoreleasepool
- {
- NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:
- [NSURL URLWithString:[NSString stringWithUTF8String:m_urlRequested.c_str()]]
- cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:kTimeoutInSeconds];
- // We handle cookies manually.
- request.HTTPShouldHandleCookies = NO;
+ NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:
+ [NSURL URLWithString:[NSString stringWithUTF8String:m_urlRequested.c_str()]]
+ cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:kTimeoutInSeconds];
+ // We handle cookies manually.
+ request.HTTPShouldHandleCookies = NO;
- request.HTTPMethod = [NSString stringWithUTF8String:m_httpMethod.c_str()];
- if (!m_contentType.empty())
- [request setValue:[NSString stringWithUTF8String:m_contentType.c_str()] forHTTPHeaderField:@"Content-Type"];
+ request.HTTPMethod = [NSString stringWithUTF8String:m_httpMethod.c_str()];
+ if (!m_contentType.empty())
+ [request setValue:[NSString stringWithUTF8String:m_contentType.c_str()] forHTTPHeaderField:@"Content-Type"];
- if (!m_contentEncoding.empty())
- [request setValue:[NSString stringWithUTF8String:m_contentEncoding.c_str()] forHTTPHeaderField:@"Content-Encoding"];
+ if (!m_contentEncoding.empty())
+ [request setValue:[NSString stringWithUTF8String:m_contentEncoding.c_str()] forHTTPHeaderField:@"Content-Encoding"];
- if (!m_userAgent.empty())
- [request setValue:[NSString stringWithUTF8String:m_userAgent.c_str()] forHTTPHeaderField:@"User-Agent"];
+ if (!m_userAgent.empty())
+ [request setValue:[NSString stringWithUTF8String:m_userAgent.c_str()] forHTTPHeaderField:@"User-Agent"];
- if (!m_cookies.empty())
- [request setValue:[NSString stringWithUTF8String:m_cookies.c_str()] forHTTPHeaderField:@"Cookie"];
+ if (!m_cookies.empty())
+ [request setValue:[NSString stringWithUTF8String:m_cookies.c_str()] forHTTPHeaderField:@"Cookie"];
#if (TARGET_OS_IPHONE > 0)
- else if (gBrowserUserAgent)
- [request setValue:gBrowserUserAgent forHTTPHeaderField:@"User-Agent"];
+ else if (gBrowserUserAgent)
+ [request setValue:gBrowserUserAgent forHTTPHeaderField:@"User-Agent"];
#endif // TARGET_OS_IPHONE
- if (!m_basicAuthUser.empty())
- {
- NSData * loginAndPassword = [[NSString stringWithUTF8String:(m_basicAuthUser + ":" + m_basicAuthPassword).c_str()] dataUsingEncoding:NSUTF8StringEncoding];
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- // base64Encoding selector below was deprecated in iOS 7+, but we still need it to support 5.1+ versions.
- [request setValue:[NSString stringWithFormat:@"Basic %@", [loginAndPassword base64Encoding]] forHTTPHeaderField:@"Authorization"];
-#pragma clang diagnostic pop
- }
+ if (!m_basicAuthUser.empty())
+ {
+ NSData * loginAndPassword = [[NSString stringWithUTF8String:(m_basicAuthUser + ":" + m_basicAuthPassword).c_str()] dataUsingEncoding:NSUTF8StringEncoding];
+ [request setValue:[NSString stringWithFormat:@"Basic %@", [loginAndPassword base64EncodedStringWithOptions:0]] forHTTPHeaderField:@"Authorization"];
+ }
- if (!m_bodyData.empty())
- {
- request.HTTPBody = [NSData dataWithBytes:m_bodyData.data() length:m_bodyData.size()];
- if (m_debugMode)
- LOG(LINFO, ("Uploading buffer of size", m_bodyData.size(), "bytes"));
- }
- else if (!m_bodyFile.empty())
+ if (!m_bodyData.empty())
+ {
+ request.HTTPBody = [NSData dataWithBytes:m_bodyData.data() length:m_bodyData.size()];
+ if (m_debugMode)
+ LOG(LINFO, ("Uploading buffer of size", m_bodyData.size(), "bytes"));
+ }
+ else if (!m_inputFile.empty())
+ {
+ NSError * err = nil;
+ NSString * path = [NSString stringWithUTF8String:m_inputFile.c_str()];
+ const unsigned long long file_size = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&err].fileSize;
+ if (err)
{
- NSError * err = nil;
- NSString * path = [NSString stringWithUTF8String:m_bodyFile.c_str()];
- const unsigned long long file_size = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&err].fileSize;
- if (err)
- {
- m_errorCode = static_cast<int>(err.code);
- if (m_debugMode)
- LOG(LERROR, ("Error: ", m_errorCode, [err.localizedDescription UTF8String]));
-
- return false;
- }
- request.HTTPBodyStream = [NSInputStream inputStreamWithFileAtPath:path];
- [request setValue:[NSString stringWithFormat:@"%llu", file_size] forHTTPHeaderField:@"Content-Length"];
+ m_errorCode = static_cast<int>(err.code);
if (m_debugMode)
- LOG(LINFO, ("Uploading file", m_bodyFile, file_size, "bytes"));
+ LOG(LERROR, ("Error: ", m_errorCode, [err.localizedDescription UTF8String]));
+
+ return false;
}
+ request.HTTPBodyStream = [NSInputStream inputStreamWithFileAtPath:path];
+ [request setValue:[NSString stringWithFormat:@"%llu", file_size] forHTTPHeaderField:@"Content-Length"];
+ if (m_debugMode)
+ LOG(LINFO, ("Uploading file", m_inputFile, file_size, "bytes"));
+ }
- NSHTTPURLResponse * response = nil;
- NSError * err = nil;
- NSData * url_data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&err];
+ NSHTTPURLResponse * response = nil;
+ NSError * err = nil;
+ NSData * url_data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&err];
- if (response)
- {
- m_errorCode = static_cast<int>(response.statusCode);
- m_urlReceived = [response.URL.absoluteString UTF8String];
-
- NSString * content = [response.allHeaderFields objectForKey:@"Content-Type"];
- if (content)
- m_contentTypeReceived = std::move([content UTF8String]);
-
- NSString * encoding = [response.allHeaderFields objectForKey:@"Content-Encoding"];
- if (encoding)
- m_contentEncodingReceived = std::move([encoding UTF8String]);
-
- // Apple merges all Set-Cookie fields into one NSDictionary key delimited by commas.
- NSString * cookies = [response.allHeaderFields objectForKey:@"Set-Cookie"];
- if (cookies)
- m_serverCookies = normalize_server_cookies(std::move([cookies UTF8String]));
-
- if (url_data)
- {
- if (m_receivedFile.empty())
- m_serverResponse.assign(reinterpret_cast<char const *>(url_data.bytes), url_data.length);
- else
- [url_data writeToFile:[NSString stringWithUTF8String:m_receivedFile.c_str()] atomically:YES];
-
- }
- return true;
- }
- // Request has failed if we are here.
- // MacOSX/iOS-specific workaround for HTTP 401 error bug.
- // @see bit.ly/1TrHlcS for more details.
- if (err.code == NSURLErrorUserCancelledAuthentication)
+ if (response)
+ {
+ m_errorCode = static_cast<int>(response.statusCode);
+ m_urlReceived = [response.URL.absoluteString UTF8String];
+
+ NSString * content = [response.allHeaderFields objectForKey:@"Content-Type"];
+ if (content)
+ m_contentTypeReceived = std::move([content UTF8String]);
+
+ NSString * encoding = [response.allHeaderFields objectForKey:@"Content-Encoding"];
+ if (encoding)
+ m_contentEncodingReceived = std::move([encoding UTF8String]);
+
+ // Apple merges all Set-Cookie fields into one NSDictionary key delimited by commas.
+ NSString * cookies = [response.allHeaderFields objectForKey:@"Set-Cookie"];
+ if (cookies)
+ m_serverCookies = NormalizeServerCookies(std::move([cookies UTF8String]));
+
+ if (url_data)
{
- m_errorCode = 401;
- return true;
+ if (m_outputFile.empty())
+ m_serverResponse.assign(reinterpret_cast<char const *>(url_data.bytes), url_data.length);
+ else
+ [url_data writeToFile:[NSString stringWithUTF8String:m_outputFile.c_str()] atomically:YES];
+
}
+ return true;
+ }
+ // Request has failed if we are here.
+ // MacOSX/iOS-specific workaround for HTTP 401 error bug.
+ // @see bit.ly/1TrHlcS for more details.
+ if (err.code == NSURLErrorUserCancelledAuthentication)
+ {
+ m_errorCode = 401;
+ return true;
+ }
- m_errorCode = static_cast<int>(err.code);
- if (m_debugMode)
- LOG(LERROR, ("Error: ", m_errorCode, ':', [err.localizedDescription UTF8String], "while connecting to", m_urlRequested));
+ m_errorCode = static_cast<int>(err.code);
+ if (m_debugMode)
+ LOG(LERROR, ("Error: ", m_errorCode, ':', [err.localizedDescription UTF8String], "while connecting to", m_urlRequested));
- return false;
- } // @autoreleasepool
+ return false;
}
} // namespace platform
diff --git a/platform/http_client_curl.cpp b/platform/http_client_curl.cpp
index bdf116a3b1..5f2665e403 100644
--- a/platform/http_client_curl.cpp
+++ b/platform/http_client_curl.cpp
@@ -53,15 +53,15 @@ DECLARE_EXCEPTION(PipeCallError, RootException);
struct ScopedRemoveFile
{
ScopedRemoveFile() = default;
- explicit ScopedRemoveFile(string const & fileName) : m_file(fileName) {}
+ explicit ScopedRemoveFile(string const & fileName) : m_fileName(fileName) {}
~ScopedRemoveFile()
{
- if (!m_file.empty())
- std::remove(m_file.c_str());
+ if (!m_fileName.empty())
+ std::remove(m_fileName.c_str());
}
- std::string m_file;
+ std::string m_fileName;
};
static string ReadFileAsString(string const & filePath)
@@ -111,9 +111,9 @@ string GetTmpFileName()
return GetPlatform().TmpPathForFile(ss.str());
}
-typedef vector<pair<string, string>> HeadersT;
+typedef vector<pair<string, string>> Headers;
-HeadersT ParseHeaders(string const & raw)
+Headers ParseHeaders(string const & raw)
{
istringstream stream(raw);
HeadersT headers;
@@ -130,6 +130,19 @@ HeadersT ParseHeaders(string const & raw)
}
return headers;
}
+
+bool WriteToFile(string const & fileName, string const & data)
+{
+ ofstream ofs(fileName);
+ if(!ofs.is_open())
+ {
+ LOG(LERROR, ("Failed to write into a temporary file."));
+ return false;
+ }
+
+ ofs << data;
+ return true;
+}
} // namespace
// Used as a test stub for basic HTTP client implementation.
// Make sure that you have curl installed in the PATH.
@@ -145,7 +158,7 @@ bool HttpClient::RunHttpRequest()
ScopedRemoveFile body_deleter;
ScopedRemoveFile received_file_deleter;
- string cmd = "curl -s -w '%{http_code}' -X " + m_httpMethod + " -D '" + headers_deleter.m_file + "' ";
+ string cmd = "curl -s -w '%{http_code}' -X " + m_httpMethod + " -D '" + headers_deleter.m_fileName + "' ";
if (!m_contentType.empty())
cmd += "-H 'Content-Type: " + m_contentType + "' ";
@@ -161,27 +174,25 @@ bool HttpClient::RunHttpRequest()
if (!m_bodyData.empty())
{
- body_deleter.m_file = GetTmpFileName();
+ body_deleter.m_fileName = GetTmpFileName();
// POST body through tmp file to avoid breaking command line.
- if (!(ofstream(body_deleter.m_file) << m_bodyData).good())
- {
- LOG(LERROR, ("Failed to write into a temporary file."));
+ if (!WriteToFile(body_deleter.m_fileName, m_bodyData))
return false;
- }
+
// TODO(AlexZ): Correctly clean up this internal var to avoid client confusion.
- m_bodyFile = body_deleter.m_file;
+ m_inputFile = body_deleter.m_fileName;
}
// Content-Length is added automatically by curl.
- if (!m_bodyFile.empty())
- cmd += "--data-binary '@" + m_bodyFile + "' ";
+ if (!m_inputFile.empty())
+ cmd += "--data-binary '@" + m_inputFile + "' ";
// Use temporary file to receive data from server.
// If user has specified file name to save data, it is not temporary and is not deleted automatically.
- string rfile = m_receivedFile;
+ string rfile = m_outputFile;
if (rfile.empty())
{
rfile = GetTmpFileName();
- received_file_deleter.m_file = rfile;
+ received_file_deleter.m_fileName = rfile;
}
cmd += "-o " + rfile + strings::to_string(" ") + "'" + m_urlRequested + "'";
@@ -202,7 +213,7 @@ bool HttpClient::RunHttpRequest()
return false;
}
- HeadersT const headers = ParseHeaders(ReadFileAsString(headers_deleter.m_file));
+ HeadersT const headers = ParseHeaders(ReadFileAsString(headers_deleter.m_fileName));
for (auto const & header : headers)
{
if (header.first == "Set-Cookie")
@@ -222,14 +233,14 @@ bool HttpClient::RunHttpRequest()
m_urlReceived = header.second;
}
}
- m_serverCookies = normalize_server_cookies(move(m_serverCookies));
+ m_serverCookies = NormalizeServerCookies(move(m_serverCookies));
if (m_urlReceived.empty())
{
m_urlReceived = m_urlRequested;
// Load body contents in final request only (skip redirects).
// Sometimes server can reply with empty body, and it's ok.
- if (m_receivedFile.empty())
+ if (m_outputFile.empty())
m_serverResponse = ReadFileAsString(rfile);
}
else
@@ -240,7 +251,7 @@ bool HttpClient::RunHttpRequest()
LOG(LINFO, ("HTTP redirect", m_errorCode, "to", m_urlReceived));
HttpClient redirect(m_urlReceived);
- redirect.set_cookies(combined_cookies());
+ redirect.SetCookies(CombinedCookies());
if (!redirect.RunHttpRequest())
{
@@ -248,8 +259,8 @@ bool HttpClient::RunHttpRequest()
return false;
}
- m_errorCode = redirect.error_code();
- m_urlReceived = redirect.url_received();
+ m_errorCode = redirect.ErrorCode();
+ m_urlReceived = redirect.UrlReceived();
m_serverCookies = move(redirect.m_serverCookies);
m_serverResponse = move(redirect.m_serverResponse);
m_contentTypeReceived = move(redirect.m_contentTypeReceived);
diff --git a/platform/platform.pro b/platform/platform.pro
index b623316b02..66f5e60527 100644
--- a/platform/platform.pro
+++ b/platform/platform.pro
@@ -92,6 +92,7 @@ SOURCES += \
country_file.cpp \
file_logging.cpp \
get_text_by_id.cpp \
+ http_client.cpp \
http_request.cpp \
local_country_file.cpp \
local_country_file_utils.cpp \
diff --git a/platform/platform_mac.mm b/platform/platform_mac.mm
index 1d509cf925..062e06b64b 100644
--- a/platform/platform_mac.mm
+++ b/platform/platform_mac.mm
@@ -19,8 +19,6 @@
Platform::Platform()
{
- @autoreleasepool
- {
// get resources directory path
string const resourcesPath = [[[NSBundle mainBundle] resourcePath] UTF8String];
string const bundlePath = [[[NSBundle mainBundle] bundlePath] UTF8String];
@@ -78,7 +76,6 @@ Platform::Platform()
LOG(LDEBUG, ("Writable Directory:", m_writableDir));
LOG(LDEBUG, ("Tmp Directory:", m_tmpDir));
LOG(LDEBUG, ("Settings Directory:", m_settingsDir));
- } // @autoreleasepool
}
string Platform::UniqueClientId() const
diff --git a/routing/online_cross_fetcher.cpp b/routing/online_cross_fetcher.cpp
index 27e6c36f0e..f114b985f1 100644
--- a/routing/online_cross_fetcher.cpp
+++ b/routing/online_cross_fetcher.cpp
@@ -62,9 +62,9 @@ OnlineCrossFetcher::OnlineCrossFetcher(string const & serverURL, ms::LatLon cons
void OnlineCrossFetcher::Do()
{
m_mwmPoints.clear();
- if (m_request.RunHttpRequest() && m_request.error_code() == 200 && !m_request.was_redirected())
- ParseResponse(m_request.server_response(), m_mwmPoints);
+ if (m_request.RunHttpRequest() && m_request.ErrorCode() == 200 && !m_request.WasRedirected())
+ ParseResponse(m_request.ServerResponse(), m_mwmPoints);
else
- LOG(LWARNING, ("Can't get OSRM server response. Code: ", m_request.error_code()));
+ LOG(LWARNING, ("Can't get OSRM server response. Code: ", m_request.ErrorCode()));
}
} // namespace routing
diff --git a/xcode/platform/platform.xcodeproj/project.pbxproj b/xcode/platform/platform.xcodeproj/project.pbxproj
index 0d9bad508f..e9895b9fb2 100644
--- a/xcode/platform/platform.xcodeproj/project.pbxproj
+++ b/xcode/platform/platform.xcodeproj/project.pbxproj
@@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
3D30587D1D8320E4004AC712 /* http_client.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 3D30587B1D8320E4004AC712 /* http_client.hpp */; };
3D30587F1D880910004AC712 /* http_client_apple.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3D30587E1D880910004AC712 /* http_client_apple.mm */; };
+ 3D97F64B1D9C05E800380945 /* http_client.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3D97F64A1D9C05E800380945 /* http_client.cpp */; };
56EB1EDC1C6B6E6C0022D831 /* file_logging.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 56EB1ED81C6B6E6C0022D831 /* file_logging.cpp */; };
56EB1EDD1C6B6E6C0022D831 /* file_logging.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 56EB1ED91C6B6E6C0022D831 /* file_logging.hpp */; };
56EB1EDE1C6B6E6C0022D831 /* mwm_traits.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 56EB1EDA1C6B6E6C0022D831 /* mwm_traits.cpp */; };
@@ -99,6 +100,7 @@
/* Begin PBXFileReference section */
3D30587B1D8320E4004AC712 /* http_client.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = http_client.hpp; sourceTree = "<group>"; };
3D30587E1D880910004AC712 /* http_client_apple.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = http_client_apple.mm; sourceTree = "<group>"; };
+ 3D97F64A1D9C05E800380945 /* http_client.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = http_client.cpp; sourceTree = "<group>"; };
56EB1ED81C6B6E6C0022D831 /* file_logging.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = file_logging.cpp; sourceTree = "<group>"; };
56EB1ED91C6B6E6C0022D831 /* file_logging.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = file_logging.hpp; sourceTree = "<group>"; };
56EB1EDA1C6B6E6C0022D831 /* mwm_traits.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mwm_traits.cpp; sourceTree = "<group>"; };
@@ -300,6 +302,7 @@
6753437A1A3F5CF500A0A8C3 /* platform */ = {
isa = PBXGroup;
children = (
+ 3D97F64A1D9C05E800380945 /* http_client.cpp */,
3D30587E1D880910004AC712 /* http_client_apple.mm */,
3D30587B1D8320E4004AC712 /* http_client.hpp */,
56EB1ED81C6B6E6C0022D831 /* file_logging.cpp */,
@@ -559,6 +562,7 @@
3D30587F1D880910004AC712 /* http_client_apple.mm in Sources */,
67247FFD1C60BD6500EDE56A /* writable_dir_changer.cpp in Sources */,
6741250C1B4C00CC00A3E828 /* local_country_file_utils.cpp in Sources */,
+ 3D97F64B1D9C05E800380945 /* http_client.cpp in Sources */,
67AB92EA1B7B3E9100AB5194 /* get_text_by_id.cpp in Sources */,
671C62061AE9014C00076BD0 /* measurement_utils.cpp in Sources */,
675343B61A3F5D5A00A0A8C3 /* http_request.cpp in Sources */,