Imported Upstream version 1.0beta1
[platform/upstream/syncevolution.git] / src / syncevo / SoupTransportAgent.cpp
1 /*
2  * Copyright (C) 2009 Intel Corporation
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) version 3.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301  USA
18  */
19
20 #include <syncevo/SoupTransportAgent.h>
21 #include <syncevo/SyncContext.h>
22
23 #ifdef ENABLE_LIBSOUP
24
25 #include <algorithm>
26 #include <libsoup/soup-status.h>
27 #include <Logging.h>
28
29 #ifdef HAVE_LIBSOUP_SOUP_GNOME_FEATURES_H
30 #include <libsoup/soup-gnome-features.h>
31 #endif
32
33 #include <syncevo/declarations.h>
34 SE_BEGIN_CXX
35
36 SoupTransportAgent::SoupTransportAgent(GMainLoop *loop) :
37     m_session(soup_session_async_new()),
38     m_loop(loop ?
39            loop :
40            g_main_loop_new(NULL, TRUE),
41            "Soup main loop"),
42     m_status(INACTIVE),
43     m_cb(NULL),
44     m_response(NULL)
45 {
46 #ifdef HAVE_LIBSOUP_SOUP_GNOME_FEATURES_H
47     // use default GNOME proxy settings
48     soup_session_add_feature_by_type(m_session.get(), SOUP_TYPE_PROXY_RESOLVER_GNOME);
49 #endif
50 }
51
52 SoupTransportAgent::~SoupTransportAgent()
53 {
54     if (m_session.get()) {
55         // ensure that no callbacks for this session will be triggered
56         // in the future, they would use a stale pointer to this agent
57         // instance
58         soup_session_abort(m_session.get());
59     }
60 }
61
62 void SoupTransportAgent::setURL(const std::string &url)
63 {
64     m_URL = url;
65 }
66
67 void SoupTransportAgent::setProxy(const std::string &proxy)
68 {
69     eptr<SoupURI, SoupURI, GLibUnref> uri(soup_uri_new(proxy.c_str()), "Proxy URI");
70     g_object_set(m_session.get(),
71                  SOUP_SESSION_PROXY_URI, uri.get(),
72                  NULL);
73 }
74
75 void SoupTransportAgent::setProxyAuth(const std::string &user, const std::string &password)
76 {
77     /**
78      * @TODO: handle "authenticate" signal for both proxy and HTTP server
79      * (https://sourceforge.net/tracker/index.php?func=detail&aid=2056162&group_id=146288&atid=764733).
80      *
81      * Proxy authentication is available, but still needs to be hooked up
82      * with libsoup. Should we make this interactive? Needs an additional
83      * API for TransportAgent into caller.
84      *
85      * HTTP authentication is not available.
86      */
87     m_proxyUser = user;
88     m_proxyPassword = password;
89 }
90
91 void SoupTransportAgent::setSSL(const std::string &cacerts,
92                                 bool verifyServer,
93                                 bool verifyHost)
94 {
95     m_verifySSL = verifyServer || verifyHost;
96     m_cacerts = cacerts;
97 }
98
99 void SoupTransportAgent::setContentType(const std::string &type)
100 {
101     m_contentType = type;
102 }
103
104 void SoupTransportAgent::setUserAgent(const std::string &agent)
105 {
106     g_object_set(m_session.get(),
107                  SOUP_SESSION_USER_AGENT, agent.c_str(),
108                  NULL);
109 }
110
111 void SoupTransportAgent::setCallback (TransportCallback cb, void *data, int interval)
112 {
113     m_cb = cb;
114     m_cbData = data;
115     m_cbInterval = interval;
116 }
117
118 void SoupTransportAgent::send(const char *data, size_t len)
119 {
120     // ownership is transferred to libsoup in soup_session_queue_message()
121     eptr<SoupMessage, GObject> message(soup_message_new("POST", m_URL.c_str()));
122     if (!message.get()) {
123         SE_THROW_EXCEPTION(TransportException, "could not allocate SoupMessage");
124     }
125
126     // use CA certificates if available and needed,
127     // fail if not available and needed
128     if (m_verifySSL) {
129         if (!m_cacerts.empty()) {
130             g_object_set(m_session.get(), SOUP_SESSION_SSL_CA_FILE, m_cacerts.c_str(), NULL);
131         } else {
132             SoupURI *uri = soup_message_get_uri(message.get());
133             if (!strcmp(uri->scheme, SOUP_URI_SCHEME_HTTPS)) {
134                 SE_THROW_EXCEPTION(TransportException, "SSL certificate checking requested, but no CA certificate file configured");
135             }
136         }
137     }
138
139     soup_message_set_request(message.get(), m_contentType.c_str(),
140                              SOUP_MEMORY_TEMPORARY, data, len);
141     m_status = ACTIVE;
142     m_abortEventSource = g_timeout_add_seconds(ABORT_CHECK_INTERVAL, AbortCallback, static_cast<gpointer> (this));
143     if(m_cb){
144         m_message = message.get();
145         m_cbEventSource = g_timeout_add_seconds(m_cbInterval, TimeoutCallback, static_cast<gpointer> (this));
146     }
147     soup_session_queue_message(m_session.get(), message.release(),
148                                SessionCallback, static_cast<gpointer>(this));
149 }
150
151 void SoupTransportAgent::cancel()
152 {
153     m_status = CANCELED;
154     soup_session_abort(m_session.get());
155     if(g_main_loop_is_running(m_loop.get()))
156       g_main_loop_quit(m_loop.get());
157 }
158
159 TransportAgent::Status SoupTransportAgent::wait(bool noReply)
160 {
161     if (!m_failure.empty()) {
162         std::string failure;
163         std::swap(failure, m_failure);
164         SE_THROW_EXCEPTION(TransportException, failure);
165     }
166
167     switch (m_status) {
168     case CLOSED:
169         return CLOSED;
170         break;
171     case ACTIVE:
172         // block in main loop until our HandleSessionCallback() stops the loop
173         g_main_loop_run(m_loop.get());
174         break;
175     default:
176         break;
177     }
178
179     /** For a canceled message, does not throw exception, just print a warning, the 
180      * upper layer may decide to retry
181      */
182     if(m_status == TIME_OUT || m_status == FAILED){
183         std::string failure;
184         std::swap(failure, m_failure);
185         SE_LOG_INFO(NULL, NULL, "SoupTransport Failure: %s", failure.c_str());
186     }
187     if (!m_failure.empty()) {
188         std::string failure;
189         std::swap(failure, m_failure);
190         SE_THROW_EXCEPTION(TransportException, failure);
191     }
192
193     m_abortEventSource.set(0);
194     m_cbEventSource.set(0);
195     return m_status;
196 }
197
198 void SoupTransportAgent::getReply(const char *&data, size_t &len, std::string &contentType)
199 {
200     if (m_response) {
201         data = m_response->data;
202         len = m_response->length;
203         contentType = m_responseContentType;
204     } else {
205         data = NULL;
206         len = 0;
207     }
208 }
209
210 void SoupTransportAgent::SessionCallback(SoupSession *session,
211                                          SoupMessage *msg,
212                                          gpointer user_data)
213 {
214     static_cast<SoupTransportAgent *>(user_data)->HandleSessionCallback(session, msg);
215 }
216
217 void SoupTransportAgent::HandleSessionCallback(SoupSession *session,
218                                                SoupMessage *msg)
219 {
220     // keep a reference to the data 
221     m_responseContentType = "";
222     if (msg->response_body) {
223         m_response = soup_message_body_flatten(msg->response_body);
224         const char *soupContentType = soup_message_headers_get(msg->response_headers,
225                                                                "Content-Type");
226         if (soupContentType) {
227             m_responseContentType = soupContentType;
228         }
229     } else {
230         m_response = NULL;
231     }
232     if (msg->status_code != 200) {
233         m_failure = m_URL;
234         m_failure += " via libsoup: ";
235         m_failure += msg->reason_phrase ? msg->reason_phrase : "failed";
236         m_status = FAILED;
237
238         if (m_responseContentType.find("text") != std::string::npos) {
239             SE_LOG_DEBUG(NULL, NULL, "unexpected HTTP response: status %d/%s, content type %s, body:\n%.*s",
240                          msg->status_code, msg->reason_phrase ? msg->reason_phrase : "<no reason>",
241                          m_responseContentType.c_str(),
242                          m_response ? (int)m_response->length : 0,
243                          m_response ? m_response->data : "");
244         }
245     } else {
246         m_status = GOT_REPLY;
247     }
248
249     g_main_loop_quit(m_loop.get());
250 }
251
252 gboolean SoupTransportAgent::AbortCallback(gpointer transport)
253 {
254     const SuspendFlags &s_flags = SyncContext::getSuspendFlags();
255
256     if (s_flags.state == SuspendFlags::CLIENT_ABORT)
257     {
258         static_cast<SoupTransportAgent *>(transport)->cancel();
259         return FALSE;
260     }
261     return TRUE;
262 }
263
264 gboolean SoupTransportAgent::processCallback()
265 {
266     bool cont = m_cb(m_cbData);
267     if(cont){
268         //stop the message processing and mark status as timeout
269         guint message_status = SOUP_STATUS_CANCELLED;
270         soup_session_cancel_message(m_session.get(), m_message, message_status);
271         m_status = TIME_OUT;
272     }else {
273         cancel();
274     }
275     return FALSE;
276 }
277
278 gboolean SoupTransportAgent::TimeoutCallback(gpointer transport)
279 {
280     SoupTransportAgent * sTransport = static_cast<SoupTransportAgent *>(transport);
281     return sTransport->processCallback();
282 }
283
284 SE_END_CXX
285
286 #endif // ENABLE_LIBSOUP
287