syncevolution/src/backends/webdav/WebDAVSource.cpp

2385 lines
96 KiB
C++
Raw Normal View History

/*
* Copyright (C) 2010 Intel Corporation
*/
#include "WebDAVSource.h"
#include <boost/bind.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/find.hpp>
#include <boost/scoped_ptr.hpp>
#include <syncevo/LogRedirect.h>
#include <syncevo/IdentityProvider.h>
#include <boost/assign.hpp>
#include <stdio.h>
#include <errno.h>
SE_BEGIN_CXX
BoolConfigProperty &WebDAVCredentialsOkay()
{
static BoolConfigProperty okay("webDAVCredentialsOkay", "credentials were accepted before");
return okay;
}
#ifdef ENABLE_DAV
/**
* Retrieve settings from SyncConfig.
* NULL pointer for config is allowed.
*/
class ContextSettings : public Neon::Settings {
public:
/**
* Base URL(s) for WebDAV service.
* More than one can be give as candidate for scanning.
*/
typedef std::vector<std::string> URLs;
private:
boost::shared_ptr<SyncConfig> m_context;
SyncSourceConfig *m_sourceConfig;
URLs m_urls;
std::string m_urlsDescription;
std::string m_url;
std::string m_urlDescription;
/** do change tracking without relying on CTag */
bool m_noCTag;
bool m_googleUpdateHack;
bool m_googleAlarmHack;
// credentials were valid in the past: stored persistently in tracking node
bool m_credentialsOkay;
public:
ContextSettings(const boost::shared_ptr<SyncConfig> &context,
SyncSourceConfig *sourceConfig) :
m_context(context),
m_sourceConfig(sourceConfig),
m_noCTag(false),
m_googleUpdateHack(false),
m_googleAlarmHack(false),
m_credentialsOkay(false)
{
URLs urls;
std::string description = "<unset>";
std::string syncName = m_context->getConfigName();
if (syncName.empty()) {
syncName = "<none>";
}
// check source config first
if (m_sourceConfig) {
urls.push_back(m_sourceConfig->getDatabaseID());
std::string &url = urls.front();
std::string sourceName = m_sourceConfig->getName();
if (sourceName.empty()) {
sourceName = "<none>";
}
source -> datastore rename, improved terminology The word "source" implies reading, while in fact access is read/write. "datastore" avoids that misconception. Writing it in one word emphasizes that it is single entity. While renaming, also remove references to explicit --*-property parameters. The only necessary use today is "--sync-property ?" and "--datastore-property ?". --datastore-property was used instead of the short --store-property because "store" might be mistaken for the verb. It doesn't matter that it is longer because it doesn't get typed often. --source-property must remain valid for backward compatility. As many user-visible instances of "source" as possible got replaced in text strings by the newer term "datastore". Debug messages were left unchanged unless some regex happened to match it. The source code will continue to use the old variable and class names based on "source". Various documentation enhancements: Better explain what local sync is and how it involves two sync configs. "originating config" gets introduces instead of just "sync config". Better explain the relationship between contexts, sync configs, and source configs ("a sync config can use the datastore configs in the same context"). An entire section on config properties in the terminology section. "item" added (Todd Wilson correctly pointed out that it was missing). Less focus on conflict resolution, as suggested by Graham Cobb. Fix examples that became invalid when fixing the password storage/lookup mechanism for GNOME keyring in 1.4. The "command line conventions", "Synchronization beyond SyncML" and "CalDAV and CardDAV" sections were updated. It's possible that the other sections also contain slightly incorrect usage of the terminology or are simply out-dated.
2014-07-28 15:29:41 +02:00
description = StringPrintf("sync config '%s', datastore config '%s', database='%s'",
syncName.c_str(),
sourceName.c_str(),
url.c_str());
}
// fall back to sync context
if ((urls.empty() || (urls.size() == 1 && urls.front().empty())) && m_context) {
urls = m_context->getSyncURL();
description = StringPrintf("sync config '%s', syncURL='%s'",
syncName.c_str(),
boost::join(urls, " ").c_str());
}
// remember result and set flags
setURLs(urls, description);
if (!urls.empty()) {
setURL(urls.front(), description);
}
// m_credentialsOkay: no corresponding setting when using
// credentials + URL from source config, in which case we
// never know that credentials should work (bad for Google,
// with its temporary authentication errors)
if (m_context) {
boost::shared_ptr<FilterConfigNode> node = m_context->getNode(WebDAVCredentialsOkay());
m_credentialsOkay = WebDAVCredentialsOkay().getPropertyValue(*node);
}
}
void setURLs(const URLs &urls, const std::string &description) { m_urls = urls; m_urlsDescription = description; }
URLs getURLs() { return m_urls; }
std::string getURLsDescription() { return m_urlsDescription; }
void setURL(const std::string &url, const std::string &description) { initializeFlags(url); m_url = url; m_urlDescription = description; }
std::string getURL() { return m_url; }
std::string getURLDescription() { return m_urlDescription; }
virtual bool verifySSLHost()
{
return !m_context || m_context->getSSLVerifyHost();
}
virtual bool verifySSLCertificate()
{
return !m_context || m_context->getSSLVerifyServer();
}
virtual std::string proxy()
{
if (!m_context ||
!m_context->getUseProxy()) {
return "";
} else {
return m_context->getProxyHost();
}
}
bool noCTag() const { return m_noCTag; }
virtual bool googleUpdateHack() const { return m_googleUpdateHack; }
virtual bool googleAlarmHack() const { return m_googleAlarmHack; }
virtual int timeoutSeconds() const { return m_context->getRetryDuration(); }
virtual int retrySeconds() const {
int seconds = m_context->getRetryInterval();
if (seconds >= 0) {
seconds /= (120 / 5); // default: 2min => 5s
}
return seconds;
}
virtual void getCredentials(const std::string &realm,
std::string &username,
std::string &password);
virtual boost::shared_ptr<AuthProvider> getAuthProvider();
std::string getUsername()
{
lookupAuthProvider();
return m_authProvider->getUsername();
}
virtual bool getCredentialsOkay() { return m_credentialsOkay; }
virtual void setCredentialsOkay(bool okay) {
if (m_credentialsOkay != okay && m_context) {
boost::shared_ptr<FilterConfigNode> node = m_context->getNode(WebDAVCredentialsOkay());
if (!node->isReadOnly()) {
WebDAVCredentialsOkay().setProperty(*node, okay);
node->flush();
}
m_credentialsOkay = okay;
}
}
virtual int logLevel()
{
return m_context ?
m_context->getLogLevel().get() :
Logger::instance().getLevel();
}
private:
void initializeFlags(const std::string &url);
boost::shared_ptr<AuthProvider> m_authProvider;
void lookupAuthProvider();
};
void ContextSettings::getCredentials(const std::string &realm,
std::string &username,
std::string &password)
{
lookupAuthProvider();
Credentials creds = m_authProvider->getCredentials();
username = creds.m_username;
password = creds.m_password;
}
boost::shared_ptr<AuthProvider> ContextSettings::getAuthProvider()
{
lookupAuthProvider();
return m_authProvider;
}
void ContextSettings::lookupAuthProvider()
{
if (m_authProvider) {
return;
}
UserIdentity identity;
InitStateString password;
// prefer source config if anything is set there
const char *credentialsFrom = "undefined";
if (m_sourceConfig) {
identity = m_sourceConfig->getUser();
password = m_sourceConfig->getPassword();
source -> datastore rename, improved terminology The word "source" implies reading, while in fact access is read/write. "datastore" avoids that misconception. Writing it in one word emphasizes that it is single entity. While renaming, also remove references to explicit --*-property parameters. The only necessary use today is "--sync-property ?" and "--datastore-property ?". --datastore-property was used instead of the short --store-property because "store" might be mistaken for the verb. It doesn't matter that it is longer because it doesn't get typed often. --source-property must remain valid for backward compatility. As many user-visible instances of "source" as possible got replaced in text strings by the newer term "datastore". Debug messages were left unchanged unless some regex happened to match it. The source code will continue to use the old variable and class names based on "source". Various documentation enhancements: Better explain what local sync is and how it involves two sync configs. "originating config" gets introduces instead of just "sync config". Better explain the relationship between contexts, sync configs, and source configs ("a sync config can use the datastore configs in the same context"). An entire section on config properties in the terminology section. "item" added (Todd Wilson correctly pointed out that it was missing). Less focus on conflict resolution, as suggested by Graham Cobb. Fix examples that became invalid when fixing the password storage/lookup mechanism for GNOME keyring in 1.4. The "command line conventions", "Synchronization beyond SyncML" and "CalDAV and CardDAV" sections were updated. It's possible that the other sections also contain slightly incorrect usage of the terminology or are simply out-dated.
2014-07-28 15:29:41 +02:00
credentialsFrom = "datastore config";
}
// fall back to context
if (m_context && !identity.wasSet() && !password.wasSet()) {
identity = m_context->getSyncUser();
password = m_context->getSyncPassword();
source -> datastore rename, improved terminology The word "source" implies reading, while in fact access is read/write. "datastore" avoids that misconception. Writing it in one word emphasizes that it is single entity. While renaming, also remove references to explicit --*-property parameters. The only necessary use today is "--sync-property ?" and "--datastore-property ?". --datastore-property was used instead of the short --store-property because "store" might be mistaken for the verb. It doesn't matter that it is longer because it doesn't get typed often. --source-property must remain valid for backward compatility. As many user-visible instances of "source" as possible got replaced in text strings by the newer term "datastore". Debug messages were left unchanged unless some regex happened to match it. The source code will continue to use the old variable and class names based on "source". Various documentation enhancements: Better explain what local sync is and how it involves two sync configs. "originating config" gets introduces instead of just "sync config". Better explain the relationship between contexts, sync configs, and source configs ("a sync config can use the datastore configs in the same context"). An entire section on config properties in the terminology section. "item" added (Todd Wilson correctly pointed out that it was missing). Less focus on conflict resolution, as suggested by Graham Cobb. Fix examples that became invalid when fixing the password storage/lookup mechanism for GNOME keyring in 1.4. The "command line conventions", "Synchronization beyond SyncML" and "CalDAV and CardDAV" sections were updated. It's possible that the other sections also contain slightly incorrect usage of the terminology or are simply out-dated.
2014-07-28 15:29:41 +02:00
credentialsFrom = "context";
}
SE_LOG_DEBUG(NULL, "using username '%s' from %s for WebDAV, password %s",
identity.toString().c_str(),
credentialsFrom,
password.wasSet() ? "was set" : "not set");
// lookup actual authentication method instead of assuming username/password
m_authProvider = AuthProvider::create(identity, password);
}
void ContextSettings::initializeFlags(const std::string &url)
{
bool googleUpdate = false,
googleAlarm = false,
noCTag = false;
Neon::URI uri = Neon::URI::parse(url);
typedef boost::split_iterator<string::iterator> string_split_iterator;
for (string_split_iterator arg =
boost::make_split_iterator(uri.m_query, boost::first_finder("&", boost::is_iequal()));
arg != string_split_iterator();
++arg) {
static const std::string keyword = "SyncEvolution=";
if (boost::istarts_with(*arg, keyword)) {
std::string params(arg->begin() + keyword.size(), arg->end());
for (string_split_iterator flag =
boost::make_split_iterator(params,
boost::first_finder(",", boost::is_iequal()));
flag != string_split_iterator();
++flag) {
if (boost::iequals(*flag, "UpdateHack")) {
googleUpdate = true;
} else if (boost::iequals(*flag, "ChildHack")) {
// Not used anymore, flag ignored.
} else if (boost::iequals(*flag, "AlarmHack")) {
googleAlarm = true;
} else if (boost::iequals(*flag, "Google")) {
googleUpdate =
googleAlarm = true;
} else if (boost::iequals(*flag, "NoCTag")) {
noCTag = true;
} else {
SE_THROW(StringPrintf("unknown SyncEvolution flag %s in URL %s",
std::string(flag->begin(), flag->end()).c_str(),
url.c_str()));
}
}
} else if (arg->end() != arg->begin()) {
SE_THROW(StringPrintf("unknown parameter %s in URL %s",
std::string(arg->begin(), arg->end()).c_str(),
url.c_str()));
}
}
// store final result
m_googleUpdateHack = googleUpdate;
m_googleAlarmHack = googleAlarm;
m_noCTag = noCTag;
}
WebDAVSource::Props_t::mapped_type & WebDAVSource::Props_t::operator [] (const WebDAVSource::Props_t::key_type &key)
{
iterator it = find(key);
if (it != end()) {
return it->second;
} else {
push_back(value_type(key, mapped_type()));
return back().second;
}
}
WebDAVSource::Props_t::iterator WebDAVSource::Props_t::find(const WebDAVSource::Props_t::key_type &key) {
for (iterator it = begin();
it != end();
++it) {
if (it->first == key) {
return it;
}
}
return end();
}
WebDAVSource::WebDAVSource(const SyncSourceParams &params,
const boost::shared_ptr<Neon::Settings> &settings) :
TrackingSyncSource(params),
m_settings(settings)
{
if (!m_settings) {
m_contextSettings.reset(new ContextSettings(params.m_context, this));
m_settings = m_contextSettings;
}
/* insert contactServer() into BackupData_t and RestoreData_t (implemented by SyncSourceRevisions) */
m_operations.m_backupData = boost::bind(&WebDAVSource::backupData,
this, m_operations.m_backupData, _1, _2, _3);
m_operations.m_restoreData = boost::bind(&WebDAVSource::restoreData,
this, m_operations.m_restoreData, _1, _2, _3);
// ignore the "Request ends, status 207 class 2xx, error line:" printed by neon
LogRedirect::addIgnoreError(", error line:");
// ignore error messages in returned data
LogRedirect::addIgnoreError("Read block (");
}
static const std::string UID("\nUID:");
const std::string *WebDAVSource::createResourceName(const std::string &item, std::string &buffer, std::string &luid)
{
luid = extractUID(item);
std::string suffix = getSuffix();
if (luid.empty()) {
// must modify item
luid = UUID();
buffer = item;
size_t start = buffer.find("\nEND:" + getContent());
if (start != buffer.npos) {
start++;
buffer.insert(start, StringPrintf("UID:%s\r\n", luid.c_str()));
}
luid += suffix;
return &buffer;
} else {
luid += suffix;
return &item;
}
}
const std::string *WebDAVSource::setResourceName(const std::string &item, std::string &buffer, const std::string &luid)
{
std::string olduid = luid;
std::string suffix = getSuffix();
if (boost::ends_with(olduid, suffix)) {
olduid.resize(olduid.size() - suffix.size());
}
// First check if the item already contains the right UID
// or at least some UID. If there is a UID, we trust it to be correct,
// because our guess here (resource name == UID) can be wrong, for
// example for items created by other clients or by us when using
// POST and letting the server choose the resource name.
//
// This relies on our peer doing the right thing.
size_t start, end;
std::string uid = extractUID(item, &start, &end);
if (uid == olduid || !uid.empty()) {
return &item;
}
// insert or overwrite
buffer = item;
if (start != std::string::npos) {
// overwrite
buffer.replace(start, end - start, olduid);
} else {
// insert
start = buffer.find("\nEND:" + getContent());
if (start != buffer.npos) {
start++;
buffer.insert(start, StringPrintf("UID:%s\n", olduid.c_str()));
}
}
return &buffer;
}
std::string WebDAVSource::extractUID(const std::string &item, size_t *startp, size_t *endp)
{
std::string luid;
if (startp) {
*startp = std::string::npos;
}
if (endp) {
*endp = std::string::npos;
}
// find UID, use that plus ".vcf" as resource name (expected by Yahoo Contacts)
size_t start = item.find(UID);
if (start != item.npos) {
start += UID.size();
size_t end = item.find("\n", start);
if (end != item.npos) {
if (startp) {
*startp = start;
}
luid = item.substr(start, end - start);
if (boost::ends_with(luid, "\r")) {
luid.resize(luid.size() - 1);
}
// keep checking for more lines because of folding
while (end + 1 < item.size() &&
item[end + 1] == ' ') {
start = end + 1;
end = item.find("\n", start);
if (end == item.npos) {
// incomplete, abort
luid = "";
if (startp) {
*startp = std::string::npos;
}
break;
}
luid += item.substr(start, end - start);
if (boost::ends_with(luid, "\r")) {
luid.resize(luid.size() - 1);
}
}
// success, return all information
if (endp) {
// don't include \r or \n
*endp = item[end - 1] == '\r' ?
end - 1 :
end;
}
}
}
return luid;
}
std::string WebDAVSource::getSuffix() const
{
return getContent() == "VCARD" ?
".vcf" :
".ics";
}
void WebDAVSource::replaceHTMLEntities(std::string &item)
{
while (true) {
bool found = false;
std::string decoded;
size_t last = 0; // last character copied
size_t next = 0; // next character to be looked at
while (true) {
next = item.find('&', next);
size_t start = next;
if (next == item.npos) {
// finish decoding
if (found) {
decoded.append(item, last, item.size() - last);
}
break;
}
next++;
size_t end = next;
while (end != item.size()) {
char c = item[end];
if ((c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
(c == '#')) {
end++;
} else {
break;
}
}
if (end == item.size() || item[end] != ';') {
// Invalid character between & and ; or no
// proper termination? No entity, continue
// decoding in next loop iteration.
next = end;
continue;
}
unsigned char c = 0;
if (next < end) {
if (item[next] == '#') {
// decimal or hexadecimal number
next++;
if (next < end) {
int base;
if (item[next] == 'x') {
// hex
base = 16;
next++;
} else {
base = 10;
}
while (next < end) {
unsigned char v = tolower(item[next]);
if (v >= '0' && v <= '9') {
next++;
c = c * base + (v - '0');
} else if (base == 16 && v >= 'a' && v <= 'f') {
next++;
c = c * base + (v - 'a') + 10;
} else {
// invalid character, abort scanning of this entity
break;
}
}
}
} else {
// check for entities
struct {
const char *m_name;
unsigned char m_character;
} entities[] = {
// core entries, extend as needed...
{ "quot", '"' },
{ "amp", '&' },
{ "apos", '\'' },
{ "lt", '<' },
{ "gt", '>' },
{ NULL, 0 }
};
int i = 0;
while (true) {
const char *name = entities[i].m_name;
if (!name) {
break;
}
if (!item.compare(next, end - next, name)) {
c = entities[i].m_character;
next += strlen(name);
break;
}
i++;
}
}
if (next == end) {
// swallowed all characters in entity, must be valid:
// copy all uncopied characters plus the new one
found = true;
decoded.reserve(item.size());
decoded.append(item, last, start - last);
decoded.append(1, c);
last = end + 1;
}
}
next = end + 1;
}
if (found) {
item = decoded;
} else {
break;
}
}
}
void WebDAVSource::open()
{
// Nothing to do here, expensive initialization is in contactServer().
}
static bool setFirstURL(Neon::URI &result,
bool &resultIsReadOnly,
const std::string &name,
const Neon::URI &uri,
bool isReadOnly)
{
if (result.empty() ||
// Overwrite read-only with read/write collection.
(resultIsReadOnly && !isReadOnly)) {
result = uri;
resultIsReadOnly = isReadOnly;
}
// Stop if read/write found.
return resultIsReadOnly;
}
void WebDAVSource::contactServer()
{
if (!m_calendar.empty() &&
m_session) {
// we have done this work before, no need to repeat it
return;
}
SE_LOG_DEBUG(NULL, "using libneon %s with %s",
ne_version_string(), Neon::features().c_str());
// Can we skip auto-detection because a full resource URL is set?
std::string database = getDatabaseID();
if (!database.empty() &&
m_contextSettings) {
m_calendar = Neon::URI::parse(database, true);
// m_contextSettings = m_settings, so this sets m_settings->getURL()
m_contextSettings->setURL(database,
StringPrintf("%s database=%s",
getDisplayName().c_str(),
database.c_str()));
// start talking to host defined by m_settings->getURL()
m_session = Neon::Session::create(m_settings);
SE_LOG_INFO(getDisplayName(), "using configured database=%s", database.c_str());
// force authentication via username/password or OAuth2
m_session->forceAuthorization(m_settings->getAuthProvider());
return;
}
// Create session and find first collection (the default). Prefer
// read/write collections over read-only, just like getDatabases()
// does.
bool isReadOnly;
m_calendar = Neon::URI();
SE_LOG_INFO(getDisplayName(), "determine final URL based on %s",
m_contextSettings ? m_contextSettings->getURLDescription().c_str() : "");
findCollections(boost::bind(setFirstURL,
boost::ref(m_calendar),
boost::ref(isReadOnly),
_1, _2, _3));
if (m_calendar.empty()) {
throwError(SE_HERE, "no database found");
}
SE_LOG_INFO(getDisplayName(), "final URL path %s", m_calendar.m_path.c_str());
// Check some server capabilities. Purely informational at this
// point, doesn't have to succeed either (Google 401 throttling
// workaround not active here, so it may really fail!).
#ifdef HAVE_LIBNEON_OPTIONS
if (Logger::instance().getLevel() >= Logger::DEV) {
try {
SE_LOG_DEBUG(NULL, "read capabilities of %s", m_calendar.toURL().c_str());
m_session->startOperation("OPTIONS", Timespec());
int caps = m_session->options(m_calendar.m_path);
static const Flag descr[] = {
{ NE_CAP_DAV_CLASS1, "Class 1 WebDAV (RFC 2518)" },
{ NE_CAP_DAV_CLASS2, "Class 2 WebDAV (RFC 2518)" },
{ NE_CAP_DAV_CLASS3, "Class 3 WebDAV (RFC 4918)" },
{ NE_CAP_MODDAV_EXEC, "mod_dav 'executable' property" },
{ NE_CAP_DAV_ACL, "WebDAV ACL (RFC 3744)" },
{ NE_CAP_VER_CONTROL, "DeltaV version-control" },
{ NE_CAP_CO_IN_PLACE, "DeltaV checkout-in-place" },
{ NE_CAP_VER_HISTORY, "DeltaV version-history" },
{ NE_CAP_WORKSPACE, "DeltaV workspace" },
{ NE_CAP_UPDATE, "DeltaV update" },
{ NE_CAP_LABEL, "DeltaV label" },
{ NE_CAP_WORK_RESOURCE, "DeltaV working-resouce" },
{ NE_CAP_MERGE, "DeltaV merge" },
{ NE_CAP_BASELINE, "DeltaV baseline" },
{ NE_CAP_ACTIVITY, "DeltaV activity" },
{ NE_CAP_VC_COLLECTION, "DeltaV version-controlled-collection" },
{ 0, NULL }
};
SE_LOG_DEBUG(NULL, "%s WebDAV capabilities: %s",
m_session->getURL().c_str(),
Flags2String(caps, descr).c_str());
} catch (const Neon::FatalException &ex) {
throw;
} catch (...) {
Exception::handle();
}
}
#endif // HAVE_LIBNEON_OPTIONS
}
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
class Candidate {
public:
enum Flags {
LIST = (1u << 0), // Also list all members to find more candidates.
NONE = 0
};
/** Normalizes url if non-empty, interprets it relative to uri. */
Candidate(const Neon::URI &uri, const std::string &url, int flags) :
m_uri(uri),
m_flags(flags)
{
if (url.empty()) {
m_uri.m_path = "";
} else {
// Use normalize path with current host, unless the url contained
// its own host and protocol.
Neon::URI other = Neon::URI::parse(url);
if (other.m_scheme.empty()) {
other.m_scheme = uri.m_scheme;
}
if (!other.m_port) {
other.m_port = uri.m_port;
}
if (other.m_host.empty()) {
other.m_host = uri.m_host;
}
m_uri = other;
}
}
Candidate(const Neon::URI &uri, int flags) :
m_uri(uri),
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
m_flags(flags)
{}
Candidate() :
m_flags(NONE)
{}
Neon::URI m_uri;
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
int m_flags;
// operator bool () const { return !m_path.empty(); }
bool empty() const { return m_uri.empty(); }
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
bool operator < (const Candidate &other) const {
int compare = m_uri.compare(other.m_uri);
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
return compare < 0 || (compare == 0 && m_flags < other.m_flags);
}
bool operator == (const Candidate &other) const {
return m_uri == other.m_uri && m_flags == other.m_flags;
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
}
};
std::string WebDAVSource::lookupDNSSRV(const std::string &domain)
{
std::string url;
int timeoutSeconds = m_settings->timeoutSeconds();
int retrySeconds = m_settings->retrySeconds();
FILE *in = NULL;
try {
Timespec startTime = Timespec::monotonic();
retry:
in = popen(StringPrintf("syncevo-webdav-lookup '%s' '%s'",
serviceType().c_str(),
domain.c_str()).c_str(),
"r");
if (!in) {
throwError(SE_HERE, "starting syncevo-webdav-lookup for DNS SRV lookup failed", errno);
}
// ridicuously long URLs are truncated...
char buffer[1024];
size_t read = fread(buffer, 1, sizeof(buffer) - 1, in);
buffer[read] = 0;
if (read > 0 && buffer[read - 1] == '\n') {
read--;
}
buffer[read] = 0;
url = buffer;
int res = pclose(in);
in = NULL;
if (res != -1 && WIFEXITED(res)) {
res = WEXITSTATUS(res);
} else {
res = -1;
}
switch (res) {
case 0:
SE_LOG_DEBUG(getDisplayName(), "found syncURL '%s' via DNS SRV", buffer);
break;
case 2:
throwError(SE_HERE, StringPrintf("syncevo-webdav-lookup did not find a DNS utility to search for %s in %s", serviceType().c_str(), domain.c_str()));
break;
case 3:
throwError(SE_HERE, StringPrintf("DNS SRV search for %s in %s did not find the service", serviceType().c_str(), domain.c_str()));
break;
case -1:
throwError(SE_HERE, StringPrintf("DNS SRV search for %s in %s failed", serviceType().c_str(), domain.c_str()));
break;
default: {
Timespec now = Timespec::monotonic();
if (retrySeconds > 0 &&
timeoutSeconds > 0) {
if (now < startTime + timeoutSeconds) {
SE_LOG_DEBUG(getDisplayName(), "DNS SRV search failed due to network issues, retry in %d seconds",
retrySeconds);
Sleep(retrySeconds);
goto retry;
} else {
SE_LOG_INFO(getDisplayName(), "DNS SRV search timed out after %d seconds", timeoutSeconds);
}
}
// probably network problem
throwError(SE_HERE, STATUS_TRANSPORT_FAILURE, StringPrintf("DNS SRV search for %s in %s failed", serviceType().c_str(), domain.c_str()));
break;
}
}
} catch (...) {
if (in) {
pclose(in);
}
throw;
}
return url;
}
bool WebDAVSource::findCollections(const boost::function<bool (const std::string &,
const Neon::URI &,
bool isReadOnly)> &storeResult)
{
bool res = true; // completed
int timeoutSeconds = m_settings->timeoutSeconds();
int retrySeconds = m_settings->retrySeconds();
SE_LOG_DEBUG(getDisplayName(), "timout %ds, retry %ds => %s",
timeoutSeconds, retrySeconds,
(timeoutSeconds <= 0 ||
retrySeconds <= 0) ? "resending disabled" : "resending allowed");
boost::shared_ptr<AuthProvider> authProvider = m_contextSettings->getAuthProvider();
std::string username = authProvider->getUsername();
// If no URL was configured, then try DNS SRV lookup.
// syncevo-webdav-lookup and at least one of the tools
// it depends on (host, nslookup, adnshost, ...) must
// be in the shell search path.
//
// Only our own m_contextSettings allows overriding the
// URL. Not an issue, in practice it is always used.
bool didDNS = false;
ContextSettings::URLs urls;
if (m_contextSettings) {
urls = m_contextSettings->getURLs();
} else {
urls.push_back(m_settings->getURL());
}
if ((urls.empty() || (urls.size() == 1 && urls.front().empty())) && m_contextSettings) {
didDNS = true;
size_t pos = username.find('@');
if (pos == username.npos) {
// throw authentication error to indicate that the credentials are wrong
throwError(SE_HERE, STATUS_UNAUTHORIZED, StringPrintf("syncURL not configured and username %s does not contain a domain", username.c_str()));
}
std::string domain = username.substr(pos + 1);
std::string url = lookupDNSSRV(domain);
urls.clear();
urls.push_back(url);
m_contextSettings->setURLs(urls,
StringPrintf("DNS SRV URL for domain %s and service %s",
domain.c_str(),
serviceType().c_str()));
}
// start talking to host defined by m_settings->getURL()
m_session = Neon::Session::create(m_settings);
SE_LOG_INFO(getDisplayName(), "start database search at %s%s%s",
m_settings->getURL().c_str(),
m_contextSettings ? ", from " : "",
m_contextSettings ? m_contextSettings->getURLDescription().c_str() : "");
WebDAV: added CardDAV support This patch moves some of the CalDAV-specific code out of WebDAVSource into CalDAVSource and adds the corresponding CardDAV code in the new CardDAVSource. It improves searching for the right collection. A Yahoo Contacts specific problem is that vCard UID and resource name have to match, with ".vcf" used as suffix in the resource name. The new createResourceName() and setResourceName() virtual methods ensure that. They are nops in CalDAV and use simple string manipulation for vCard. Another Yahoo Contacts odity is merging of contacts in PUT, without telling the client. This can be detected by checking the expected resource name with PROPFIND: the server then replies with the real resource name. This check is done only if no href is returned in the response to PUT, so it should only be triggered for Yahoo Contacts. CardDAV support is still experimental. It was tested with Yahoo Contacts, using https://carddav.address.yahoo.com/dav/%u as sync URL. This is different from the CalDAV URL, which currently prevents using the same @yahoo context for both calendar and contacts. Working on CardDAV showed that resource name comparisons had issues due to different encoding of special characters. Added canonicalization of paths to fix this problem. This also includes appending the trailing slash for collections (Neon::URI::normalizePath()). Note that Buteo only serializes sync sessions based on their type, so as soon as we allow CardDAV in addition to CalDAV, it might happen that SyncEvolution is invokved multiple times in parallel => need to remove global variables first.
2010-11-25 10:08:07 +01:00
// Find default calendar. Same for address book, with slightly
// different parameters.
//
// Stops when:
// - current path is calendar collection (= contains VEVENTs)
// Gives up:
// - when running in circles
// - nothing else to try out
// - tried 10 times
// Follows:
// - current-user-principal
// - CalDAV calendar-home-set
// - collections
//
// TODO: support more than one calendar. Instead of stopping at the first one,
// scan more throroughly, then decide deterministically.
int counter = 0;
const int limit = 1000;
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
// Keeps track of paths to look at and those which were already
// tested. What is done for each candidate varies.
class Tried : public std::set<Candidate> {
std::list<Candidate> m_candidates;
bool m_found;
public:
Tried() : m_found(false) {}
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
/** Was path not tested yet and is not already a candidate? */
bool isNew(const Candidate &candidate) {
return !candidate.empty() && find(candidate) == end() &&
std::find(m_candidates.begin(), m_candidates.end(), candidate) == m_candidates.end();
}
/** Hand over next candidate to caller, empty if none available. */
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
Candidate getNextCandidate() {
if (!m_candidates.empty() ) {
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
Candidate candidate = m_candidates.front();
m_candidates.pop_front();
return candidate;
} else {
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
return Candidate();
}
}
/** remember that path was tested */
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
void insert(const Candidate &candidate) {
std::set<Candidate>::insert(candidate);
m_candidates.remove(candidate);
}
enum Position {
FRONT,
BACK
};
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
void addCandidate(const Candidate &candidate, Position position) {
if (isNew(candidate)) {
if (position == FRONT) {
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
m_candidates.push_front(candidate);
} else {
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
m_candidates.push_back(candidate);
}
}
}
void foundResult() { m_found = true; }
/** Nothing left to try and nothing found => bail out with error for last candidate. */
bool errorIsFatal() { return m_candidates.empty() && !m_found; }
} tried;
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
// Populate URLs to be scanned with configured URLs.
BOOST_FOREACH(const std::string &url, urls) {
Neon::URI uri = Neon::URI::parse(url);
// Avoid listing members for the initial URLs. If the user gave us
// the root of a generic WebDAV server, a recursive listing of
// all resource collections on it will take too long. We only
// list the home sets.
Candidate candidate(Neon::URI::parse(url), Candidate::NONE);
tried.addCandidate(candidate, Tried::BACK);
// Add well-known URL as fallback to be tried if configured
// path was empty. eGroupware also replies with a redirect for the
// empty path, but relying on that alone is risky because it isn't
// specified.
if (candidate.m_uri.m_path.empty() || candidate.m_uri.m_path == "/") {
std::string wellknown = wellKnownURL();
if (!wellknown.empty()) {
tried.addCandidate(Candidate(candidate.m_uri, wellknown, Candidate::NONE), Tried::BACK);
}
}
}
Candidate candidate = tried.getNextCandidate();
Props_t davProps;
Neon::Session::PropfindPropCallback_t callback =
boost::bind(&WebDAVSource::openPropCallback,
this, boost::ref(davProps), _1, _2, _3, _4);
// With Yahoo! the initial connection often failed with 50x
// errors. Retrying individual requests is error prone because at
// least one (asking for .well-known/[caldav|carddav]) always
// results in 502. Let the PROPFIND requests be resent, but in
// such a way that the overall discovery will never take longer
// than the total configured timeout period.
//
// The PROPFIND with openPropCallback is idempotent, because it
// will just overwrite previously found information in davProps.
// Therefore resending is okay.
Timespec finalDeadline = createDeadline(); // no resending if left empty
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
// Remember whether we have found the home set. If we do not come
// across it as part of the regular search, then we need to search
// a bit harder for it.
bool haveHomeSet = false;
// Remember whether we have results for https://apidata.googleusercontent.com:443/caldav/v2.
bool haveGoogleCalDAV2 = false;
while (true) {
bool usernameInserted = false;
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
Candidate next;
// Replace %u with the username, if the %u is found. Also, keep track
// of this event happening, because if we later on get a 404 error,
// we will convert it to 401 only if the path contains the username
// and it was indeed us who put the username there (not the server).
if (boost::find_first(candidate.m_uri.m_path, "%u")) {
boost::replace_all(candidate.m_uri.m_path, "%u", Neon::URI::escape(username));
usernameInserted = true;
}
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
tried.insert(candidate);
SE_LOG_DEBUG(NULL, "testing %s", candidate.m_uri.toURL().c_str());
Neon::URI currentURI = m_session->getURI();
Neon::URI &newURI = candidate.m_uri;
bool success = false;
bool isWellKnown = boost::starts_with(candidate.m_uri.m_path, "/.well-known/");
// Special hack Google: if we already have results for the current CalDAV
// endpoint, then don't try the legacy one.
if (newURI.m_host == "www.google.com" &&
(boost::starts_with(newURI.m_path, "/calendar/dav/") || newURI.m_path =="/calendar/dav") &&
haveGoogleCalDAV2) {
SE_LOG_DEBUG(getDisplayName(), "skipping legacy Google CalDAV");
goto next;
}
// Accessing the well-known URIs should lead to a redirect, but
// with Yahoo! Calendar all I got was a 502 "connection refused".
// Yahoo! Contacts also doesn't redirect. Instead on ends with
// a Principal resource - perhaps reading that would lead further.
//
// So anyway, let's try the well-known URI first, but also add
// the root path as fallback.
if (candidate.m_uri.m_path == "/.well-known/caldav/" ||
candidate.m_uri.m_path == "/.well-known/carddav/") {
// remove trailing slash added by normalization, to be aligned with draft-daboo-srv-caldav-10
candidate.m_uri.m_path.resize(candidate.m_uri.m_path.size() - 1);
// Yahoo! Calendar returns no redirect. According to rfc4918 appendix-E,
// a client may simply try the root path in case of such a failure,
// which happens to work for Yahoo.
tried.addCandidate(Candidate(currentURI, "/", Candidate::NONE), Tried::BACK);
// TODO: Google Calendar, with workarounds
// candidates.push_back(StringPrintf("/calendar/dav/%s/user/", Neon::URI::escape(username).c_str()));
}
try {
if (newURI.m_scheme != currentURI.m_scheme ||
newURI.m_host != currentURI.m_host ||
newURI.getPort() != currentURI.getPort()) {
// Need to re-initialize the session.
if (m_contextSettings) {
SE_LOG_DEBUG(getDisplayName(), "switching HTTP session from %s to %s",
currentURI.toURL().c_str(),
newURI.toURL().c_str());
m_contextSettings->setURL(newURI.toURL(),
"redirect during database scan");
} else {
SE_THROW(StringPrintf("switching HTTP session from %s to %s not possible at the moment",
currentURI.toURL().c_str(),
newURI.toURL().c_str()));
}
m_session = Neon::Session::create(m_settings);
}
currentURI = newURI;
// disable resending for some known cases where it never succeeds
Timespec deadline = finalDeadline;
if (isWellKnown &&
m_settings->getURL().find("yahoo.com") != string::npos) {
deadline = Timespec();
}
if (Logger::instance().getLevel() >= Logger::DEV) {
// First dump WebDAV "allprops" properties (does not contain
// properties which must be asked for explicitly!). Only
// relevant for debugging.
try {
SE_LOG_DEBUG(NULL, "debugging: read all WebDAV properties of %s", candidate.m_uri.toURL().c_str());
// Use OAuth2, if available.
boost::shared_ptr<AuthProvider> authProvider = m_settings->getAuthProvider();
if (authProvider->methodIsSupported(AuthProvider::AUTH_METHOD_OAUTH2)) {
m_session->forceAuthorization(authProvider);
}
Neon::Session::PropfindPropCallback_t callback =
boost::bind(&WebDAVSource::openPropCallback,
this, boost::ref(davProps), _1, _2, _3, _4);
m_session->propfindProp(candidate.m_uri.m_path, 0, NULL, callback, Timespec());
} catch (const Neon::FatalException &ex) {
throw;
} catch (...) {
handleException(HANDLE_EXCEPTION_NO_ERROR);
}
}
// Now ask for some specific properties of interest for us.
// Using CALDAV:allprop would be nice, but doesn't seem to
// be possible with Neon.
//
// The "current-user-principal" is particularly relevant,
// because it leads us from
// "/.well-known/[carddav/caldav]" (or whatever that
// redirected to) to the current user and its
// "[calendar/addressbook]-home-set".
//
// Apple Calendar Server only returns that information if
// we force authorization to be used. Otherwise it returns
// <current-user-principal>
// <unauthenticated/>
// </current-user-principal>
//
// We send valid credentials here, using Basic authorization,
// if configured to use credentials instead of something like OAuth2.
// The rationale is that this cuts down on the number of
// requests for https while still being secure. For
// http, our Neon wrapper is smart enough to ignore our request.
//
// See also:
// http://tools.ietf.org/html/rfc4918#appendix-E
// http://lists.w3.org/Archives/Public/w3c-dist-auth/2005OctDec/0243.html
// http://thread.gmane.org/gmane.comp.web.webdav.neon.general/717/focus=719
m_session->forceAuthorization(m_settings->getAuthProvider());
davProps.clear();
// Avoid asking for CardDAV properties when only using CalDAV
// and vice versa, to avoid breaking both when the server is only
// broken for one of them (like Google, which (temporarily?) sent
// invalid CardDAV properties).
static const ne_propname caldav[] = {
// WebDAV ACL
{ "DAV:", "alternate-URI-set" },
{ "DAV:", "principal-URL" },
{ "DAV:", "current-user-principal" },
{ "DAV:", "group-member-set" },
{ "DAV:", "group-membership" },
{ "DAV:", "displayname" },
{ "DAV:", "resourcetype" },
// CalDAV
{ "urn:ietf:params:xml:ns:caldav", "calendar-home-set" },
{ "urn:ietf:params:xml:ns:caldav", "calendar-description" },
{ "urn:ietf:params:xml:ns:caldav", "calendar-timezone" },
{ "urn:ietf:params:xml:ns:caldav", "supported-calendar-component-set" },
{ "urn:ietf:params:xml:ns:caldav", "supported-calendar-data" },
{ "urn:ietf:params:xml:ns:caldav", "max-resource-size" },
{ "urn:ietf:params:xml:ns:caldav", "min-date-time" },
{ "urn:ietf:params:xml:ns:caldav", "max-date-time" },
{ "urn:ietf:params:xml:ns:caldav", "max-instances" },
{ "urn:ietf:params:xml:ns:caldav", "max-attendees-per-instance" },
// ACL, http://www.ietf.org/rfc/rfc3744.txt
{ "DAV:", "current-user-privilege-set" },
{ NULL, NULL }
};
static const ne_propname carddav[] = {
// WebDAV ACL
{ "DAV:", "alternate-URI-set" },
{ "DAV:", "principal-URL" },
{ "DAV:", "current-user-principal" },
{ "DAV:", "group-member-set" },
{ "DAV:", "group-membership" },
{ "DAV:", "displayname" },
{ "DAV:", "resourcetype" },
// CardDAV
{ "urn:ietf:params:xml:ns:carddav", "addressbook-home-set" },
{ "urn:ietf:params:xml:ns:carddav", "principal-address" },
{ "urn:ietf:params:xml:ns:carddav", "addressbook-description" },
{ "urn:ietf:params:xml:ns:carddav", "supported-address-data" },
{ "urn:ietf:params:xml:ns:carddav", "max-resource-size" },
// ACL, http://www.ietf.org/rfc/rfc3744.txt
{ "DAV:", "current-user-privilege-set" },
{ NULL, NULL }
};
SE_LOG_DEBUG(NULL, "read relevant properties of %s", candidate.m_uri.toURL().c_str());
m_session->propfindProp(candidate.m_uri.m_path, 0,
getContent() == "VCARD" ? carddav : caldav,
callback, deadline);
success = true;
} catch (const Neon::FatalException &ex) {
throw;
} catch (const Neon::RedirectException &ex) {
// follow to new location
Neon::URI next = Neon::URI::parse(ex.getLocation(), true);
// keep old host + scheme + port if not set in next location
if (next.m_scheme.empty()) {
next.m_scheme = currentURI.m_scheme;
}
if (next.m_host.empty()) {
next.m_host = currentURI.m_host;
}
if (!next.m_port) {
next.m_port = currentURI.m_port;
}
Candidate nextCandidate(next, candidate.m_flags);
if (tried.isNew(nextCandidate)) {
SE_LOG_DEBUG(NULL, "new candidate from %s -> %s redirect",
currentURI.toURL().c_str(),
next.toURL().c_str());
tried.addCandidate(nextCandidate, Tried::FRONT);
} else {
SE_LOG_DEBUG(NULL, "already known candidate from %s -> %s redirect",
currentURI.toURL().c_str(),
next.toURL().c_str());
}
} catch (const TransportStatusException &ex) {
SE_LOG_DEBUG(NULL, "TransportStatusException: %s", ex.what());
if (ex.syncMLStatus() == 404 && boost::find_first(candidate.m_uri.m_path, username) && usernameInserted) {
// We're actually looking at an authentication error: the path to the calendar has
// not been found, so the username was wrong. Let's hijack the error message and
// code of the exception by throwing a new one.
string descr = StringPrintf("Path not found: %s. Is the username '%s' correct?",
candidate.m_uri.toURL().c_str(), username.c_str());
int code = 401;
SE_THROW_EXCEPTION_STATUS(TransportStatusException, descr, SyncMLStatus(code));
} else if (isWellKnown && !didDNS) {
// The server doesn't have the .well-known redirect that we were looking for.
// We might be able to find the right server via DNS SRV lookup instead.
// Happens with [www].icloud.com, for which DNS SRV points to
// https://[caldav|carddav].icloud.com:443/.well-known/[caldav|carddav]
std::string domain = m_session->getURI().m_host;
std::string wwwdomain;
static const char WWW[] = "www.";
if (boost::starts_with(domain, WWW)) {
wwwdomain = domain;
domain.erase(0, sizeof(WWW) - 1);
}
std::string url;
didDNS = true;
try {
SE_LOG_DEBUG(getDisplayName(), "try DNS SRV lookup after .well-known failed: %s", domain.c_str());
url = lookupDNSSRV(domain);
} catch (const Exception &ex) {
if (!wwwdomain.empty()) {
SE_LOG_DEBUG(getDisplayName(), "try DNS SRV lookup with www prefix: %s", wwwdomain.c_str());
url = lookupDNSSRV(wwwdomain);
} else if (tried.errorIsFatal()) {
throw;
} else {
SE_LOG_DEBUG(NULL, "ignore error for DNS SRV fallback: %s", ex.what());
}
}
if (!url.empty()) {
Neon::URI uri = Neon::URI::parse(url);
Candidate dnsCandidate(uri, Candidate::NONE);
if (tried.isNew(dnsCandidate)) {
tried.addCandidate(dnsCandidate, Tried::FRONT);
SE_LOG_DEBUG(getDisplayName(), "new candidate from DNS SRV lookup: %s", uri.toURL().c_str());
}
}
} else {
if (tried.errorIsFatal()) {
throw;
} else {
// ignore the error (whatever it was!), try next
// candidate; needed to handle 502 "Connection
// refused" for /.well-known/caldav/ from Yahoo!
// Calendar
SE_LOG_DEBUG(NULL, "ignore error for URI candidate: %s", ex.what());
}
}
} catch (const Exception &ex) {
if (tried.errorIsFatal()) {
throw;
} else {
// ignore the error (whatever it was!), try next
// candidate; needed to handle 502 "Connection
// refused" for /.well-known/caldav/ from Yahoo!
// Calendar
SE_LOG_DEBUG(NULL, "ignore error for URI candidate: %s", ex.what());
}
}
if (success) {
Props_t::iterator pathProps = davProps.find(candidate.m_uri.m_path);
if (pathProps == davProps.end()) {
// No reply for requested path? Happens with Yahoo Calendar server,
// which returns information about "/dav" when asked about "/".
// Move to that path.
if (!davProps.empty()) {
pathProps = davProps.begin();
string newpath = pathProps->first;
SE_LOG_DEBUG(NULL, "use properties for '%s' instead of '%s'",
newpath.c_str(), candidate.m_uri.toURL().c_str());
candidate.m_uri.m_path = newpath;
}
}
StringMap *props = pathProps == davProps.end() ? NULL : &pathProps->second;
bool isResult = false;
std::string type;
if (props) {
type = (*props)["DAV::resourcetype"];
}
bool isCollection = type.find("<DAV:collection></DAV:collection>") != type.npos;
if (isCollection && props && isLeafCollection(*props) && typeMatches(*props)) {
isResult = true;
StringMap::const_iterator it;
// TODO: filter out CalDAV collections which do
// not contain the right components
// (urn:ietf:params:xml:ns:caldav:supported-calendar-component-set)
// found something
tried.foundResult();
it = props->find("DAV::displayname");
Neon::URI uri = m_session->getURI();
uri.m_path = candidate.m_uri.m_path;
std::string name;
if (it != props->end()) {
name = it->second;
}
// Might be read-only. Assume it is read/write unless we
// find the opposite.
bool isReadOnly = false;
it = props->find("DAV::current-user-privilege-set");
if (it != props->end()) {
const std::string &priviliges = it->second;
SE_LOG_DEBUG(NULL, "current-user-privilege-set: %s", priviliges.c_str());
// Be careful here: parsing XML with string operations is fragile,
// so don't go to read-only mode if we don't find DAV::read.
// Also beware of the double vs. single colon oddity from libneon.
if ((priviliges.find("DAV::write") == priviliges.npos &&
priviliges.find("DAV::read") != priviliges.npos) ||
(priviliges.find("DAV:write") == priviliges.npos &&
priviliges.find("DAV:read") != priviliges.npos)) {
isReadOnly = true;
}
} else {
SE_LOG_DEBUG(NULL, "no current-user-privilege-set, assume read/write");
}
SE_LOG_DEBUG(NULL, "found %s = %s",
name.c_str(),
uri.toURL().c_str());
if (uri.m_host == "apidata.googleusercontent.com" &&
boost::starts_with(uri.m_path, "/caldav/v2/")) {
haveGoogleCalDAV2 = true;
}
res = storeResult(name,
uri,
isReadOnly);
if (!res) {
// done
break;
}
}
// find next path:
// prefer CardDAV:calendar-home-set or CalDAV:addressbook-home-set
std::list<std::string> homes;
if (props) {
homes = extractHREFs((*props)[homeSetProp()]);
}
BOOST_FOREACH(const std::string &home, homes) {
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
// The home set is a collection of collections, so it
// cannot be the collection we look for. But it contains them,
// so we must list its content.
Candidate homeCandidate(m_session->getURI(), home, Candidate::LIST);
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
if (tried.isNew(homeCandidate)) {
haveHomeSet = true;
if (next.empty()) {
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
// Follow it directly before any other
// candidates because the home set is most
// likely to contain the default collection.
SE_LOG_DEBUG(NULL, "follow home-set property to %s", homeCandidate.m_uri.toURL().c_str());
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
next = homeCandidate;
} else {
SE_LOG_DEBUG(NULL, "new candidate from home-set property %s", home.c_str());
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
tried.addCandidate(homeCandidate, Tried::FRONT);
}
}
}
// alternatively, follow principal URL
if (next.empty()) {
Candidate principal(m_session->getURI(),
props ? extractHREF((*props)["DAV::current-user-principal"]) : "",
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
Candidate::NONE);
// TODO:
// xmlns:d="DAV:"
// <d:current-user-principal><d:href>/m8/carddav/principals/__uids__/patrick.ohly@googlemail.com/</d:href></d:current-user-principal>
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
if (tried.isNew(principal)) {
next = principal;
SE_LOG_DEBUG(NULL, "follow current-user-prinicipal to %s", next.m_uri.toURL().c_str());
}
}
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
if (isResult && next.empty() && !haveHomeSet) {
// We found a valid collection without having seen the
// home set, and the meta data of the collection does
// not point us to the principal or the home set.
//
// Happens with Google CaldDAV, causing us to not find
// other calendars if scan started at the default
// calendar. As a workaround, walk up the uri and check
// them for meta data.
std::string path = candidate.m_uri.m_path;
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
size_t pos;
while ((pos = path.rfind('/')) != path.npos) {
path.resize(pos);
Candidate parent(m_session->getURI(), path.empty() ? "/" : path, Candidate::NONE);
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
if (tried.isNew(parent)) {
SE_LOG_DEBUG(NULL, "check parent %s", parent.m_uri.toURL().c_str());
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
tried.addCandidate(parent, Tried::BACK);
}
}
}
// Finally, recursively descend into some collections.
if (isCollection) {
if (props && isLeafCollection(*props)) {
// The goal here was to prevent diving into collections which are
// known to not contain other relevant collections.
SE_LOG_DEBUG(NULL, "skipping listing because collection cannot contain other relevant collections: %s", candidate.m_uri.toURL().c_str());
} else if (!(candidate.m_flags & Candidate::LIST)) {
SE_LOG_DEBUG(NULL, "skipping listing because we don't know whether collection contains relevant collections: %s", candidate.m_uri.toURL().c_str());
} else {
// List members and find new candidates.
// Yahoo! Calendar does not return resources contained in /dav/<user>/Calendar/
// if <allprops> is used. Properties must be requested explicitly.
SE_LOG_DEBUG(NULL, "list items in %s", candidate.m_uri.toURL().c_str());
// See findCollections() for the reason why we are not mixing CalDAV and CardDAV
// properties.
static const ne_propname caldav[] = {
{ "DAV:", "displayname" },
{ "DAV:", "resourcetype" },
{ "urn:ietf:params:xml:ns:caldav", "calendar-home-set" },
{ "urn:ietf:params:xml:ns:caldav", "calendar-description" },
{ "urn:ietf:params:xml:ns:caldav", "calendar-timezone" },
{ "urn:ietf:params:xml:ns:caldav", "supported-calendar-component-set" },
{ NULL, NULL }
};
static const ne_propname carddav[] = {
{ "DAV:", "displayname" },
{ "DAV:", "resourcetype" },
{ "urn:ietf:params:xml:ns:carddav", "addressbook-home-set" },
{ "urn:ietf:params:xml:ns:carddav", "addressbook-description" },
{ "urn:ietf:params:xml:ns:carddav", "supported-address-data" },
{ NULL, NULL }
};
davProps.clear();
m_session->propfindProp(candidate.m_uri.m_path, 1,
getContent() == "VCARD" ? carddav : caldav,
callback, finalDeadline);
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
// Also list recursively. The home set may be an
// "ordinary collection that has child or
// descendant calendar collections owned by the
// principal" (RFC 4791).
int subFlags = Candidate::LIST;
BOOST_FOREACH(Props_t::value_type &entry, davProps) {
const std::string &sub = entry.first;
const std::string &subType = entry.second["DAV::resourcetype"];
Candidate subCandidate(m_session->getURI(), sub, subFlags);
// new candidates are:
// - untested
// - not already a candidate
// - a resource, but not the CalDAV schedule-inbox/outbox
// - not shared ("global-addressbook" in Apple Calendar Server),
// because these are unlikely to be the right "default" collection
//
// Trying to prune away collections here which are not of the
// right type *and* cannot contain collections of the right
// type (example: Apple Calendar Server "inbox" under
// calendar-home-set URL with type "CALDAV:schedule-inbox") requires
// knowledge not current provided by derived classes. TODO (?).
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
if (!tried.isNew(subCandidate)) {
SE_LOG_DEBUG(NULL, "skipping because already checked: %s", sub.c_str());
} else if (subType.find("<DAV:collection></DAV:collection>") == subType.npos ||
subType.find("<urn:ietf:params:xml:ns:caldavschedule-") != subType.npos) {
SE_LOG_DEBUG(NULL, "skipping because of wrong resourcetype: %s\n%s",
sub.c_str(),
subType.c_str());
#if 0
// Do not ignore shared collections. We might have read-write
// access (for example, Google marks additional calendars as
// 'shared').
} else if (subType.find("<http://calendarserver.org/ns/shared") != subType.npos) {
SE_LOG_DEBUG(NULL, "skipping because it is shared: %s", sub.c_str());
#endif
} else if (!typeMatches(entry.second)) {
SE_LOG_DEBUG(NULL, "skipping because of wrong type: %s", sub.c_str());
} else {
Candidate subCandidate(m_session->getURI(), sub, subFlags);
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
if (tried.isNew(subCandidate)) {
tried.addCandidate(subCandidate, Tried::BACK);
SE_LOG_DEBUG(NULL, "new sub candidate: %s", sub.c_str());
}
WebDAV: added CardDAV support This patch moves some of the CalDAV-specific code out of WebDAVSource into CalDAVSource and adds the corresponding CardDAV code in the new CardDAVSource. It improves searching for the right collection. A Yahoo Contacts specific problem is that vCard UID and resource name have to match, with ".vcf" used as suffix in the resource name. The new createResourceName() and setResourceName() virtual methods ensure that. They are nops in CalDAV and use simple string manipulation for vCard. Another Yahoo Contacts odity is merging of contacts in PUT, without telling the client. This can be detected by checking the expected resource name with PROPFIND: the server then replies with the real resource name. This check is done only if no href is returned in the response to PUT, so it should only be triggered for Yahoo Contacts. CardDAV support is still experimental. It was tested with Yahoo Contacts, using https://carddav.address.yahoo.com/dav/%u as sync URL. This is different from the CalDAV URL, which currently prevents using the same @yahoo context for both calendar and contacts. Working on CardDAV showed that resource name comparisons had issues due to different encoding of special characters. Added canonicalization of paths to fix this problem. This also includes appending the trailing slash for collections (Neon::URI::normalizePath()). Note that Buteo only serializes sync sessions based on their type, so as soon as we allow CardDAV in addition to CalDAV, it might happen that SyncEvolution is invokved multiple times in parallel => need to remove global variables first.
2010-11-25 10:08:07 +01:00
}
}
}
}
}
next:
if (next.empty()) {
// use next untried candidate
next = tried.getNextCandidate();
if (next.empty()) {
// done searching
break;
}
SE_LOG_DEBUG(NULL, "follow candidate %s", next.m_uri.toURL().c_str());
}
counter++;
if (counter > limit) {
throwError(SE_HERE, StringPrintf("giving up search for collection after %d attempts", limit));
}
WebDAV: improved database search (Google, Zimbra) Zimbra has a principal URL that also serves as home set. When using it as start URL, SyncEvolution only looked the URL once, without listing its content, and thus did not find the databases. When following the Zimbra principal URL indirectly, SyncEvolution did check all of the collections there recursively. Unfortunately that also includes many mail folders, causing the scan to abort after checking 1000 collections (an internal safe guard). The solution for both includes tracking what to do with a URL. For the initial URL, only meta data about the URL itself gets checked. Recursive scanning is only done for the home set. If that home set contains many collections, scanning is still slow and may run into the internal safe guard limit. This cannot be avoided because the CalDAV spec explicitly states that the home set may contain normal collections which contain other collections, so a client has to do the recursive scan. When looking at a specific calendar, Google CalDAV does not report what the current principal or the home set is and therefore SyncEvolution stopped after finding just the initial calendar. Now it detects the lack of meta information and adds all parents also as candidates that need to be looked at. The downside of this is that it doesn't know anything about which parents are relevant, so it ends up checking https://www.google.com/calendar/ and https://www.google.com/. In both cases Basic Auth gets rejected with a temporary redirect to the Google login page, which is something that SyncEvolution must ignore immediately during scanning without applying the resend workaround for "temporary rejection of valid credentials" that can happen for valid Google CalDAV URLs. The sorting of sub collections is redundant and can be removed, because sorting already happens when storing the information for each path. When scanning Google CalDAV starting at https://www.google.com/calendar/dav/, the main calendar gets listed by Google first, but because we reorder by sorting by path, it ends up last in the SyncEvolution database list and thus is not picked as default. This needs to be fixed separately by preserving the server's order.
2014-04-24 21:12:38 +02:00
candidate = next;
}
return res;
}
std::string WebDAVSource::extractHREF(const std::string &propval)
{
// all additional parameters after opening resp. closing tag
static const std::string hrefStart = "<DAV:href";
static const std::string hrefEnd = "</DAV:href";
size_t start = propval.find(hrefStart);
start = propval.find('>', start);
if (start != propval.npos) {
start++;
size_t end = propval.find(hrefEnd, start);
if (end != propval.npos) {
return propval.substr(start, end - start);
}
}
return "";
}
std::list<std::string> WebDAVSource::extractHREFs(const std::string &propval)
{
std::list<std::string> res;
// all additional parameters after opening resp. closing tag
static const std::string hrefStart = "<DAV:href";
static const std::string hrefEnd = "</DAV:href";
size_t current = 0;
while (current < propval.size()) {
size_t start = propval.find(hrefStart, current);
start = propval.find('>', start);
if (start != propval.npos) {
start++;
size_t end = propval.find(hrefEnd, start);
if (end != propval.npos) {
res.push_back(propval.substr(start, end - start));
current = end;
} else {
break;
}
} else {
break;
}
}
return res;
}
void WebDAVSource::openPropCallback(Props_t &davProps,
const Neon::URI &uri,
const ne_propname *prop,
const char *value,
const ne_status *status)
{
// TODO: recognize CALDAV:calendar-timezone and use it for local time conversion of events
std::string name;
if (prop->nspace) {
name = prop->nspace;
}
name += ":";
name += prop->name;
if (value) {
davProps[uri.m_path][name] = value;
boost::trim_if(davProps[uri.m_path][name],
boost::is_space());
}
}
bool WebDAVSource::isEmpty()
{
contactServer();
// listing all items is relatively efficient, let's use that
WebDAV: added CardDAV support This patch moves some of the CalDAV-specific code out of WebDAVSource into CalDAVSource and adds the corresponding CardDAV code in the new CardDAVSource. It improves searching for the right collection. A Yahoo Contacts specific problem is that vCard UID and resource name have to match, with ".vcf" used as suffix in the resource name. The new createResourceName() and setResourceName() virtual methods ensure that. They are nops in CalDAV and use simple string manipulation for vCard. Another Yahoo Contacts odity is merging of contacts in PUT, without telling the client. This can be detected by checking the expected resource name with PROPFIND: the server then replies with the real resource name. This check is done only if no href is returned in the response to PUT, so it should only be triggered for Yahoo Contacts. CardDAV support is still experimental. It was tested with Yahoo Contacts, using https://carddav.address.yahoo.com/dav/%u as sync URL. This is different from the CalDAV URL, which currently prevents using the same @yahoo context for both calendar and contacts. Working on CardDAV showed that resource name comparisons had issues due to different encoding of special characters. Added canonicalization of paths to fix this problem. This also includes appending the trailing slash for collections (Neon::URI::normalizePath()). Note that Buteo only serializes sync sessions based on their type, so as soon as we allow CardDAV in addition to CalDAV, it might happen that SyncEvolution is invokved multiple times in parallel => need to remove global variables first.
2010-11-25 10:08:07 +01:00
// TODO: use truncated result search
RevisionMap_t revisions;
listAllItems(revisions);
return revisions.empty();
}
void WebDAVSource::close()
{
m_session.reset();
}
static bool storeCollection(SyncSource::Databases &result,
const std::string &name,
const Neon::URI &uri,
bool isReadOnly)
{
std::string url = uri.toURL();
// avoid duplicates
BOOST_FOREACH(const SyncSource::Database &entry, result) {
if (entry.m_uri == url) {
// already found before
return true;
}
}
result.push_back(SyncSource::Database(name, url, false, isReadOnly));
return true;
}
WebDAVSource::Databases WebDAVSource::getDatabases()
{
Databases result;
// do a scan if some kind of credentials were set
if (m_contextSettings->getAuthProvider()->wasConfigured()) {
findCollections(boost::bind(storeCollection,
boost::ref(result),
_1, _2, _3));
// Move all read-only collections to the end of the array.
// They are probably not the default calendar (for example,
// with ownCloud we find a read-only "Birthday Calendar"
// before the "Default Calendar").
//
// WebDAVSource::contactServer() does the same.
size_t e = result.size(), i = 0;
while (i < e) {
if (result[i].m_isReadOnly) {
// Move to end.
result.push_back(result[i]);
// Remove at current position.
result.erase(result.begin() + i);
// Check that position again and ignore the already
// checked entry at the end.
e--;
} else {
// Next position.
i++;
}
}
if (!result.empty()) {
result.front().m_isDefault = true;
}
} else {
result.push_back(Database("select database via absolute URL, set username/password to scan, set syncURL to base URL if server does not support auto-discovery",
"<path>"));
}
return result;
}
void WebDAVSource::getSynthesisInfo(SynthesisInfo &info,
XMLConfigFragments &fragments)
{
contactServer();
TrackingSyncSource::getSynthesisInfo(info, fragments);
// only CalDAV enforces unique UID
std::string content = getContent();
if (content == "VEVENT" || content == "VTODO" || content == "VJOURNAL") {
info.m_globalIDs = true;
}
if (content == "VEVENT") {
info.m_backendRule = "HAVE-SYNCEVOLUTION-EXDATE-DETACHED";
} else if (content == "VCARD") {
// Assume that a CardDAV server has and preserves UID values.
CardDAV: use Apple/Google/CardDAV vCard flavor In principle, CardDAV servers support arbitrary vCard 3.0 data. Extensions can be different and need to be preserved. However, when multiple different clients or the server's Web UI interpret the vCards, they need to agree on the semantic of these vCard extensions. In practice, CardDAV was pushed by Apple and Apple clients are probably the most common clients of CardDAV services. When the Google Contacts Web UI creates or edits a contact, Google CardDAV will send that data using the vCard flavor used by Apple. Therefore it makes sense to exchange contacts with *all* CardDAV servers using that format. This format could be made configurable in SyncEvolution on a case-by-case basis; at the moment, it is hard-coded. During syncing, SyncEvolution takes care to translate between the vCard flavor used internally (based on Evolution) and the CardDAV vCard flavor. This mapping includes: X-AIM/JABBER/... <-> IMPP + X-SERVICE-TYPE Any IMPP property declared as X-SERVICE-TYPE=AIM will get mapped to X-AIM. Same for others. Some IMPP service types have no known X- property extension; they are stored in EDS as IMPP. X- property extensions without a known X-SERVICE-TYPE (for example, GaduGadu and Groupwise) are stored with X-SERVICE-TYPE values chosen by SyncEvolution so that Google CardDAV preserves them (GroupWise with mixed case got translated by Google into Groupwise, so the latter is used). Google always sends an X-ABLabel:Other for IMPP. This is ignored because the service type overrides it. The value itself also gets transformed during the mapping. IMPP uses an URI as value, with a chat protocol (like "aim" or "xmpp") and some protocol specific identifier. For each X- extension the protocol is determined by the property name and the value is the protocol specific identifier without URL encoding. X-SPOUSE/MANAGER/ASSISTANT <-> X-ABRELATEDNAMES + X-ABLabel The mapping is based on the X-ABLabel property attached to the X-ABRELATEDNAMES property. This depends on the English words "Spouse", "Manager", "Assistant" that Google CardDAV and Apple devices seem to use regardless of the configured language. As with IMPP, only the subset of related names which have a corresponding X- property extension get mapped. The rest is stored in EDS using the X-ABRELATEDNAMES property. X-ANNIVERSARY <-> X-ABDATE Same here, with X-ABLabel:Anniversary as the special case which gets mapped. X-ABLabel parameter <-> property CardDAV vCards have labels attached to arbitrary other properties (TEL, ADR, X-ABDATE, X-ABRELATEDNAMES, ...) via vCard group tags: item1.X-ABDATE:2010-01-01 item1.X-ABLabel:Anniversary The advantage is that property values can contain arbitrary characters, including line breaks and double quotation marks, which is not possible in property parameters. Neither EDS nor KDE (judging from the lack of responses on the KDE-PIM mailing list) support custom labels. SyncEvolution could have used grouping as it is done in CardDAV, but grouping is not used much (not at all?) by the UIs working with the vCards in EDS and KDE. It seemed easier to use a new X-ABLabel parameter. Characters which cannot be stored in a parameter get converted (double space to single space, line break to space, etc.) during syncing. In practice, these characters don't appear in X-ABLabel properties anyway because neither Apple nor Google UIs allow entering them for custom labels. The "Other" label is used by Google even in case where it adds no information. For example, all XMPP properties have an associated X-ABLabel=Other although the Web UI does not provide a means to edit or show such a label. Editing the text before the value in the UI changes the X-SERVICE-TYPE parameter value, not the X-ABLabel as for other fields. Therefore the "Other" label is ignored by removing it during syncing. X-EVOLUTION-UI-SLOT (the parameter used in Evolution to determine the order of properties in the UI) gets stored in CardDAV. The only exception is Google CardDAV which got confused when an IMPP property had both X-SERVICE-TYPE and X-EVOLUTION-UI-SLOT parameters set. For Google, X-EVOLUTION-UI-SLOT is only sent on other properties and thus ordering of chat information can get lost when syncing with Google. CardDAV needs to use test data with the new CardDAV vCard flavor. Most CardDAV servers can store EDS vCards and thus Client::Source::*::testImport passed (with minor tweaks in synccompare) when using the default eds_contact.vcf, but Client::Sync::*::testItems fails when comparing synced data with test cases when the synced data uses the native format and the test cases are still the ones from EDS. A libsynthesis with URLENCODE/DECODE() and sharedfield parameter for <property> is needed.
2014-05-16 11:25:00 +02:00
info.m_backendRule = "CARDDAV";
fragments.m_remoterules["CARDDAV"] =
" <remoterule name='CARDDAV'>\n"
" <deviceid>none</deviceid>\n"
" <noemptyproperties>yes</noemptyproperties>\n"
" <include rule='HAVE-EVOLUTION-UI-SLOT'/>\n"
" <include rule='HAVE-EVOLUTION-UI-SLOT-IN-IMPP'/>\n"
" <include rule='HAVE-VCARD-UID'/>\n"
" <include rule='HAVE-ABLABEL-PROPERTY'/>\n"
" </remoterule>";
// Assume that a CardDAV server uses IMPP (RFC 4770) and
// Apple Address book (X-AB) extensions. Convert to the traditional,
// internal fields (ANNIVERSARY, JABBER, etc.) after reading
// from a CardDAV server and from the traditional fields
// before writing.
info.m_beforeWriteScript = "$VCARD_BEFOREWRITE_SCRIPT_WEBDAV;\n";
info.m_afterReadScript = "$VCARD_AFTERREAD_SCRIPT_WEBDAV;\n";
}
// TODO: instead of identifying the peer based on the
// session URI, use some information gathered about
// it during contactServer()
if (m_session) {
string host = m_session->getURI().m_host;
if (host.find("google") != host.npos) {
info.m_backendRule = "GOOGLE";
CardDAV: use Apple/Google/CardDAV vCard flavor In principle, CardDAV servers support arbitrary vCard 3.0 data. Extensions can be different and need to be preserved. However, when multiple different clients or the server's Web UI interpret the vCards, they need to agree on the semantic of these vCard extensions. In practice, CardDAV was pushed by Apple and Apple clients are probably the most common clients of CardDAV services. When the Google Contacts Web UI creates or edits a contact, Google CardDAV will send that data using the vCard flavor used by Apple. Therefore it makes sense to exchange contacts with *all* CardDAV servers using that format. This format could be made configurable in SyncEvolution on a case-by-case basis; at the moment, it is hard-coded. During syncing, SyncEvolution takes care to translate between the vCard flavor used internally (based on Evolution) and the CardDAV vCard flavor. This mapping includes: X-AIM/JABBER/... <-> IMPP + X-SERVICE-TYPE Any IMPP property declared as X-SERVICE-TYPE=AIM will get mapped to X-AIM. Same for others. Some IMPP service types have no known X- property extension; they are stored in EDS as IMPP. X- property extensions without a known X-SERVICE-TYPE (for example, GaduGadu and Groupwise) are stored with X-SERVICE-TYPE values chosen by SyncEvolution so that Google CardDAV preserves them (GroupWise with mixed case got translated by Google into Groupwise, so the latter is used). Google always sends an X-ABLabel:Other for IMPP. This is ignored because the service type overrides it. The value itself also gets transformed during the mapping. IMPP uses an URI as value, with a chat protocol (like "aim" or "xmpp") and some protocol specific identifier. For each X- extension the protocol is determined by the property name and the value is the protocol specific identifier without URL encoding. X-SPOUSE/MANAGER/ASSISTANT <-> X-ABRELATEDNAMES + X-ABLabel The mapping is based on the X-ABLabel property attached to the X-ABRELATEDNAMES property. This depends on the English words "Spouse", "Manager", "Assistant" that Google CardDAV and Apple devices seem to use regardless of the configured language. As with IMPP, only the subset of related names which have a corresponding X- property extension get mapped. The rest is stored in EDS using the X-ABRELATEDNAMES property. X-ANNIVERSARY <-> X-ABDATE Same here, with X-ABLabel:Anniversary as the special case which gets mapped. X-ABLabel parameter <-> property CardDAV vCards have labels attached to arbitrary other properties (TEL, ADR, X-ABDATE, X-ABRELATEDNAMES, ...) via vCard group tags: item1.X-ABDATE:2010-01-01 item1.X-ABLabel:Anniversary The advantage is that property values can contain arbitrary characters, including line breaks and double quotation marks, which is not possible in property parameters. Neither EDS nor KDE (judging from the lack of responses on the KDE-PIM mailing list) support custom labels. SyncEvolution could have used grouping as it is done in CardDAV, but grouping is not used much (not at all?) by the UIs working with the vCards in EDS and KDE. It seemed easier to use a new X-ABLabel parameter. Characters which cannot be stored in a parameter get converted (double space to single space, line break to space, etc.) during syncing. In practice, these characters don't appear in X-ABLabel properties anyway because neither Apple nor Google UIs allow entering them for custom labels. The "Other" label is used by Google even in case where it adds no information. For example, all XMPP properties have an associated X-ABLabel=Other although the Web UI does not provide a means to edit or show such a label. Editing the text before the value in the UI changes the X-SERVICE-TYPE parameter value, not the X-ABLabel as for other fields. Therefore the "Other" label is ignored by removing it during syncing. X-EVOLUTION-UI-SLOT (the parameter used in Evolution to determine the order of properties in the UI) gets stored in CardDAV. The only exception is Google CardDAV which got confused when an IMPP property had both X-SERVICE-TYPE and X-EVOLUTION-UI-SLOT parameters set. For Google, X-EVOLUTION-UI-SLOT is only sent on other properties and thus ordering of chat information can get lost when syncing with Google. CardDAV needs to use test data with the new CardDAV vCard flavor. Most CardDAV servers can store EDS vCards and thus Client::Source::*::testImport passed (with minor tweaks in synccompare) when using the default eds_contact.vcf, but Client::Sync::*::testItems fails when comparing synced data with test cases when the synced data uses the native format and the test cases are still the ones from EDS. A libsynthesis with URLENCODE/DECODE() and sharedfield parameter for <property> is needed.
2014-05-16 11:25:00 +02:00
// Same as CARDDAV above, minus HAVE-EVOLUTION-UI-SLOT-IN-IMPP.
// Sending IMPP;X-SERVICE-TYPE=..;X-EVOLUTION-UI-SLOT=
// causes Google to ignore X-SERVICE-TYPE.
fragments.m_remoterules["GOOGLE"] =
" <remoterule name='GOOGLE'>\n"
" <deviceid>none</deviceid>\n"
CardDAV: use Apple/Google/CardDAV vCard flavor In principle, CardDAV servers support arbitrary vCard 3.0 data. Extensions can be different and need to be preserved. However, when multiple different clients or the server's Web UI interpret the vCards, they need to agree on the semantic of these vCard extensions. In practice, CardDAV was pushed by Apple and Apple clients are probably the most common clients of CardDAV services. When the Google Contacts Web UI creates or edits a contact, Google CardDAV will send that data using the vCard flavor used by Apple. Therefore it makes sense to exchange contacts with *all* CardDAV servers using that format. This format could be made configurable in SyncEvolution on a case-by-case basis; at the moment, it is hard-coded. During syncing, SyncEvolution takes care to translate between the vCard flavor used internally (based on Evolution) and the CardDAV vCard flavor. This mapping includes: X-AIM/JABBER/... <-> IMPP + X-SERVICE-TYPE Any IMPP property declared as X-SERVICE-TYPE=AIM will get mapped to X-AIM. Same for others. Some IMPP service types have no known X- property extension; they are stored in EDS as IMPP. X- property extensions without a known X-SERVICE-TYPE (for example, GaduGadu and Groupwise) are stored with X-SERVICE-TYPE values chosen by SyncEvolution so that Google CardDAV preserves them (GroupWise with mixed case got translated by Google into Groupwise, so the latter is used). Google always sends an X-ABLabel:Other for IMPP. This is ignored because the service type overrides it. The value itself also gets transformed during the mapping. IMPP uses an URI as value, with a chat protocol (like "aim" or "xmpp") and some protocol specific identifier. For each X- extension the protocol is determined by the property name and the value is the protocol specific identifier without URL encoding. X-SPOUSE/MANAGER/ASSISTANT <-> X-ABRELATEDNAMES + X-ABLabel The mapping is based on the X-ABLabel property attached to the X-ABRELATEDNAMES property. This depends on the English words "Spouse", "Manager", "Assistant" that Google CardDAV and Apple devices seem to use regardless of the configured language. As with IMPP, only the subset of related names which have a corresponding X- property extension get mapped. The rest is stored in EDS using the X-ABRELATEDNAMES property. X-ANNIVERSARY <-> X-ABDATE Same here, with X-ABLabel:Anniversary as the special case which gets mapped. X-ABLabel parameter <-> property CardDAV vCards have labels attached to arbitrary other properties (TEL, ADR, X-ABDATE, X-ABRELATEDNAMES, ...) via vCard group tags: item1.X-ABDATE:2010-01-01 item1.X-ABLabel:Anniversary The advantage is that property values can contain arbitrary characters, including line breaks and double quotation marks, which is not possible in property parameters. Neither EDS nor KDE (judging from the lack of responses on the KDE-PIM mailing list) support custom labels. SyncEvolution could have used grouping as it is done in CardDAV, but grouping is not used much (not at all?) by the UIs working with the vCards in EDS and KDE. It seemed easier to use a new X-ABLabel parameter. Characters which cannot be stored in a parameter get converted (double space to single space, line break to space, etc.) during syncing. In practice, these characters don't appear in X-ABLabel properties anyway because neither Apple nor Google UIs allow entering them for custom labels. The "Other" label is used by Google even in case where it adds no information. For example, all XMPP properties have an associated X-ABLabel=Other although the Web UI does not provide a means to edit or show such a label. Editing the text before the value in the UI changes the X-SERVICE-TYPE parameter value, not the X-ABLabel as for other fields. Therefore the "Other" label is ignored by removing it during syncing. X-EVOLUTION-UI-SLOT (the parameter used in Evolution to determine the order of properties in the UI) gets stored in CardDAV. The only exception is Google CardDAV which got confused when an IMPP property had both X-SERVICE-TYPE and X-EVOLUTION-UI-SLOT parameters set. For Google, X-EVOLUTION-UI-SLOT is only sent on other properties and thus ordering of chat information can get lost when syncing with Google. CardDAV needs to use test data with the new CardDAV vCard flavor. Most CardDAV servers can store EDS vCards and thus Client::Source::*::testImport passed (with minor tweaks in synccompare) when using the default eds_contact.vcf, but Client::Sync::*::testItems fails when comparing synced data with test cases when the synced data uses the native format and the test cases are still the ones from EDS. A libsynthesis with URLENCODE/DECODE() and sharedfield parameter for <property> is needed.
2014-05-16 11:25:00 +02:00
" <noemptyproperties>yes</noemptyproperties>\n"
" <include rule='HAVE-EVOLUTION-UI-SLOT'/>\n"
// " <include rule='HAVE-EVOLUTION-UI-SLOT-IN-IMPP'/>\n"
" <include rule='HAVE-VCARD-UID'/>\n"
" <include rule='HAVE-ABLABEL-PROPERTY'/>\n"
" </remoterule>";
} else if (host.find("yahoo") != host.npos) {
info.m_backendRule = "YAHOO";
fragments.m_remoterules["YAHOO"] =
" <remoterule name='YAHOO'>\n"
" <deviceid>none</deviceid>\n"
// Yahoo! Contacts reacts with a "500 - internal server error"
// to an empty X-GENDER property. In general, empty properties
// should never be necessary in CardDAV and CalDAV, because
// sent items conceptually replace the one on the server, so
// disable them all.
" <noemptyproperties>yes</noemptyproperties>\n"
// BDAY is ignored if it has the compact 19991231 instead of
// 1999-12-31, although both are valid.
" <include rule='EXTENDED-DATE-FORMAT'/>\n"
// Yahoo accepts extensions, so send them. However, it
// doesn't seem to store the X-EVOLUTION-UI-SLOT parameter
// extensions.
" <include rule=\"ALL\"/>\n"
CardDAV: use Apple/Google/CardDAV vCard flavor In principle, CardDAV servers support arbitrary vCard 3.0 data. Extensions can be different and need to be preserved. However, when multiple different clients or the server's Web UI interpret the vCards, they need to agree on the semantic of these vCard extensions. In practice, CardDAV was pushed by Apple and Apple clients are probably the most common clients of CardDAV services. When the Google Contacts Web UI creates or edits a contact, Google CardDAV will send that data using the vCard flavor used by Apple. Therefore it makes sense to exchange contacts with *all* CardDAV servers using that format. This format could be made configurable in SyncEvolution on a case-by-case basis; at the moment, it is hard-coded. During syncing, SyncEvolution takes care to translate between the vCard flavor used internally (based on Evolution) and the CardDAV vCard flavor. This mapping includes: X-AIM/JABBER/... <-> IMPP + X-SERVICE-TYPE Any IMPP property declared as X-SERVICE-TYPE=AIM will get mapped to X-AIM. Same for others. Some IMPP service types have no known X- property extension; they are stored in EDS as IMPP. X- property extensions without a known X-SERVICE-TYPE (for example, GaduGadu and Groupwise) are stored with X-SERVICE-TYPE values chosen by SyncEvolution so that Google CardDAV preserves them (GroupWise with mixed case got translated by Google into Groupwise, so the latter is used). Google always sends an X-ABLabel:Other for IMPP. This is ignored because the service type overrides it. The value itself also gets transformed during the mapping. IMPP uses an URI as value, with a chat protocol (like "aim" or "xmpp") and some protocol specific identifier. For each X- extension the protocol is determined by the property name and the value is the protocol specific identifier without URL encoding. X-SPOUSE/MANAGER/ASSISTANT <-> X-ABRELATEDNAMES + X-ABLabel The mapping is based on the X-ABLabel property attached to the X-ABRELATEDNAMES property. This depends on the English words "Spouse", "Manager", "Assistant" that Google CardDAV and Apple devices seem to use regardless of the configured language. As with IMPP, only the subset of related names which have a corresponding X- property extension get mapped. The rest is stored in EDS using the X-ABRELATEDNAMES property. X-ANNIVERSARY <-> X-ABDATE Same here, with X-ABLabel:Anniversary as the special case which gets mapped. X-ABLabel parameter <-> property CardDAV vCards have labels attached to arbitrary other properties (TEL, ADR, X-ABDATE, X-ABRELATEDNAMES, ...) via vCard group tags: item1.X-ABDATE:2010-01-01 item1.X-ABLabel:Anniversary The advantage is that property values can contain arbitrary characters, including line breaks and double quotation marks, which is not possible in property parameters. Neither EDS nor KDE (judging from the lack of responses on the KDE-PIM mailing list) support custom labels. SyncEvolution could have used grouping as it is done in CardDAV, but grouping is not used much (not at all?) by the UIs working with the vCards in EDS and KDE. It seemed easier to use a new X-ABLabel parameter. Characters which cannot be stored in a parameter get converted (double space to single space, line break to space, etc.) during syncing. In practice, these characters don't appear in X-ABLabel properties anyway because neither Apple nor Google UIs allow entering them for custom labels. The "Other" label is used by Google even in case where it adds no information. For example, all XMPP properties have an associated X-ABLabel=Other although the Web UI does not provide a means to edit or show such a label. Editing the text before the value in the UI changes the X-SERVICE-TYPE parameter value, not the X-ABLabel as for other fields. Therefore the "Other" label is ignored by removing it during syncing. X-EVOLUTION-UI-SLOT (the parameter used in Evolution to determine the order of properties in the UI) gets stored in CardDAV. The only exception is Google CardDAV which got confused when an IMPP property had both X-SERVICE-TYPE and X-EVOLUTION-UI-SLOT parameters set. For Google, X-EVOLUTION-UI-SLOT is only sent on other properties and thus ordering of chat information can get lost when syncing with Google. CardDAV needs to use test data with the new CardDAV vCard flavor. Most CardDAV servers can store EDS vCards and thus Client::Source::*::testImport passed (with minor tweaks in synccompare) when using the default eds_contact.vcf, but Client::Sync::*::testItems fails when comparing synced data with test cases when the synced data uses the native format and the test cases are still the ones from EDS. A libsynthesis with URLENCODE/DECODE() and sharedfield parameter for <property> is needed.
2014-05-16 11:25:00 +02:00
" <include rule=\"HAVE-VCARD-UID\"/>\n"
" <include rule=\"HAVE-ABLABEL-PROPERTY\"/>\n"
" </remoterule>";
}
}
SE_LOG_DEBUG(getDisplayName(), "using data conversion rules for '%s'", info.m_backendRule.c_str());
}
void WebDAVSource::storeServerInfos()
{
if (getDatabaseID().empty()) {
// user did not select resource, remember the one used for the
// next sync
setDatabaseID(m_calendar.toURL());
getProperties()->flush();
}
}
void WebDAVSource::checkPostSupport()
{
if (m_postPath.wasSet()) {
return;
}
static const ne_propname getaddmember[] = {
{ "DAV:", "add-member" },
{ NULL, NULL }
};
Timespec deadline = createDeadline();
Props_t davProps;
Neon::Session::PropfindPropCallback_t callback =
boost::bind(&WebDAVSource::openPropCallback,
this, boost::ref(davProps), _1, _2, _3, _4);
SE_LOG_DEBUG(NULL, "check POST support of %s", m_calendar.m_path.c_str());
m_session->propfindProp(m_calendar.m_path, 0, getaddmember, callback, deadline);
// Fatal communication problems will be reported via exceptions.
// Once we get here, invalid or incomplete results can be
// treated as "don't have revision string".
m_postPath = extractHREF(davProps[m_calendar.m_path]["DAV::add-member"]);
SE_LOG_DEBUG(NULL, "%s POST support: %s",
m_calendar.m_path.c_str(),
m_postPath.empty() ? "<none>" : m_postPath.get().c_str());
}
/**
* See https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-ctag.txt
*/
static const ne_propname getctag[] = {
{ "http://calendarserver.org/ns/", "getctag" },
{ NULL, NULL }
};
std::string WebDAVSource::databaseRevision()
{
if (m_contextSettings && m_contextSettings->noCTag()) {
// return empty string to disable usage of CTag
return "";
}
contactServer();
Timespec deadline = createDeadline();
Props_t davProps;
Neon::Session::PropfindPropCallback_t callback =
boost::bind(&WebDAVSource::openPropCallback,
this, boost::ref(davProps), _1, _2, _3, _4);
SE_LOG_DEBUG(NULL, "read ctag of %s", m_calendar.m_path.c_str());
m_session->propfindProp(m_calendar.m_path, 0, getctag, callback, deadline);
// Fatal communication problems will be reported via exceptions.
// Once we get here, invalid or incomplete results can be
// treated as "don't have revision string".
string ctag = davProps[m_calendar.m_path]["http://calendarserver.org/ns/:getctag"];
return ctag;
}
static const ne_propname getetag[] = {
{ "DAV:", "getetag" },
WebDAV: added CardDAV support This patch moves some of the CalDAV-specific code out of WebDAVSource into CalDAVSource and adds the corresponding CardDAV code in the new CardDAVSource. It improves searching for the right collection. A Yahoo Contacts specific problem is that vCard UID and resource name have to match, with ".vcf" used as suffix in the resource name. The new createResourceName() and setResourceName() virtual methods ensure that. They are nops in CalDAV and use simple string manipulation for vCard. Another Yahoo Contacts odity is merging of contacts in PUT, without telling the client. This can be detected by checking the expected resource name with PROPFIND: the server then replies with the real resource name. This check is done only if no href is returned in the response to PUT, so it should only be triggered for Yahoo Contacts. CardDAV support is still experimental. It was tested with Yahoo Contacts, using https://carddav.address.yahoo.com/dav/%u as sync URL. This is different from the CalDAV URL, which currently prevents using the same @yahoo context for both calendar and contacts. Working on CardDAV showed that resource name comparisons had issues due to different encoding of special characters. Added canonicalization of paths to fix this problem. This also includes appending the trailing slash for collections (Neon::URI::normalizePath()). Note that Buteo only serializes sync sessions based on their type, so as soon as we allow CardDAV in addition to CalDAV, it might happen that SyncEvolution is invokved multiple times in parallel => need to remove global variables first.
2010-11-25 10:08:07 +01:00
{ "DAV:", "resourcetype" },
{ NULL, NULL }
};
void WebDAVSource::listAllItems(RevisionMap_t &revisions)
{
contactServer();
if (!getContentMixed()) {
// Can use simple PROPFIND because we do not have to
// double-check that each item really contains the right data.
bool failed = false;
Timespec deadline = createDeadline();
m_session->propfindURI(m_calendar.m_path, 1, getetag,
boost::bind(&WebDAVSource::listAllItemsCallback,
this, _1, _2, boost::ref(revisions),
boost::ref(failed)),
deadline);
if (failed) {
SE_THROW("incomplete listing of all items");
}
} else {
// We have to read item data and verify that it really is
// something we have to (and may) work on. Currently only
// happens for CalDAV, CardDAV items are uniform. The CalDAV
// comp-filter alone should the trick, but some servers (for
// example Radicale 0.7) ignore it and thus we could end up
// deleting items we were not meant to touch.
const std::string query =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<C:calendar-query xmlns:D=\"DAV:\"\n"
"xmlns:C=\"urn:ietf:params:xml:ns:caldav\">\n"
"<D:prop>\n"
"<D:getetag/>\n"
"<C:calendar-data>\n"
"<C:comp name=\"VCALENDAR\">\n"
"<C:comp name=\"" + getContent() + "\">\n"
"<C:prop name=\"UID\"/>\n"
"</C:comp>\n"
"</C:comp>\n"
"</C:calendar-data>\n"
"</D:prop>\n"
// filter expected by Yahoo! Calendar
"<C:filter>\n"
"<C:comp-filter name=\"VCALENDAR\">\n"
"<C:comp-filter name=\"" + getContent() + "\">\n"
"</C:comp-filter>\n"
"</C:comp-filter>\n"
"</C:filter>\n"
"</C:calendar-query>\n";
Timespec deadline = createDeadline();
getSession()->startOperation("REPORT 'meta data'", deadline);
while (true) {
string data;
Neon::XMLParser parser;
parser.initReportParser(boost::bind(&WebDAVSource::checkItem, this,
boost::ref(revisions),
_1, _2, &data));
parser.pushHandler(boost::bind(Neon::XMLParser::accept, "urn:ietf:params:xml:ns:caldav", "calendar-data", _2, _3),
boost::bind(Neon::XMLParser::append, boost::ref(data), _2, _3));
Neon::Request report(*getSession(), "REPORT", getCalendar().m_path, query, parser);
report.addHeader("Depth", "1");
report.addHeader("Content-Type", "application/xml; charset=\"utf-8\"");
if (report.run()) {
break;
}
}
}
}
std::string WebDAVSource::findByUID(const std::string &uid,
const Timespec &deadline)
{
RevisionMap_t revisions;
std::string query;
if (getContent() == "VCARD") {
// use CardDAV
query =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<C:addressbook-query xmlns:D=\"DAV:\"\n"
"xmlns:C=\"urn:ietf:params:xml:ns:carddav:addressbook\">\n"
"<D:prop>\n"
"<D:getetag/>\n"
"</D:prop>\n"
"<C:filter>\n"
"<C:comp-filter name=\"" + getContent() + "\">\n"
"<C:prop-filter name=\"UID\">\n"
"<C:text-match>" + uid + "</C:text-match>\n"
"</C:prop-filter>\n"
"</C:comp-filter>\n"
"</C:filter>\n"
"</C:addressbook-query>\n";
} else {
// use CalDAV
query =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<C:calendar-query xmlns:D=\"DAV:\"\n"
"xmlns:C=\"urn:ietf:params:xml:ns:caldav\">\n"
"<D:prop>\n"
"<D:getetag/>\n"
"</D:prop>\n"
"<C:filter>\n"
"<C:comp-filter name=\"VCALENDAR\">\n"
"<C:comp-filter name=\"" + getContent() + "\">\n"
"<C:prop-filter name=\"UID\">\n"
// Collation from RFC 4791, not supported yet by all servers.
// Apple Calendar Server did not like CDATA.
// TODO: escape special characters.
"<C:text-match" /* collation=\"i;octet\" */ ">" /* <[CDATA[ */ + uid + /* ]]> */ "</C:text-match>\n"
"</C:prop-filter>\n"
"</C:comp-filter>\n"
"</C:comp-filter>\n"
"</C:filter>\n"
"</C:calendar-query>\n";
}
getSession()->startOperation("REPORT 'UID lookup'", deadline);
while (true) {
Neon::XMLParser parser;
parser.initReportParser(boost::bind(&WebDAVSource::checkItem, this,
boost::ref(revisions),
_1, _2, (std::string *)0));
Neon::Request report(*getSession(), "REPORT", getCalendar().m_path, query, parser);
report.addHeader("Depth", "1");
report.addHeader("Content-Type", "application/xml; charset=\"utf-8\"");
if (report.run()) {
break;
}
}
switch (revisions.size()) {
case 0:
SE_THROW_EXCEPTION_STATUS(TransportStatusException,
"object not found",
SyncMLStatus(404));
break;
case 1:
return revisions.begin()->first;
break;
default:
// should not happen
SE_THROW(StringPrintf("UID %s not unique?!", uid.c_str()));
}
// not reached
return "";
}
void WebDAVSource::listAllItemsCallback(const Neon::URI &uri,
const ne_prop_result_set *results,
RevisionMap_t &revisions,
bool &failed)
{
static const ne_propname prop = {
"DAV:", "getetag"
};
static const ne_propname resourcetype = {
"DAV:", "resourcetype"
};
const char *type = ne_propset_value(results, &resourcetype);
if (type && strstr(type, "<DAV:collection></DAV:collection>")) {
// skip collections
return;
}
std::string uid = path2luid(uri.m_path);
if (uid.empty()) {
// skip collection itself (should have been detected as collection already)
return;
}
const char *etag = ne_propset_value(results, &prop);
if (etag) {
std::string rev = ETag2Rev(etag);
SE_LOG_DEBUG(NULL, "item %s = rev %s",
uid.c_str(), rev.c_str());
revisions[uid] = rev;
} else {
failed = true;
SE_LOG_ERROR(NULL,
"%s: %s",
uri.toURL().c_str(),
Neon::Status2String(ne_propset_status(results, &prop)).c_str());
}
}
int WebDAVSource::checkItem(RevisionMap_t &revisions,
const std::string &href,
const std::string &etag,
std::string *data)
{
// Ignore responses with no data: this is not perfect (should better
// try to figure out why there is no data), but better than
// failing.
//
// One situation is the response for the collection itself,
// which comes with a 404 status and no data with Google Calendar.
if (data && data->empty()) {
return 0;
}
// No need to parse, user content cannot start at start of line in
// iCalendar 2.0.
if (!data ||
data->find("\nBEGIN:" + getContent()) != data->npos) {
std::string davLUID = path2luid(Neon::URI::parse(href).m_path);
std::string rev = ETag2Rev(etag);
revisions[davLUID] = rev;
}
// reset data for next item
if (data) {
data->clear();
}
return 0;
}
std::string WebDAVSource::path2luid(const std::string &path)
{
// m_calendar.m_path is normalized, path is not.
// Before comparing, normalize it.
std::string res = Neon::URI::normalizePath(path, false);
if (boost::starts_with(res, m_calendar.m_path)) {
res = Neon::URI::unescape(res.substr(m_calendar.m_path.size()));
} else {
// keep full, absolute path as LUID
}
return res;
}
std::string WebDAVSource::luid2path(const std::string &luid)
{
if (boost::starts_with(luid, "/")) {
return luid;
} else {
return m_calendar.resolve(Neon::URI::escape(luid)).m_path;
}
}
void WebDAVSource::readItem(const string &uid, std::string &item, bool raw)
{
Timespec deadline = createDeadline();
m_session->startOperation("GET", deadline);
while (true) {
item.clear();
Neon::Request req(*m_session, "GET", luid2path(uid),
"", item);
// useful with CardDAV: server might support more than vCard 3.0, but we don't
req.addHeader("Accept", contentType());
try {
if (req.run()) {
break;
}
} catch (const TransportStatusException &ex) {
if (ex.syncMLStatus() == 410) {
// Radicale reports 410 'Gone'. Hmm, okay.
// Let's map it to the expected 404.
SE_THROW_EXCEPTION_STATUS(TransportStatusException,
"object not found (was 410 'Gone')",
SyncMLStatus(404));
}
throw;
}
}
}
TrackingSyncSource::InsertItemResult WebDAVSource::insertItem(const string &uid, const std::string &item, bool raw)
{
std::string new_uid;
std::string rev;
InsertItemResultState state = ITEM_OKAY;
// By default use PUT. Change that to POST when creating new items
// and server supports it. That avoids the problem of having to
// choose a path and figuring out whether the server really used it.
static const char putOperation[] = "PUT";
static const char postOperation[] = "POST";
const char *operation = putOperation;
if (uid.empty()) {
checkPostSupport();
if (!m_postPath.empty()) {
operation = postOperation;
}
}
Timespec deadline = createDeadline(); // no resending if left empty
m_session->startOperation(operation, deadline);
std::string result;
int counter = 0;
retry:
counter++;
result = "";
if (uid.empty()) {
WebDAV: added CardDAV support This patch moves some of the CalDAV-specific code out of WebDAVSource into CalDAVSource and adds the corresponding CardDAV code in the new CardDAVSource. It improves searching for the right collection. A Yahoo Contacts specific problem is that vCard UID and resource name have to match, with ".vcf" used as suffix in the resource name. The new createResourceName() and setResourceName() virtual methods ensure that. They are nops in CalDAV and use simple string manipulation for vCard. Another Yahoo Contacts odity is merging of contacts in PUT, without telling the client. This can be detected by checking the expected resource name with PROPFIND: the server then replies with the real resource name. This check is done only if no href is returned in the response to PUT, so it should only be triggered for Yahoo Contacts. CardDAV support is still experimental. It was tested with Yahoo Contacts, using https://carddav.address.yahoo.com/dav/%u as sync URL. This is different from the CalDAV URL, which currently prevents using the same @yahoo context for both calendar and contacts. Working on CardDAV showed that resource name comparisons had issues due to different encoding of special characters. Added canonicalization of paths to fix this problem. This also includes appending the trailing slash for collections (Neon::URI::normalizePath()). Note that Buteo only serializes sync sessions based on their type, so as soon as we allow CardDAV in addition to CalDAV, it might happen that SyncEvolution is invokved multiple times in parallel => need to remove global variables first.
2010-11-25 10:08:07 +01:00
// Pick a resource name (done by derived classes, by default random),
// catch unexpected conflicts via If-None-Match: *.
WebDAV: added CardDAV support This patch moves some of the CalDAV-specific code out of WebDAVSource into CalDAVSource and adds the corresponding CardDAV code in the new CardDAVSource. It improves searching for the right collection. A Yahoo Contacts specific problem is that vCard UID and resource name have to match, with ".vcf" used as suffix in the resource name. The new createResourceName() and setResourceName() virtual methods ensure that. They are nops in CalDAV and use simple string manipulation for vCard. Another Yahoo Contacts odity is merging of contacts in PUT, without telling the client. This can be detected by checking the expected resource name with PROPFIND: the server then replies with the real resource name. This check is done only if no href is returned in the response to PUT, so it should only be triggered for Yahoo Contacts. CardDAV support is still experimental. It was tested with Yahoo Contacts, using https://carddav.address.yahoo.com/dav/%u as sync URL. This is different from the CalDAV URL, which currently prevents using the same @yahoo context for both calendar and contacts. Working on CardDAV showed that resource name comparisons had issues due to different encoding of special characters. Added canonicalization of paths to fix this problem. This also includes appending the trailing slash for collections (Neon::URI::normalizePath()). Note that Buteo only serializes sync sessions based on their type, so as soon as we allow CardDAV in addition to CalDAV, it might happen that SyncEvolution is invokved multiple times in parallel => need to remove global variables first.
2010-11-25 10:08:07 +01:00
std::string buffer;
const std::string *data = createResourceName(item, buffer, new_uid);
Neon::Request req(*m_session, operation,
operation == postOperation ? m_postPath : luid2path(new_uid),
WebDAV: added CardDAV support This patch moves some of the CalDAV-specific code out of WebDAVSource into CalDAVSource and adds the corresponding CardDAV code in the new CardDAVSource. It improves searching for the right collection. A Yahoo Contacts specific problem is that vCard UID and resource name have to match, with ".vcf" used as suffix in the resource name. The new createResourceName() and setResourceName() virtual methods ensure that. They are nops in CalDAV and use simple string manipulation for vCard. Another Yahoo Contacts odity is merging of contacts in PUT, without telling the client. This can be detected by checking the expected resource name with PROPFIND: the server then replies with the real resource name. This check is done only if no href is returned in the response to PUT, so it should only be triggered for Yahoo Contacts. CardDAV support is still experimental. It was tested with Yahoo Contacts, using https://carddav.address.yahoo.com/dav/%u as sync URL. This is different from the CalDAV URL, which currently prevents using the same @yahoo context for both calendar and contacts. Working on CardDAV showed that resource name comparisons had issues due to different encoding of special characters. Added canonicalization of paths to fix this problem. This also includes appending the trailing slash for collections (Neon::URI::normalizePath()). Note that Buteo only serializes sync sessions based on their type, so as soon as we allow CardDAV in addition to CalDAV, it might happen that SyncEvolution is invokved multiple times in parallel => need to remove global variables first.
2010-11-25 10:08:07 +01:00
*data, result);
// Clearing the idempotent flag would allow us to clearly
// distinguish between a connection error (no changes made
// on server) and a server failure (may or may not have
// changed something) because it'll close the connection
// first.
//
// But because we are going to try resending
// the PUT anyway in case of 5xx errors we might as well
// treat it like an idempotent request (which it is,
// in a way, because we'll try to get our data onto
// the server no matter what) and keep reusing an
// existing connection.
// req.setFlag(NE_REQFLAG_IDEMPOTENT, 0);
// For this to work we must allow the server to overwrite
// an item that we might have created before. Don't allow
// that in the first attempt. Only relevant for PUT.
if (operation != postOperation && counter == 1) {
req.addHeader("If-None-Match", "*");
}
WebDAV: added CardDAV support This patch moves some of the CalDAV-specific code out of WebDAVSource into CalDAVSource and adds the corresponding CardDAV code in the new CardDAVSource. It improves searching for the right collection. A Yahoo Contacts specific problem is that vCard UID and resource name have to match, with ".vcf" used as suffix in the resource name. The new createResourceName() and setResourceName() virtual methods ensure that. They are nops in CalDAV and use simple string manipulation for vCard. Another Yahoo Contacts odity is merging of contacts in PUT, without telling the client. This can be detected by checking the expected resource name with PROPFIND: the server then replies with the real resource name. This check is done only if no href is returned in the response to PUT, so it should only be triggered for Yahoo Contacts. CardDAV support is still experimental. It was tested with Yahoo Contacts, using https://carddav.address.yahoo.com/dav/%u as sync URL. This is different from the CalDAV URL, which currently prevents using the same @yahoo context for both calendar and contacts. Working on CardDAV showed that resource name comparisons had issues due to different encoding of special characters. Added canonicalization of paths to fix this problem. This also includes appending the trailing slash for collections (Neon::URI::normalizePath()). Note that Buteo only serializes sync sessions based on their type, so as soon as we allow CardDAV in addition to CalDAV, it might happen that SyncEvolution is invokved multiple times in parallel => need to remove global variables first.
2010-11-25 10:08:07 +01:00
req.addHeader("Content-Type", contentType().c_str());
static const std::set<int> expected = boost::assign::list_of(412)(403);
if (!req.run(&expected)) {
goto retry;
}
SE_LOG_DEBUG(NULL, "add item status: %s",
Neon::Status2String(req.getStatus()).c_str());
switch (req.getStatusCode()) {
case 204:
// stored, potentially in a different resource than requested
// when the UID was recognized
break;
case 201:
// created
break;
case 403:
// For a POST, this might be a UID conflict that we didn't detect
// ourselves. Happens for VJOURNAL and the testInsertTwice test
// when testing with Apple Calendar server. It then returns:
// Content-Type: text/xml
// Body:
// <?xml version='1.0' encoding='UTF-8'?>
// <error xmlns='DAV:'>
// <no-uid-conflict xmlns='urn:ietf:params:xml:ns:caldav'>
// <href xmlns='DAV:'>/calendars/__uids__/user01/tasks/c5490e736b6836c4d353d98bc78b3a3d.ics</href>
// </no-uid-conflict>
// <error-description xmlns='http://twistedmatrix.com/xml_namespace/dav/'>UID already exists</error-description>
// </error>
//
// Handling that would be nice (see FDO #77424), but for now we just
// do the same as for "Precondition Failed" and search for the UID.
if (operation == postOperation) {
try {
std::string uid = extractUID(item);
if (!uid.empty()) {
std::string luid = findByUID(uid, deadline);
return InsertItemResult(luid, "", ITEM_NEEDS_MERGE);
}
} catch (...) {
// Ignore the error and report the original problem below.
Exception::log();
}
}
SE_THROW_EXCEPTION_STATUS(TransportStatusException,
std::string("unexpected status for PUT: ") +
Neon::Status2String(req.getStatus()),
SyncMLStatus(req.getStatus()->code));
break;
case 412: {
// "Precondition Failed": our only precondition is the one about
// If-None-Match, which means that there must be an existing item
// with the same UID. Go find it, so that we can report back the
// right luid.
std::string uid = extractUID(item);
std::string luid = findByUID(uid, deadline);
return InsertItemResult(luid, "", ITEM_NEEDS_MERGE);
break;
}
default:
SE_THROW_EXCEPTION_STATUS(TransportStatusException,
std::string("unexpected status for insert: ") +
Neon::Status2String(req.getStatus()),
SyncMLStatus(req.getStatus()->code));
break;
}
rev = getETag(req);
std::string real_luid = getLUID(req);
if (!real_luid.empty()) {
// Google renames the resource automatically to something of the form
// <UID>.ics. Interestingly enough, our 1234567890!@#$%^&*()<>@dummy UID
// test case leads to a resource path which Google then cannot find
// via CalDAV. client-test must run with CLIENT_TEST_SIMPLE_UID=1...
SE_LOG_DEBUG(NULL, "new item mapped to %s", real_luid.c_str());
new_uid = real_luid;
// TODO: find a better way of detecting unexpected updates.
// state = ...
WebDAV: added CardDAV support This patch moves some of the CalDAV-specific code out of WebDAVSource into CalDAVSource and adds the corresponding CardDAV code in the new CardDAVSource. It improves searching for the right collection. A Yahoo Contacts specific problem is that vCard UID and resource name have to match, with ".vcf" used as suffix in the resource name. The new createResourceName() and setResourceName() virtual methods ensure that. They are nops in CalDAV and use simple string manipulation for vCard. Another Yahoo Contacts odity is merging of contacts in PUT, without telling the client. This can be detected by checking the expected resource name with PROPFIND: the server then replies with the real resource name. This check is done only if no href is returned in the response to PUT, so it should only be triggered for Yahoo Contacts. CardDAV support is still experimental. It was tested with Yahoo Contacts, using https://carddav.address.yahoo.com/dav/%u as sync URL. This is different from the CalDAV URL, which currently prevents using the same @yahoo context for both calendar and contacts. Working on CardDAV showed that resource name comparisons had issues due to different encoding of special characters. Added canonicalization of paths to fix this problem. This also includes appending the trailing slash for collections (Neon::URI::normalizePath()). Note that Buteo only serializes sync sessions based on their type, so as soon as we allow CardDAV in addition to CalDAV, it might happen that SyncEvolution is invokved multiple times in parallel => need to remove global variables first.
2010-11-25 10:08:07 +01:00
} else if (!rev.empty()) {
// Yahoo Contacts returns an etag, but no href. For items
// that were really created as requested, that's okay. But
// Yahoo Contacts silently merges the new contact with an
// existing one, presumably if it is "similar" enough. The
// web interface allows creating identical contacts
// multiple times; not so CardDAV. We are not even told
// the path of that other contact... Detect this by
// checking whether the item really exists.
//
// Google also returns an etag without a href. However,
// Google really creates a new item. We cannot tell here
// whether merging took place. As we are supporting Google
// but not Yahoo at the moment, let's assume that a new item
// was created.
WebDAV: added CardDAV support This patch moves some of the CalDAV-specific code out of WebDAVSource into CalDAVSource and adds the corresponding CardDAV code in the new CardDAVSource. It improves searching for the right collection. A Yahoo Contacts specific problem is that vCard UID and resource name have to match, with ".vcf" used as suffix in the resource name. The new createResourceName() and setResourceName() virtual methods ensure that. They are nops in CalDAV and use simple string manipulation for vCard. Another Yahoo Contacts odity is merging of contacts in PUT, without telling the client. This can be detected by checking the expected resource name with PROPFIND: the server then replies with the real resource name. This check is done only if no href is returned in the response to PUT, so it should only be triggered for Yahoo Contacts. CardDAV support is still experimental. It was tested with Yahoo Contacts, using https://carddav.address.yahoo.com/dav/%u as sync URL. This is different from the CalDAV URL, which currently prevents using the same @yahoo context for both calendar and contacts. Working on CardDAV showed that resource name comparisons had issues due to different encoding of special characters. Added canonicalization of paths to fix this problem. This also includes appending the trailing slash for collections (Neon::URI::normalizePath()). Note that Buteo only serializes sync sessions based on their type, so as soon as we allow CardDAV in addition to CalDAV, it might happen that SyncEvolution is invokved multiple times in parallel => need to remove global variables first.
2010-11-25 10:08:07 +01:00
RevisionMap_t revisions;
bool failed = false;
m_session->propfindURI(luid2path(new_uid), 0, getetag,
boost::bind(&WebDAVSource::listAllItemsCallback,
this, _1, _2, boost::ref(revisions),
boost::ref(failed)),
deadline);
WebDAV: added CardDAV support This patch moves some of the CalDAV-specific code out of WebDAVSource into CalDAVSource and adds the corresponding CardDAV code in the new CardDAVSource. It improves searching for the right collection. A Yahoo Contacts specific problem is that vCard UID and resource name have to match, with ".vcf" used as suffix in the resource name. The new createResourceName() and setResourceName() virtual methods ensure that. They are nops in CalDAV and use simple string manipulation for vCard. Another Yahoo Contacts odity is merging of contacts in PUT, without telling the client. This can be detected by checking the expected resource name with PROPFIND: the server then replies with the real resource name. This check is done only if no href is returned in the response to PUT, so it should only be triggered for Yahoo Contacts. CardDAV support is still experimental. It was tested with Yahoo Contacts, using https://carddav.address.yahoo.com/dav/%u as sync URL. This is different from the CalDAV URL, which currently prevents using the same @yahoo context for both calendar and contacts. Working on CardDAV showed that resource name comparisons had issues due to different encoding of special characters. Added canonicalization of paths to fix this problem. This also includes appending the trailing slash for collections (Neon::URI::normalizePath()). Note that Buteo only serializes sync sessions based on their type, so as soon as we allow CardDAV in addition to CalDAV, it might happen that SyncEvolution is invokved multiple times in parallel => need to remove global variables first.
2010-11-25 10:08:07 +01:00
// Turns out we get a result for our original path even in
// the case of a merge, although the original path is not
// listed when looking at the collection. Let's use that
// to return the "real" uid to our caller.
if (revisions.size() == 1 &&
revisions.begin()->first != new_uid) {
SE_LOG_DEBUG(NULL, "%s mapped to %s by peer",
WebDAV: added CardDAV support This patch moves some of the CalDAV-specific code out of WebDAVSource into CalDAVSource and adds the corresponding CardDAV code in the new CardDAVSource. It improves searching for the right collection. A Yahoo Contacts specific problem is that vCard UID and resource name have to match, with ".vcf" used as suffix in the resource name. The new createResourceName() and setResourceName() virtual methods ensure that. They are nops in CalDAV and use simple string manipulation for vCard. Another Yahoo Contacts odity is merging of contacts in PUT, without telling the client. This can be detected by checking the expected resource name with PROPFIND: the server then replies with the real resource name. This check is done only if no href is returned in the response to PUT, so it should only be triggered for Yahoo Contacts. CardDAV support is still experimental. It was tested with Yahoo Contacts, using https://carddav.address.yahoo.com/dav/%u as sync URL. This is different from the CalDAV URL, which currently prevents using the same @yahoo context for both calendar and contacts. Working on CardDAV showed that resource name comparisons had issues due to different encoding of special characters. Added canonicalization of paths to fix this problem. This also includes appending the trailing slash for collections (Neon::URI::normalizePath()). Note that Buteo only serializes sync sessions based on their type, so as soon as we allow CardDAV in addition to CalDAV, it might happen that SyncEvolution is invokved multiple times in parallel => need to remove global variables first.
2010-11-25 10:08:07 +01:00
new_uid.c_str(),
revisions.begin()->first.c_str());
new_uid = revisions.begin()->first;
// This would have to be uncommented for Yahoo.
// state = ITEM_REPLACED;
WebDAV: added CardDAV support This patch moves some of the CalDAV-specific code out of WebDAVSource into CalDAVSource and adds the corresponding CardDAV code in the new CardDAVSource. It improves searching for the right collection. A Yahoo Contacts specific problem is that vCard UID and resource name have to match, with ".vcf" used as suffix in the resource name. The new createResourceName() and setResourceName() virtual methods ensure that. They are nops in CalDAV and use simple string manipulation for vCard. Another Yahoo Contacts odity is merging of contacts in PUT, without telling the client. This can be detected by checking the expected resource name with PROPFIND: the server then replies with the real resource name. This check is done only if no href is returned in the response to PUT, so it should only be triggered for Yahoo Contacts. CardDAV support is still experimental. It was tested with Yahoo Contacts, using https://carddav.address.yahoo.com/dav/%u as sync URL. This is different from the CalDAV URL, which currently prevents using the same @yahoo context for both calendar and contacts. Working on CardDAV showed that resource name comparisons had issues due to different encoding of special characters. Added canonicalization of paths to fix this problem. This also includes appending the trailing slash for collections (Neon::URI::normalizePath()). Note that Buteo only serializes sync sessions based on their type, so as soon as we allow CardDAV in addition to CalDAV, it might happen that SyncEvolution is invokved multiple times in parallel => need to remove global variables first.
2010-11-25 10:08:07 +01:00
}
}
} else {
new_uid = uid;
WebDAV: added CardDAV support This patch moves some of the CalDAV-specific code out of WebDAVSource into CalDAVSource and adds the corresponding CardDAV code in the new CardDAVSource. It improves searching for the right collection. A Yahoo Contacts specific problem is that vCard UID and resource name have to match, with ".vcf" used as suffix in the resource name. The new createResourceName() and setResourceName() virtual methods ensure that. They are nops in CalDAV and use simple string manipulation for vCard. Another Yahoo Contacts odity is merging of contacts in PUT, without telling the client. This can be detected by checking the expected resource name with PROPFIND: the server then replies with the real resource name. This check is done only if no href is returned in the response to PUT, so it should only be triggered for Yahoo Contacts. CardDAV support is still experimental. It was tested with Yahoo Contacts, using https://carddav.address.yahoo.com/dav/%u as sync URL. This is different from the CalDAV URL, which currently prevents using the same @yahoo context for both calendar and contacts. Working on CardDAV showed that resource name comparisons had issues due to different encoding of special characters. Added canonicalization of paths to fix this problem. This also includes appending the trailing slash for collections (Neon::URI::normalizePath()). Note that Buteo only serializes sync sessions based on their type, so as soon as we allow CardDAV in addition to CalDAV, it might happen that SyncEvolution is invokved multiple times in parallel => need to remove global variables first.
2010-11-25 10:08:07 +01:00
std::string buffer;
const std::string *data = setResourceName(item, buffer, new_uid);
Neon::Request req(*m_session, "PUT", luid2path(new_uid),
WebDAV: added CardDAV support This patch moves some of the CalDAV-specific code out of WebDAVSource into CalDAVSource and adds the corresponding CardDAV code in the new CardDAVSource. It improves searching for the right collection. A Yahoo Contacts specific problem is that vCard UID and resource name have to match, with ".vcf" used as suffix in the resource name. The new createResourceName() and setResourceName() virtual methods ensure that. They are nops in CalDAV and use simple string manipulation for vCard. Another Yahoo Contacts odity is merging of contacts in PUT, without telling the client. This can be detected by checking the expected resource name with PROPFIND: the server then replies with the real resource name. This check is done only if no href is returned in the response to PUT, so it should only be triggered for Yahoo Contacts. CardDAV support is still experimental. It was tested with Yahoo Contacts, using https://carddav.address.yahoo.com/dav/%u as sync URL. This is different from the CalDAV URL, which currently prevents using the same @yahoo context for both calendar and contacts. Working on CardDAV showed that resource name comparisons had issues due to different encoding of special characters. Added canonicalization of paths to fix this problem. This also includes appending the trailing slash for collections (Neon::URI::normalizePath()). Note that Buteo only serializes sync sessions based on their type, so as soon as we allow CardDAV in addition to CalDAV, it might happen that SyncEvolution is invokved multiple times in parallel => need to remove global variables first.
2010-11-25 10:08:07 +01:00
*data, result);
// See above for discussion of idempotent and PUT.
// req.setFlag(NE_REQFLAG_IDEMPOTENT, 0);
WebDAV: added CardDAV support This patch moves some of the CalDAV-specific code out of WebDAVSource into CalDAVSource and adds the corresponding CardDAV code in the new CardDAVSource. It improves searching for the right collection. A Yahoo Contacts specific problem is that vCard UID and resource name have to match, with ".vcf" used as suffix in the resource name. The new createResourceName() and setResourceName() virtual methods ensure that. They are nops in CalDAV and use simple string manipulation for vCard. Another Yahoo Contacts odity is merging of contacts in PUT, without telling the client. This can be detected by checking the expected resource name with PROPFIND: the server then replies with the real resource name. This check is done only if no href is returned in the response to PUT, so it should only be triggered for Yahoo Contacts. CardDAV support is still experimental. It was tested with Yahoo Contacts, using https://carddav.address.yahoo.com/dav/%u as sync URL. This is different from the CalDAV URL, which currently prevents using the same @yahoo context for both calendar and contacts. Working on CardDAV showed that resource name comparisons had issues due to different encoding of special characters. Added canonicalization of paths to fix this problem. This also includes appending the trailing slash for collections (Neon::URI::normalizePath()). Note that Buteo only serializes sync sessions based on their type, so as soon as we allow CardDAV in addition to CalDAV, it might happen that SyncEvolution is invokved multiple times in parallel => need to remove global variables first.
2010-11-25 10:08:07 +01:00
req.addHeader("Content-Type", contentType());
// TODO: match exactly the expected revision, aka ETag,
// or implement locking. Note that the ETag might not be
// known, for example in this case:
// - PUT succeeds
// - PROPGET does not
// - insertItem() fails
// - Is retried? Might need slow sync in this case!
//
// req.addHeader("If-Match", etag);
if (!req.run()) {
goto retry;
}
SE_LOG_DEBUG(NULL, "update item status: %s",
Neon::Status2String(req.getStatus()).c_str());
switch (req.getStatusCode()) {
case 204:
// the expected outcome, as we were asking for an overwrite
break;
case 201:
// Huh? Shouldn't happen, but Google sometimes reports it
// even when updating an item. Accept it.
// SE_THROW("unexpected creation instead of update");
break;
default:
SE_THROW_EXCEPTION_STATUS(TransportStatusException,
std::string("unexpected status for update: ") +
Neon::Status2String(req.getStatus()),
SyncMLStatus(req.getStatus()->code));
break;
}
rev = getETag(req);
std::string real_luid = getLUID(req);
if (!real_luid.empty() && real_luid != new_uid) {
SE_THROW(StringPrintf("updating item: real luid %s does not match old luid %s",
real_luid.c_str(), new_uid.c_str()));
}
}
if (rev.empty()) {
// Server did not include etag header. Must request it
// explicitly (leads to race condition!). Google Calendar
// assigns a new ETag even if the body has not changed,
// so any kind of caching of ETag would not work either.
bool failed = false;
RevisionMap_t revisions;
m_session->propfindURI(luid2path(new_uid), 0, getetag,
boost::bind(&WebDAVSource::listAllItemsCallback,
this, _1, _2, boost::ref(revisions),
boost::ref(failed)),
deadline);
rev = revisions[new_uid];
if (failed || rev.empty()) {
SE_THROW("could not retrieve ETag");
}
}
return InsertItemResult(new_uid, rev, state);
}
std::string WebDAVSource::ETag2Rev(const std::string &etag)
{
std::string res = etag;
if (boost::starts_with(res, "W/")) {
res.erase(0, 2);
}
if (res.size() >= 2 &&
res[0] == '"' &&
res[res.size() - 1] == '"') {
res = res.substr(1, res.size() - 2);
}
return res;
}
std::string WebDAVSource::getLUID(Neon::Request &req)
{
std::string location = req.getResponseHeader("Location");
if (location.empty()) {
return location;
} else {
return path2luid(Neon::URI::parse(location).m_path);
}
}
bool WebDAVSource::isLeafCollection(const StringMap &props) const
{
// CardDAV and CalDAV both promise to not contain anything
// unrelated to them
StringMap::const_iterator it = props.find("DAV::resourcetype");
if (it != props.end()) {
const std::string &type = it->second;
// allow parameters (no closing bracket)
// and allow also "carddavaddressbook" (caused by invalid Neon
// string concatenation?!)
if (type.find("<urn:ietf:params:xml:ns:caldav:calendar") != type.npos ||
type.find("<urn:ietf:params:xml:ns:caldavcalendar") != type.npos ||
type.find("<urn:ietf:params:xml:ns:carddav:addressbook") != type.npos ||
type.find("<urn:ietf:params:xml:ns:carddavaddressbook") != type.npos) {
return true;
}
}
return false;
}
Timespec WebDAVSource::createDeadline() const
{
int timeoutSeconds = m_settings->timeoutSeconds();
int retrySeconds = m_settings->retrySeconds();
if (timeoutSeconds > 0 &&
retrySeconds > 0) {
return Timespec::monotonic() + timeoutSeconds;
} else {
return Timespec();
}
}
void WebDAVSource::removeItem(const string &uid)
{
Timespec deadline = createDeadline();
m_session->startOperation("DELETE", deadline);
std::string item, result;
boost::scoped_ptr<Neon::Request> req;
while (true) {
req.reset(new Neon::Request(*m_session, "DELETE", luid2path(uid),
item, result));
// TODO: match exactly the expected revision, aka ETag,
// or implement locking.
// req.addHeader("If-Match", etag);
static const std::set<int> expected = boost::assign::list_of(412);
if (req->run(&expected)) {
break;
}
}
SE_LOG_DEBUG(NULL, "remove item status: %s",
Neon::Status2String(req->getStatus()).c_str());
switch (req->getStatusCode()) {
case 204:
// the expected outcome
break;
case 200:
// reported by Radicale, also okay
break;
case 412:
// Radicale reports 412 'Precondition Failed'. Hmm, okay.
// Let's map it to the expected 404.
SE_THROW_EXCEPTION_STATUS(TransportStatusException,
"object not found (was 412 'Precondition Failed')",
SyncMLStatus(404));
break;
default:
SE_THROW_EXCEPTION_STATUS(TransportStatusException,
std::string("unexpected status for removal: ") +
Neon::Status2String(req->getStatus()),
SyncMLStatus(req->getStatus()->code));
break;
}
}
#endif /* ENABLE_DAV */
SE_END_CXX
#ifdef ENABLE_MODULES
# include "WebDAVSourceRegister.cpp"
#endif