1 // carddav-serveretag.cpp - CardDAV Object - Server ETag subroutines.
3 // (c) 2012-2015 Xestia Software Development.
5 // This file is part of Xestia Address Book.
7 // Xestia Address Book is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License as published by the
9 // Free Software Foundation, version 3 of the license.
11 // Xestia Address Book is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License along
17 // with Xestia Address Book. If not, see <http://www.gnu.org/licenses/>
20 #include "../version.h"
22 #include <wx/tokenzr.h>
24 #include <libxml/parser.h>
25 #include <libxml/tree.h>
28 #include "../vcard/vcard.h"
29 #include "../common/dirs.h"
31 void CardDAV::GetServerETagValueThread()
34 // Get the server etag value (threaded).
41 AbortConnection = FALSE;
43 bool FilenameIsDirectory = FALSE;
46 wxString ServerAddressURL;
48 wxString ServerAddressSSL;
49 wxString ServerAddressNormal;
51 conn = curl_easy_init();
53 #if defined(__APPLE__)
55 SetConnectionObject(conn);
59 struct CardDAVCURLPasser {
62 bool HeaderMode = TRUE;
64 } CardDAVHeader, CardDAVFooter;
66 CardDAVHeader.Data = this;
67 CardDAVHeader.HeaderMode = TRUE;
69 CardDAVFooter.Data = this;
70 CardDAVFooter.HeaderMode = FALSE;
75 ServerAddressURL = ServerAddress + wxT(":") + wxString::Format(wxT("%i"), ServerPort) + ServerPrefix + ServerFilenameLocation;
76 ServerAddressSSL = wxT("https://") + ServerAddressURL;
77 ServerAddressNormal = wxT("http://") + ServerAddressURL;
79 ServerAuth = ServerUser + wxT(":") + ServerPass;
81 std::map<int,int>::iterator ActIter;
82 struct UploadDataStruc UploadData;
85 ActIter = ActivityListPtr->find((int)ItemIndex);
87 static const char* query =
88 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
89 "<C:addressbook-query xmlns:D=\"DAV:\""
90 " xmlns:C=\"urn:ietf:params:xml:ns:carddav\">"
91 "<D:prop><D:getetag/>"
94 "</C:addressbook-query>";
98 wxString ServerCertFilename;
99 bool MatchingCert = FALSE;
101 curl_easy_setopt(conn, CURLOPT_URL, (const char*)ServerAddressSSL.mb_str(wxConvUTF8));
102 curl_easy_setopt(conn, CURLOPT_NOPROGRESS, 0);
103 curl_easy_setopt(conn, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
104 curl_easy_setopt(conn, CURLOPT_TIMEOUT, 60);
105 curl_easy_setopt(conn, CURLOPT_FAILONERROR, TRUE);
106 curl_easy_setopt(conn, CURLOPT_USERAGENT, XSDAB_USERAGENT);
107 curl_easy_setopt(conn, CURLOPT_USERPWD, (const char*)ServerAuth.mb_str(wxConvUTF8));
108 curl_easy_setopt(conn, CURLOPT_WRITEFUNCTION, WritebackFunc);
109 curl_easy_setopt(conn, CURLOPT_WRITEDATA, &PageData);
110 curl_easy_setopt(conn, CURLOPT_WRITEHEADER, &PageHeader);
111 curl_easy_setopt(conn, CURLOPT_PROGRESSDATA, this);
112 curl_easy_setopt(conn, CURLOPT_PROGRESSFUNCTION, ProgressFunc);
113 curl_easy_setopt(conn, CURLOPT_CUSTOMREQUEST, "REPORT");
114 curl_easy_setopt(conn, CURLOPT_NOSIGNAL, 1);
115 curl_easy_setopt(conn, CURLOPT_POSTFIELDS, query);
116 curl_easy_setopt(conn, CURLOPT_POSTFIELDSIZE, strlen(query));
118 #if defined(__APPLE__) || defined(__WIN32__)
122 ServerCertFilename = GetAccountDir(ServerAccount, TRUE);
124 if (wxFile::Exists(ServerCertFilename) == TRUE){
126 curl_easy_setopt(conn, CURLOPT_SSL_VERIFYPEER, 1);
127 curl_easy_setopt(conn, CURLOPT_SSL_VERIFYHOST, 2);
128 curl_easy_setopt(conn, CURLOPT_CAINFO, (const char*)ServerCertFilename.mb_str(wxConvUTF8));
134 claconncode = (curl_easy_perform(conn));
136 // If CURLE_PEER_FAILED_VERIFICATION is returned, retry without
137 // the local certificate in use.
139 if (claconncode == CURLE_PEER_FAILED_VERIFICATION){
141 curl_easy_cleanup(conn);
142 conn = curl_easy_init();
144 curl_easy_setopt(conn, CURLOPT_URL, (const char*)ServerAddressSSL.mb_str(wxConvUTF8));
145 curl_easy_setopt(conn, CURLOPT_NOPROGRESS, 0);
146 curl_easy_setopt(conn, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
147 curl_easy_setopt(conn, CURLOPT_TIMEOUT, 60);
148 curl_easy_setopt(conn, CURLOPT_FAILONERROR, TRUE);
149 curl_easy_setopt(conn, CURLOPT_USERAGENT, XSDAB_USERAGENT);
150 curl_easy_setopt(conn, CURLOPT_USERPWD, (const char*)ServerAuth.mb_str(wxConvUTF8));
151 curl_easy_setopt(conn, CURLOPT_WRITEFUNCTION, WritebackFunc);
152 curl_easy_setopt(conn, CURLOPT_WRITEDATA, &PageData);
153 curl_easy_setopt(conn, CURLOPT_WRITEHEADER, &PageHeader);
154 curl_easy_setopt(conn, CURLOPT_PROGRESSDATA, this);
155 curl_easy_setopt(conn, CURLOPT_PROGRESSFUNCTION, ProgressFunc);
156 curl_easy_setopt(conn, CURLOPT_CUSTOMREQUEST, "REPORT");
157 curl_easy_setopt(conn, CURLOPT_NOSIGNAL, 1);
158 curl_easy_setopt(conn, CURLOPT_POSTFIELDS, query);
159 curl_easy_setopt(conn, CURLOPT_POSTFIELDSIZE, strlen(query));
161 claconncode = (curl_easy_perform(conn));
163 // If claconncode is CURLE_OK then delete the certificate file as that
164 // is no longer needed.
166 if (claconncode == CURLE_OK){
168 // Delete the certificate file.
170 wxRemoveFile(ServerCertFilename);
176 // Check if it fails with a CURLE_SSL_CACERT then compare
177 // the certificates as PEM files.
179 #if defined(__APPLE__)
183 if (claconncode == CURLE_SSL_CACERT && wxFile::Exists(ServerCertFilename) == TRUE){
186 sslerrconn = curl_easy_init();
187 CURLcode sslerrconncode;
189 wxString ServerAddressOnly = wxT("https://") + ServerAddress + wxT(":") + wxString::Format(wxT("%i"), ServerPort) + wxT("/");
194 curl_easy_setopt(sslerrconn, CURLOPT_URL, (const char*)ServerAddressOnly.mb_str(wxConvUTF8));
195 curl_easy_setopt(sslerrconn, CURLOPT_NOPROGRESS, 0);
196 curl_easy_setopt(sslerrconn, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
197 curl_easy_setopt(sslerrconn, CURLOPT_TIMEOUT, 60);
198 curl_easy_setopt(sslerrconn, CURLOPT_FAILONERROR, TRUE);
199 curl_easy_setopt(sslerrconn, CURLOPT_USERAGENT, XSDAB_USERAGENT);
200 curl_easy_setopt(sslerrconn, CURLOPT_WRITEFUNCTION, WritebackFunc);
201 curl_easy_setopt(sslerrconn, CURLOPT_WRITEDATA, &PageData);
202 curl_easy_setopt(sslerrconn, CURLOPT_WRITEHEADER, &PageHeader);
203 curl_easy_setopt(sslerrconn, CURLOPT_PROGRESSDATA, this);
204 curl_easy_setopt(sslerrconn, CURLOPT_PROGRESSFUNCTION, ProgressFunc);
205 curl_easy_setopt(sslerrconn, CURLOPT_NOSIGNAL, 1);
206 curl_easy_setopt(sslerrconn, CURLOPT_SSL_VERIFYPEER, 0);
207 curl_easy_setopt(sslerrconn, CURLOPT_CERTINFO, 1);
208 curl_easy_setopt(sslerrconn, CURLOPT_SSL_VERIFYPEER, 1);
209 curl_easy_setopt(sslerrconn, CURLOPT_SSL_VERIFYHOST, 2);
210 curl_easy_setopt(sslerrconn, CURLOPT_CAINFO, (const char*)ServerCertFilename.mb_str(wxConvUTF8));
212 wxString SSLLocalData;
213 wxString SSLServerData;
215 sslerrconncode = (curl_easy_perform(sslerrconn));
217 SSLCertCol = BuildSSLCollection(sslerrconn);
218 std::map<int, SSLCertData>::iterator SSLCDIter = SSLCertCol.SSLCollection.find(0);
219 std::multimap<wxString,wxString>::iterator SSLDataIter = SSLCDIter->second.CertData.find(wxT("Cert"));
221 wxFFile SSLLocalFile;
223 #if wxABI_VERSION < 20900
224 SSLLocalFile.Open(ServerCertFilename.c_str(), wxT("r"));
226 SSLLocalFile.Open(ServerCertFilename, wxT("r"));
229 // Load the recovery database for tasks not done.
231 if (SSLLocalFile.IsOpened() == TRUE){
233 // Check if we are using wxWidgets version 2.8 or less and
234 // execute the required command accordingly.
236 SSLLocalFile.ReadAll(&SSLLocalData, wxConvAuto());
241 SSLServerData = SSLDataIter->second;
243 if (SSLLocalData == SSLServerData){
245 // Server key matches with local key so retry with CURLOPT_SSL_VERIFYPEER
246 // and CURLOPT_SSL_VERIFYHOST off.
248 curl_easy_cleanup(conn);
249 conn = curl_easy_init();
254 curl_easy_setopt(conn, CURLOPT_URL, (const char*)ServerAddressSSL.mb_str(wxConvUTF8));
255 curl_easy_setopt(conn, CURLOPT_NOPROGRESS, 0);
256 curl_easy_setopt(conn, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
257 curl_easy_setopt(conn, CURLOPT_TIMEOUT, 60);
258 curl_easy_setopt(conn, CURLOPT_FAILONERROR, TRUE);
259 curl_easy_setopt(conn, CURLOPT_USERAGENT, XSDAB_USERAGENT);
260 curl_easy_setopt(conn, CURLOPT_USERPWD, (const char*)ServerAuth.mb_str(wxConvUTF8));
261 curl_easy_setopt(conn, CURLOPT_WRITEFUNCTION, WritebackFunc);
262 curl_easy_setopt(conn, CURLOPT_WRITEDATA, &PageData);
263 curl_easy_setopt(conn, CURLOPT_WRITEHEADER, &PageHeader);
264 curl_easy_setopt(conn, CURLOPT_PROGRESSDATA, this);
265 curl_easy_setopt(conn, CURLOPT_PROGRESSFUNCTION, ProgressFunc);
266 curl_easy_setopt(conn, CURLOPT_CUSTOMREQUEST, "REPORT");
267 curl_easy_setopt(conn, CURLOPT_NOSIGNAL, 1);
268 curl_easy_setopt(conn, CURLOPT_POSTFIELDS, query);
269 curl_easy_setopt(conn, CURLOPT_POSTFIELDSIZE, strlen(query));
270 curl_easy_setopt(conn, CURLOPT_SSL_VERIFYPEER, 0);
271 curl_easy_setopt(conn, CURLOPT_SSL_VERIFYHOST, 0);
273 claconncode = (curl_easy_perform(conn));
279 if (MatchingCert == FALSE){
281 claconncode = CURLE_SSL_CACERT;
286 curl_easy_cleanup(sslerrconn);
292 // Sort out SSL error.
294 // When SSL cert error occurs, connect again and fetch certificates.
295 // Display a message to the user explaining that an invalid
296 // certificate has been given and let the user decide what
299 if (claconncode == CURLE_OK){
301 } else if (claconncode == CURLE_SSL_CACERT || claconncode == CURLE_PEER_FAILED_VERIFICATION){
304 sslerrconn = curl_easy_init();
305 CURLcode sslerrconncode;
307 wxString ServerAddressOnly = wxT("https://") + ServerAddress + wxT(":") + wxString::Format(wxT("%i"), ServerPort) + wxT("/");
309 // Replace conn with sslerrconn!
311 curl_easy_setopt(sslerrconn, CURLOPT_URL, (const char*)ServerAddressOnly.mb_str(wxConvUTF8));
312 curl_easy_setopt(sslerrconn, CURLOPT_NOPROGRESS, 0);
313 curl_easy_setopt(sslerrconn, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
314 curl_easy_setopt(sslerrconn, CURLOPT_TIMEOUT, 60);
315 curl_easy_setopt(sslerrconn, CURLOPT_FAILONERROR, TRUE);
316 curl_easy_setopt(sslerrconn, CURLOPT_USERAGENT, XSDAB_USERAGENT);
317 curl_easy_setopt(sslerrconn, CURLOPT_WRITEFUNCTION, WritebackFunc);
318 curl_easy_setopt(sslerrconn, CURLOPT_WRITEDATA, &PageData);
319 curl_easy_setopt(sslerrconn, CURLOPT_WRITEHEADER, &PageHeader);
320 curl_easy_setopt(sslerrconn, CURLOPT_PROGRESSDATA, this);
321 curl_easy_setopt(sslerrconn, CURLOPT_PROGRESSFUNCTION, ProgressFunc);
322 curl_easy_setopt(sslerrconn, CURLOPT_NOSIGNAL, 1);
323 curl_easy_setopt(sslerrconn, CURLOPT_SSL_VERIFYPEER, 0);
324 curl_easy_setopt(sslerrconn, CURLOPT_CERTINFO, 1);
326 sslerrconncode = (curl_easy_perform(sslerrconn));
328 #if defined(__APPLE__)
330 SetConnectionObject(sslerrconn);
334 SSLCertCol = BuildSSLCollection(sslerrconn);
335 SSLCertCol.SuccessCode = 1;
337 curl_easy_cleanup(conn);
338 curl_easy_cleanup(sslerrconn);
342 } else if (claconncode == CURLE_HTTP_RETURNED_ERROR){
344 fprintf(stderr, "GetServerETagValueThread(): curl_easy_perform() failed: %s\n",
345 curl_easy_strerror(claconncode));
347 curl_easy_getinfo(conn, CURLINFO_RESPONSE_CODE, &http_code);
348 fprintf(stderr, "Error code was: %d\n", http_code);
350 curl_easy_cleanup(conn);
356 fprintf(stderr, "GetServerETagValueThread(): curl_easy_perform() failed: %s\n",
357 curl_easy_strerror(claconncode));
359 curl_easy_getinfo(conn, CURLINFO_RESPONSE_CODE, &http_code);
360 fprintf(stderr, "Error code was: %d\n", http_code);
362 curl_easy_cleanup(conn);
372 wxString EmptyString;
374 curl_easy_setopt(conn, CURLOPT_URL, (const char*)ServerAddressNormal.mb_str(wxConvUTF8));
375 curl_easy_setopt(conn, CURLOPT_NOPROGRESS, 0);
376 curl_easy_setopt(conn, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
377 curl_easy_setopt(conn, CURLOPT_TIMEOUT, 60);
378 curl_easy_setopt(conn, CURLOPT_FAILONERROR, TRUE);
379 curl_easy_setopt(conn, CURLOPT_USERAGENT, XSDAB_USERAGENT);
380 curl_easy_setopt(conn, CURLOPT_USERPWD, (const char*)ServerAuth.mb_str(wxConvUTF8));
381 curl_easy_setopt(conn, CURLOPT_WRITEFUNCTION, WritebackFunc);
382 curl_easy_setopt(conn, CURLOPT_WRITEDATA, &PageData);
383 curl_easy_setopt(conn, CURLOPT_WRITEHEADER, &PageHeader);
384 curl_easy_setopt(conn, CURLOPT_PROGRESSDATA, this);
385 curl_easy_setopt(conn, CURLOPT_PROGRESSFUNCTION, ProgressFunc);
386 curl_easy_setopt(conn, CURLOPT_CUSTOMREQUEST, "REPORT");
387 curl_easy_setopt(conn, CURLOPT_NOSIGNAL, 1);
388 curl_easy_setopt(conn, CURLOPT_POSTFIELDS, query);
389 curl_easy_setopt(conn, CURLOPT_POSTFIELDSIZE, strlen(query));
394 conncode = (curl_easy_perform(conn));
396 if (conncode == CURLE_OK){
398 } else if (conncode == CURLE_HTTP_RETURNED_ERROR){
400 fprintf(stderr, "curl_easy_perform() failed: %s\n",
401 curl_easy_strerror(conncode));
407 fprintf(stderr, "curl_easy_perform() failed: %s\n",
408 curl_easy_strerror(conncode));
416 xmlDocPtr xmlCardDAVDoc;
418 xmlCardDAVDoc = xmlReadMemory(PageData.mb_str(wxConvUTF8), (int)PageData.Len(), "noname.xml", NULL, 0);
420 xmlNodePtr nodeLevel1;
421 xmlNodePtr nodeLevel2;
422 xmlNodePtr nodeLevel3;
423 xmlNodePtr nodeLevel4;
424 xmlNodePtr nodeLevel5;
425 xmlNodePtr nodeLevel6;
427 std::map<wxString,wxString> xmlDataMap;
429 wxString DataFilename;
432 std::string xmlStringSafe;
434 // Tranverse through the catacombs of the response to get our ETag for the file.
436 for (nodeLevel1 = xmlCardDAVDoc->children;
438 nodeLevel1 = nodeLevel1->next)
441 bool HREFFound = FALSE;
442 bool ETagFound = FALSE;
444 for (nodeLevel2 = nodeLevel1->children;
446 nodeLevel2 = nodeLevel2->next)
449 for (nodeLevel3 = nodeLevel2->children;
451 nodeLevel3 = nodeLevel3->next)
454 if (!xmlStrcmp(nodeLevel3->name, (const xmlChar *)"href") ||
455 !xmlStrcmp(nodeLevel3->name, (const xmlChar *)"d:href") ||
456 !xmlStrcmp(nodeLevel3->name, (const xmlChar *)"D:href")
461 for (nodeLevel4 = nodeLevel3->children;
463 nodeLevel4 = nodeLevel4->next)
466 if (!xmlStrcmp(nodeLevel4->name, (const xmlChar *)"text") ||
467 !xmlStrcmp(nodeLevel4->name, (const xmlChar *)"d:text") ||
468 !xmlStrcmp(nodeLevel4->name, (const xmlChar *)"D:text")
471 DataFilename = wxString::FromUTF8((const char*)nodeLevel4->content);
472 wxStringTokenizer wSTDFilename(DataFilename, wxT("/"));
474 while (wSTDFilename.HasMoreTokens()){
476 DataFilename = wSTDFilename.GetNextToken();
490 for (nodeLevel4 = nodeLevel3->children;
492 nodeLevel4 = nodeLevel4->next)
495 for (nodeLevel5 = nodeLevel4->children;
497 nodeLevel5 = nodeLevel5->next)
500 if (!xmlStrcmp(nodeLevel5->name, (const xmlChar *)"getetag") ||
501 !xmlStrcmp(nodeLevel5->name, (const xmlChar *)"d:getetag") ||
502 !xmlStrcmp(nodeLevel5->name, (const xmlChar *)"D:getetag")
505 for (nodeLevel6 = nodeLevel5->children;
507 nodeLevel6 = nodeLevel6->next)
510 // Strip the quotes from the ETag.
512 ETagData = wxString::FromUTF8((const char*)nodeLevel6->content);
513 if (ETagData.Mid(0, 1) == wxT("\"") && ETagData.Mid((ETagData.Len() - 1), 1) == wxT("\"")){
515 ETagData.Remove(0, 1);
516 ETagData.RemoveLast();
536 if (HREFFound == TRUE && ETagFound == TRUE){
538 // Add to the map data.
540 xmlDataMap.insert(std::make_pair(DataFilename, ETagData));
550 xmlFreeDoc(xmlCardDAVDoc);
552 // Get the first result.
554 for (std::map<wxString,wxString>::iterator iter = xmlDataMap.begin();
555 iter != xmlDataMap.end(); ++iter){
557 ETagResult = iter->second;
562 if (ETagResult.IsEmpty()){
572 void CardDAV::GetServerETagValue(){
574 // Get the server etag value.
576 std::thread ConnectThread(&CardDAV::GetServerETagValueThread, this);
577 ConnectThread.detach();