diff options
author | Sergey Yershov <syershov@maps.me> | 2016-09-29 13:15:51 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-09-29 13:15:51 +0300 |
commit | ea20412b1da6b31a2a69181ccecb2d7b9006b6a2 (patch) | |
tree | 8fd9e801a09c85e0bfc223f4f01b0d2f7099322a /platform | |
parent | e998dda9b23f5f248b313a7dd4bfae0c03a17210 (diff) | |
parent | a6aae6c63175f7bb90d5a88f205d90ba6c74e485 (diff) |
Merge pull request #4383 from milchakov/http_client
[platform] http client
Diffstat (limited to 'platform')
-rw-r--r-- | platform/apple_location_service.mm | 6 | ||||
-rw-r--r-- | platform/http_client.cpp | 165 | ||||
-rw-r--r-- | platform/http_client.hpp | 130 | ||||
-rw-r--r-- | platform/http_client_apple.mm | 158 | ||||
-rw-r--r-- | platform/http_client_curl.cpp | 272 | ||||
-rw-r--r-- | platform/http_thread_apple.mm | 5 | ||||
-rw-r--r-- | platform/ios_video_timer.mm | 6 | ||||
-rw-r--r-- | platform/platform.pro | 12 | ||||
-rw-r--r-- | platform/platform_ios.mm | 4 | ||||
-rw-r--r-- | platform/platform_mac.mm | 4 |
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 |