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:
authorSergey Yershov <syershov@maps.me>2016-09-29 13:15:51 +0300
committerGitHub <noreply@github.com>2016-09-29 13:15:51 +0300
commitea20412b1da6b31a2a69181ccecb2d7b9006b6a2 (patch)
tree8fd9e801a09c85e0bfc223f4f01b0d2f7099322a /platform
parente998dda9b23f5f248b313a7dd4bfae0c03a17210 (diff)
parenta6aae6c63175f7bb90d5a88f205d90ba6c74e485 (diff)
Merge pull request #4383 from milchakov/http_client
[platform] http client
Diffstat (limited to 'platform')
-rw-r--r--platform/apple_location_service.mm6
-rw-r--r--platform/http_client.cpp165
-rw-r--r--platform/http_client.hpp130
-rw-r--r--platform/http_client_apple.mm158
-rw-r--r--platform/http_client_curl.cpp272
-rw-r--r--platform/http_thread_apple.mm5
-rw-r--r--platform/ios_video_timer.mm6
-rw-r--r--platform/platform.pro12
-rw-r--r--platform/platform_ios.mm4
-rw-r--r--platform/platform_mac.mm4
10 files changed, 736 insertions, 26 deletions
diff --git a/platform/apple_location_service.mm b/platform/apple_location_service.mm
index c4568c26b1..0ec2ae0dc8 100644
--- a/platform/apple_location_service.mm
+++ b/platform/apple_location_service.mm
@@ -34,12 +34,6 @@ public:
m_locationManager.desiredAccuracy = kCLLocationAccuracyBest;
}
- virtual ~AppleLocationService()
- {
- [m_locationManager release];
- [m_objCppWrapper release];
- }
-
void OnLocationUpdate(GpsInfo const & info)
{
m_observer.OnLocationUpdated(info);
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
new file mode 100644
index 0000000000..11f77084d4
--- /dev/null
+++ b/platform/http_client.hpp
@@ -0,0 +1,130 @@
+/*******************************************************************************
+The MIT License (MIT)
+
+Copyright (c) 2015 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*******************************************************************************/
+#pragma once
+
+#include "base/macros.hpp"
+
+#include "std/string.hpp"
+
+namespace platform
+{
+class HttpClient
+{
+public:
+ static auto constexpr kNoError = -1;
+
+ HttpClient() = default;
+ HttpClient(string const & url) : m_urlRequested(url) {}
+
+ // 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 & 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 & SetReceivedFile(string const & received_file);
+ HttpClient & SetUserAgent(string const & user_agent);
+ // This method is mutually exclusive with set_body_file().
+ template <typename StringT>
+ HttpClient & SetBodyData(StringT && body_data, string const & content_type,
+ string const & http_method = "POST",
+ string const & content_encoding = "")
+ {
+ 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 & SetUserAndPassword(string const & user, string const & password);
+ // Set HTTP Cookie header.
+ 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 & SetHandleRedirects(bool handle_redirects);
+
+ string const & UrlRequested() const;
+ // @returns empty string in the case of error
+ 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 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 CombinedCookies() const;
+ // Returns cookie value or empty string if it's not present.
+ 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 NormalizeServerCookies(string && cookies);
+
+ 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);
+};
+
+string DebugPrint(HttpClient const & request);
+} // namespace platform
diff --git a/platform/http_client_apple.mm b/platform/http_client_apple.mm
new file mode 100644
index 0000000000..4d063c08c2
--- /dev/null
+++ b/platform/http_client_apple.mm
@@ -0,0 +1,158 @@
+/*******************************************************************************
+The MIT License (MIT)
+
+Copyright (c) 2015 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*******************************************************************************/
+
+#if ! __has_feature(objc_arc)
+#error This file must be compiled with ARC. Either turn on ARC for the project or use -fobjc-arc flag
+#endif
+
+#import <Foundation/NSString.h>
+#import <Foundation/NSURL.h>
+#import <Foundation/NSURLError.h>
+#import <Foundation/NSData.h>
+#import <Foundation/NSStream.h>
+#import <Foundation/NSURLRequest.h>
+#import <Foundation/NSURLResponse.h>
+#import <Foundation/NSURLConnection.h>
+#import <Foundation/NSError.h>
+#import <Foundation/NSFileManager.h>
+
+#include <TargetConditionals.h> // TARGET_OS_IPHONE
+#if (TARGET_OS_IPHONE > 0) // Works for all iOS devices, including iPad.
+extern NSString * gBrowserUserAgent;
+#endif
+
+#include "platform/http_client.hpp"
+
+#include "base/logging.hpp"
+
+namespace platform
+{
+// If we try to upload our data from the background fetch handler on iOS, we have ~30 seconds to do that gracefully.
+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()
+{
+ 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"];
+
+ 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_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"];
+#endif // TARGET_OS_IPHONE
+
+ 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_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)
+ {
+ 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"];
+ 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];
+
+ 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)
+ {
+ 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));
+
+ return false;
+}
+} // namespace platform
diff --git a/platform/http_client_curl.cpp b/platform/http_client_curl.cpp
new file mode 100644
index 0000000000..5f2665e403
--- /dev/null
+++ b/platform/http_client_curl.cpp
@@ -0,0 +1,272 @@
+/*******************************************************************************
+ The MIT License (MIT)
+
+ Copyright (c) 2014 Alexander Zolotarev <me@alex.bio> from Minsk, Belarus
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ *******************************************************************************/
+#include "platform/http_client.hpp"
+#include "platform/platform.hpp"
+
+#include "base/assert.hpp"
+#include "base/exception.hpp"
+#include "base/logging.hpp"
+#include "base/string_utils.hpp"
+
+#include "boost/uuid/uuid_generators.hpp"
+#include "boost/uuid/uuid_io.hpp"
+
+#include "std/array.hpp"
+#include "std/fstream.hpp"
+#include "std/sstream.hpp"
+#include "std/vector.hpp"
+
+#include <cstdio> // popen, tmpnam
+
+#ifdef _MSC_VER
+#define popen _popen
+#define pclose _pclose
+#else
+#include <unistd.h> // close
+#endif
+
+namespace
+{
+DECLARE_EXCEPTION(PipeCallError, RootException);
+
+struct ScopedRemoveFile
+{
+ ScopedRemoveFile() = default;
+ explicit ScopedRemoveFile(string const & fileName) : m_fileName(fileName) {}
+
+ ~ScopedRemoveFile()
+ {
+ if (!m_fileName.empty())
+ std::remove(m_fileName.c_str());
+ }
+
+ std::string m_fileName;
+};
+
+static string ReadFileAsString(string const & filePath)
+{
+ ifstream ifs(filePath, ifstream::in);
+ if (!ifs.is_open())
+ return {};
+
+ return {istreambuf_iterator<char>(ifs), istreambuf_iterator<char>()};
+}
+
+
+string RunCurl(string const & cmd)
+{
+ FILE * pipe = ::popen(cmd.c_str(), "r");
+ ASSERT(pipe, ());
+ array<char, 8 * 1024> arr;
+ string result;
+ size_t read;
+ do
+ {
+ read = ::fread(arr.data(), 1, arr.size(), pipe);
+ if (read > 0)
+ {
+ result.append(arr.data(), read);
+ }
+ } while (read == arr.size());
+
+ auto const err = ::pclose(pipe);
+ // Exception will be cought in RunHTTPRequest
+ if (err)
+ throw PipeCallError("", "Error " + strings::to_string(err) + " while calling " + cmd);
+
+ return result;
+}
+
+string GetTmpFileName()
+{
+ boost::uuids::random_generator gen;
+ boost::uuids::uuid u = gen();
+
+ stringstream ss;
+ ss << u;
+
+ ASSERT(!ss.str().empty(), ());
+
+ return GetPlatform().TmpPathForFile(ss.str());
+}
+
+typedef vector<pair<string, string>> Headers;
+
+Headers ParseHeaders(string const & raw)
+{
+ istringstream stream(raw);
+ HeadersT headers;
+ string line;
+ while (getline(stream, line))
+ {
+ auto const cr = line.rfind('\r');
+ if (cr != string::npos)
+ line.erase(cr);
+
+ auto const delims = line.find(": ");
+ if (delims != string::npos)
+ headers.push_back(make_pair(line.substr(0, delims), line.substr(delims + 2)));
+ }
+ 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.
+// TODO(AlexZ): Not a production-ready implementation.
+namespace platform
+{
+// Extract HTTP headers via temporary file with -D switch.
+// HTTP status code is extracted from curl output (-w switches).
+// Redirects are handled recursively. TODO(AlexZ): avoid infinite redirects loop.
+bool HttpClient::RunHttpRequest()
+{
+ ScopedRemoveFile headers_deleter(GetTmpFileName());
+ ScopedRemoveFile body_deleter;
+ ScopedRemoveFile received_file_deleter;
+
+ 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 + "' ";
+
+ if (!m_contentEncoding.empty())
+ cmd += "-H 'Content-Encoding: " + m_contentEncoding + "' ";
+
+ if (!m_basicAuthUser.empty())
+ cmd += "-u '" + m_basicAuthUser + ":" + m_basicAuthPassword + "' ";
+
+ if (!m_cookies.empty())
+ cmd += "-b '" + m_cookies + "' ";
+
+ if (!m_bodyData.empty())
+ {
+ body_deleter.m_fileName = GetTmpFileName();
+ // POST body through tmp file to avoid breaking command line.
+ if (!WriteToFile(body_deleter.m_fileName, m_bodyData))
+ return false;
+
+ // TODO(AlexZ): Correctly clean up this internal var to avoid client confusion.
+ m_inputFile = body_deleter.m_fileName;
+ }
+ // Content-Length is added automatically by curl.
+ 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_outputFile;
+ if (rfile.empty())
+ {
+ rfile = GetTmpFileName();
+ received_file_deleter.m_fileName = rfile;
+ }
+
+ cmd += "-o " + rfile + strings::to_string(" ") + "'" + m_urlRequested + "'";
+
+
+ if (m_debugMode)
+ {
+ LOG(LINFO, ("Executing", cmd));
+ }
+
+ try
+ {
+ m_errorCode = stoi(RunCurl(cmd));
+ }
+ catch (RootException const & ex)
+ {
+ LOG(LERROR, (ex.Msg()));
+ return false;
+ }
+
+ HeadersT const headers = ParseHeaders(ReadFileAsString(headers_deleter.m_fileName));
+ for (auto const & header : headers)
+ {
+ if (header.first == "Set-Cookie")
+ {
+ m_serverCookies += header.second + ", ";
+ }
+ else if (header.first == "Content-Type")
+ {
+ m_contentTypeReceived = header.second;
+ }
+ else if (header.first == "Content-Encoding")
+ {
+ m_contentEncodingReceived = header.second;
+ }
+ else if (header.first == "Location")
+ {
+ m_urlReceived = header.second;
+ }
+ }
+ 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_outputFile.empty())
+ m_serverResponse = ReadFileAsString(rfile);
+ }
+ else
+ {
+ // Handle HTTP redirect.
+ // TODO(AlexZ): Should we check HTTP redirect code here?
+ if (m_debugMode)
+ LOG(LINFO, ("HTTP redirect", m_errorCode, "to", m_urlReceived));
+
+ HttpClient redirect(m_urlReceived);
+ redirect.SetCookies(CombinedCookies());
+
+ if (!redirect.RunHttpRequest())
+ {
+ m_errorCode = -1;
+ return false;
+ }
+
+ 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);
+ m_contentEncodingReceived = move(redirect.m_contentEncodingReceived);
+ }
+
+ return true;
+}
+} // namespace platform
diff --git a/platform/http_thread_apple.mm b/platform/http_thread_apple.mm
index 5f6f54e208..423b35db91 100644
--- a/platform/http_thread_apple.mm
+++ b/platform/http_thread_apple.mm
@@ -23,12 +23,10 @@ static id<DownloadIndicatorProtocol> downloadIndicator = nil;
{
LOG(LDEBUG, ("ID:", [self hash], "Connection is destroyed"));
[m_connection cancel];
- [m_connection release];
#ifdef OMIM_OS_IPHONE
[downloadIndicator enableStandby];
[downloadIndicator disableDownloadIndicator];
#endif
- [super dealloc];
}
- (void) cancel
@@ -66,7 +64,6 @@ static id<DownloadIndicatorProtocol> downloadIndicator = nil;
val = [[NSString alloc] initWithFormat: @"bytes=%qi-", beg];
}
[request addValue:val forHTTPHeaderField:@"Range"];
- [val release];
}
if (!pb.empty())
@@ -94,7 +91,6 @@ static id<DownloadIndicatorProtocol> downloadIndicator = nil;
if (m_connection == 0)
{
LOG(LERROR, ("Can't create connection for", url));
- [self release];
return nil;
}
else
@@ -226,7 +222,6 @@ HttpThread * CreateNativeHttpThread(string const & url,
void DeleteNativeHttpThread(HttpThread * request)
{
[request cancel];
- [request release];
}
} // namespace downloader
diff --git a/platform/ios_video_timer.mm b/platform/ios_video_timer.mm
index b60b234687..ec3077d40e 100644
--- a/platform/ios_video_timer.mm
+++ b/platform/ios_video_timer.mm
@@ -52,7 +52,6 @@ public:
// So we should check EStopped flag in 'perform' to skip pending call.
m_state = EStopped;
[m_displayLink invalidate];
- [m_objCppWrapper release];
m_displayLink = 0;
}
}
@@ -89,11 +88,6 @@ public:
return self;
}
-- (void)dealloc
-{
- [super dealloc];
-}
-
- (void)perform
{
m_timer->perform();
diff --git a/platform/platform.pro b/platform/platform.pro
index 24ff9fb2d6..66f5e60527 100644
--- a/platform/platform.pro
+++ b/platform/platform.pro
@@ -47,7 +47,15 @@ INCLUDEPATH += $$ROOT_DIR/3party/jansson/src
macx-*|iphone* {
HEADERS += http_thread_apple.h
- OBJECTIVE_SOURCES += http_thread_apple.mm
+ OBJECTIVE_SOURCES += \
+ http_thread_apple.mm \
+ http_client_apple.mm \
+
+ QMAKE_OBJECTIVE_CFLAGS += -fobjc-arc
+}
+
+linux*|win* {
+ SOURCES += http_client_curl.cpp
}
!win32* {
@@ -66,6 +74,7 @@ HEADERS += \
get_text_by_id.hpp \
http_request.hpp \
http_thread_callback.hpp \
+ http_client.hpp \
local_country_file.hpp \
local_country_file_utils.hpp \
location.hpp \
@@ -83,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_ios.mm b/platform/platform_ios.mm
index 7173a8f653..892b351795 100644
--- a/platform/platform_ios.mm
+++ b/platform/platform_ios.mm
@@ -37,8 +37,6 @@
Platform::Platform()
{
- NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
-
m_isTablet = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);
NSBundle * bundle = [NSBundle mainBundle];
@@ -65,8 +63,6 @@ Platform::Platform()
UIDevice * device = [UIDevice currentDevice];
NSLog(@"Device: %@, SystemName: %@, SystemVersion: %@", device.model, device.systemName,
device.systemVersion);
-
- [pool release];
}
Platform::EError Platform::MkDir(string const & dirName) const
diff --git a/platform/platform_mac.mm b/platform/platform_mac.mm
index 85025bd62f..062e06b64b 100644
--- a/platform/platform_mac.mm
+++ b/platform/platform_mac.mm
@@ -19,8 +19,6 @@
Platform::Platform()
{
- NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
-
// get resources directory path
string const resourcesPath = [[[NSBundle mainBundle] resourcePath] UTF8String];
string const bundlePath = [[[NSBundle mainBundle] bundlePath] UTF8String];
@@ -78,8 +76,6 @@ Platform::Platform()
LOG(LDEBUG, ("Writable Directory:", m_writableDir));
LOG(LDEBUG, ("Tmp Directory:", m_tmpDir));
LOG(LDEBUG, ("Settings Directory:", m_settingsDir));
-
- [pool release];
}
string Platform::UniqueClientId() const