1 /*
2  *  This file is part of Poedit (http://poedit.net)
3  *
4  *  Copyright (C) 2014-2015 Vaclav Slavik
5  *
6  *  Permission is hereby granted, free of charge, to any person obtaining a
7  *  copy of this software and associated documentation files (the "Software"),
8  *  to deal in the Software without restriction, including without limitation
9  *  the rights to use, copy, modify, merge, publish, distribute, sublicense,
10  *  and/or sell copies of the Software, and to permit persons to whom the
11  *  Software is furnished to do so, subject to the following conditions:
12  *
13  *  The above copyright notice and this permission notice shall be included in
14  *  all copies or substantial portions of the Software.
15  *
16  *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21  *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22  *  DEALINGS IN THE SOFTWARE.
23  *
24  */
25 
26 #ifndef Poedit_http_client_h
27 #define Poedit_http_client_h
28 
29 #ifdef HAVE_HTTP_CLIENT
30 
31 #include <exception>
32 #include <functional>
33 #include <map>
34 #include <memory>
35 #include <string>
36 
37 class http_client;
38 
39 class json_dict
40 {
41 public:
42     struct native;
json_dict()43     json_dict() {}
json_dict(std::shared_ptr<native> value)44     json_dict(std::shared_ptr<native> value) : m_native(value) {}
45 
46     bool is_null(const char *name) const;
47 
48     json_dict subdict(const char *name) const;
49 
50     std::string utf8_string(const char *name) const;
51     std::wstring wstring(const char *name) const;
52 #ifdef __APPLE__
native_string(const char * name)53     std::string native_string(const char *name) const { return utf8_string(name); }
54 #else
native_string(const char * name)55     std::wstring native_string(const char *name) const { return wstring(name); }
56 #endif
57 
58     int number(const char *name) const;
59     double double_number(const char *name) const;
60     void iterate_array(const char *name, std::function<void(const json_dict&)> on_item) const;
61 
62 private:
63     std::shared_ptr<native> m_native;
64 };
65 
66 
67 /// Stores POSTed data (RFC 1867)
68 class multipart_form_data
69 {
70 public:
71     multipart_form_data();
72 
73     /// Add a form value.
74     void add_value(const std::string& name, const std::string& value);
75 
76     /// Add file upload.
77     void add_file(const std::string& name, const std::string& filename, const std::string& file_content);
78 
79     /// Content-Type header to use with the data.
80     std::string content_type() const;
81 
82     /// Returns generated body of the request.
83     std::string body() const;
84 
85 private:
86     std::string m_boundary;
87     std::string m_body;
88 };
89 
90 
91 /// Response to a HTTP request
92 class http_response
93 {
94 public:
http_response(const json_dict & data)95     http_response(const json_dict& data) : m_ok(true), m_data(data) {}
http_response(std::exception_ptr e)96     http_response(std::exception_ptr e) : m_ok(false), m_error(e) {}
97 
98     /// Is the response acceptable?
ok()99     bool ok() const { return m_ok; }
100 
101     /// Returns json data from the response. May throw if there was error.
json()102     const json_dict& json() const
103     {
104         if (m_error)
105             std::rethrow_exception(m_error);
106         return m_data;
107     }
108 
109     /// Returns the stored exception, if any
exception()110     std::exception_ptr exception() const { return m_error; }
111 
112 private:
113     bool m_ok;
114     std::exception_ptr m_error;
115     json_dict m_data;
116 
117     friend class http_client;
118 };
119 
120 
121 /**
122     Client for accessing HTTP REST APIs.
123  */
124 class http_client
125 {
126 public:
127     /// Connection flags for the client.
128     enum flags
129     {
130         /// The host uses SNI for SSL. Will use http instead of https on Windows XP.
131         uses_sni = 1
132     };
133 
134     /**
135         Creates an instance of the client object.
136 
137         The client is good for accessing URLs with the provided prefix
138         (which may be any prefix, not just the hostname).
139 
140         @param flags OR-combination of http_client::flags values.
141      */
142     http_client(const std::string& url_prefix, int flags = 0);
143     virtual ~http_client();
144 
145     /// Return true if the server is reachable, i.e. client is online
146     bool is_reachable() const;
147 
148     /// Sets Authorization header to be used in all requests
149     void set_authorization(const std::string& auth);
150 
151     typedef std::function<void(const http_response&)> response_func_t;
152 
153     /// Perform a GET request at the given URL and call function to handle the result
154     void get(const std::string& url, response_func_t handler);
155 
156     /// Perform a GET request at the given URL and ignore the response.
get(const std::string & url)157     void get(const std::string& url)
158     {
159         get(url, [](const http_response&){});
160     }
161 
162     /**
163         Perform a GET request and store the body in a file.
164 
165         Returned response's body won't be accessible in any way from @a handler.
166      */
167     void download(const std::string& url, const std::wstring& output_file, response_func_t handler);
168 
169     /**
170         Perform a POST request with multipart/form-data formatted @a params.
171      */
172     void post(const std::string& url, const multipart_form_data& data, response_func_t handler);
173 
174     // Variants that have separate error and success handlers:
175     template <typename T1, typename T2>
get(const std::string & url,const T1 & onResult,const T2 & onError)176     void get(const std::string& url, const T1& onResult, const T2& onError)
177     {
178         get(url, [onResult,onError](const http_response& r){
179             try
180             {
181                 onResult(r.json());
182             }
183             catch (...)
184             {
185                 onError(std::current_exception());
186             }
187         });
188     }
189 
190     template <typename T1, typename T2>
download(const std::string & url,const std::wstring & output_file,const T1 & onSuccess,const T2 & onError)191     void download(const std::string& url, const std::wstring& output_file, const T1& onSuccess, const T2& onError)
192     {
193         download(url, output_file, [onSuccess,onError](const http_response& r){
194             if (r.ok())
195                 onSuccess();
196             else
197                 onError(r.exception());
198         });
199     }
200 
201     template <typename T1, typename T2>
post(const std::string & url,const multipart_form_data & data,const T1 & onSuccess,const T2 & onError)202     void post(const std::string& url, const multipart_form_data& data, const T1& onSuccess, const T2& onError)
203     {
204         post(url, data, [onSuccess,onError](const http_response& r){
205             if (r.ok())
206                 onSuccess();
207             else
208                 onError(r.exception());
209         });
210     }
211 
212     // Helper for encoding text as URL-encoded UTF-8
213     static std::string url_encode(const std::string& s);
214     static std::string url_encode(const std::wstring& s);
215 
216 protected:
217     /**
218         Extract more detailed, client specific error response from the
219         JSON body of error response, if available.
220 
221         Does nothing by default, but can be overridden in derived class.
222      */
parse_json_error(const json_dict &)223     virtual std::string parse_json_error(const json_dict& /*response*/) const
224         { return std::string(); }
225 
226     /**
227          Called when an error response is returned, before calling error handler.
228 
229          Can be used to react to specific errors, e.g. invalidate expired OAuth tokens,
230          or to modify the response.
231      */
on_error_response(int &,std::string &)232     virtual void on_error_response(int& /*statusCode*/, std::string& /*message*/) {};
233 
234 private:
235     class impl;
236     friend class http_response;
237 
238     std::unique_ptr<impl> m_impl;
239 };
240 
241 #endif // HAVE_HTTP_CLIENT
242 
243 #endif // Poedit_http_client_h
244