2010-10-04 15:06:18 +02:00
/*
* Copyright ( C ) 2010 Intel Corporation
*/
# include "WebDAVSource.h"
2010-10-05 13:17:06 +02:00
# include <boost/bind.hpp>
# include <boost/algorithm/string/replace.hpp>
2010-10-05 14:48:59 +02:00
# include <boost/algorithm/string/predicate.hpp>
2010-11-09 14:16:02 +01:00
# include <boost/algorithm/string/classification.hpp>
2011-05-23 11:04:51 +02:00
# include <boost/algorithm/string/find.hpp>
2011-04-14 10:10:17 +02:00
# include <boost/scoped_ptr.hpp>
2010-10-05 13:17:06 +02:00
2011-01-27 12:32:37 +01:00
# include <syncevo/LogRedirect.h>
2013-07-29 16:51:26 +02:00
# include <syncevo/IdentityProvider.h>
2011-01-27 12:32:37 +01:00
2012-07-02 14:07:51 +02:00
# include <boost/assign.hpp>
2011-01-25 17:07:07 +01:00
# include <stdio.h>
# include <errno.h>
2010-10-04 15:06:18 +02:00
SE_BEGIN_CXX
2011-08-12 21:03:34 +02:00
BoolConfigProperty & WebDAVCredentialsOkay ( )
{
static BoolConfigProperty okay ( " webDAVCredentialsOkay " , " credentials were accepted before " ) ;
return okay ;
}
2011-04-15 13:30:51 +02:00
2011-04-20 18:13:16 +02:00
# ifdef ENABLE_DAV
2010-10-08 15:10:40 +02:00
/**
* Retrieve settings from SyncConfig .
* NULL pointer for config is allowed .
*/
class ContextSettings : public Neon : : Settings {
2011-04-15 13:30:51 +02:00
boost : : shared_ptr < SyncConfig > m_context ;
2012-03-12 18:17:13 +01:00
SyncSourceConfig * m_sourceConfig ;
2010-11-10 10:06:07 +01:00
std : : string m_url ;
2014-04-23 11:34:09 +02:00
std : : string m_urlDescription ;
2011-11-07 22:06:07 +01:00
/** do change tracking without relying on CTag */
bool m_noCTag ;
2010-11-10 10:06:07 +01:00
bool m_googleUpdateHack ;
bool m_googleChildHack ;
2010-11-30 17:45:40 +01:00
bool m_googleAlarmHack ;
2011-04-15 13:30:51 +02:00
// credentials were valid in the past: stored persistently in tracking node
bool m_credentialsOkay ;
2010-10-08 15:10:40 +02:00
public :
2012-03-12 18:17:13 +01:00
ContextSettings ( const boost : : shared_ptr < SyncConfig > & context ,
SyncSourceConfig * sourceConfig ) :
2010-11-30 17:45:40 +01:00
m_context ( context ) ,
2012-03-12 18:17:13 +01:00
m_sourceConfig ( sourceConfig ) ,
2011-11-07 22:06:07 +01:00
m_noCTag ( false ) ,
2010-11-30 17:45:40 +01:00
m_googleUpdateHack ( false ) ,
m_googleChildHack ( false ) ,
2011-04-15 13:30:51 +02:00
m_googleAlarmHack ( false ) ,
m_credentialsOkay ( false )
2010-10-08 15:10:40 +02:00
{
2012-03-12 18:17:13 +01:00
std : : string url ;
2014-04-23 11:34:09 +02:00
std : : string description = " <unset> " ;
std : : string syncName = m_context - > getConfigName ( ) ;
if ( syncName . empty ( ) ) {
syncName = " <none> " ;
}
2012-03-12 18:17:13 +01:00
// check source config first
if ( m_sourceConfig ) {
url = m_sourceConfig - > getDatabaseID ( ) ;
2013-07-26 10:22:11 +02:00
if ( url . find ( " %u " ) ! = url . npos ) {
std : : string username = getUsername ( ) ;
boost : : replace_all ( url , " %u " , Neon : : URI : : escape ( username ) ) ;
}
2014-04-23 11:34:09 +02:00
std : : string sourceName = m_sourceConfig - > getName ( ) ;
if ( sourceName . empty ( ) ) {
sourceName = " <none> " ;
}
description = StringPrintf ( " sync config '%s', source config '%s', database='%s' " ,
syncName . c_str ( ) ,
sourceName . c_str ( ) ,
url . c_str ( ) ) ;
2012-03-12 18:17:13 +01:00
}
// fall back to sync context
if ( url . empty ( ) & & m_context ) {
2010-10-08 15:10:40 +02:00
vector < string > urls = m_context - > getSyncURL ( ) ;
2011-05-23 11:04:51 +02:00
2010-10-29 17:25:20 +02:00
if ( ! urls . empty ( ) ) {
2012-03-12 18:17:13 +01:00
url = urls . front ( ) ;
2013-07-26 10:22:11 +02:00
if ( url . find ( " %u " ) ! = url . npos ) {
std : : string username = getUsername ( ) ;
boost : : replace_all ( url , " %u " , Neon : : URI : : escape ( username ) ) ;
}
2010-10-29 17:25:20 +02:00
}
2014-04-23 11:34:09 +02:00
description = StringPrintf ( " sync config '%s', syncURL='%s' " ,
syncName . c_str ( ) ,
url . c_str ( ) ) ;
2012-03-12 18:17:13 +01:00
}
// remember result and set flags
2014-04-23 11:34:09 +02:00
setURL ( url , description ) ;
2012-03-12 18:17:13 +01:00
// 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 ) {
2011-08-12 21:03:34 +02:00
boost : : shared_ptr < FilterConfigNode > node = m_context - > getNode ( WebDAVCredentialsOkay ( ) ) ;
m_credentialsOkay = WebDAVCredentialsOkay ( ) . getPropertyValue ( * node ) ;
2010-10-08 15:10:40 +02:00
}
}
2014-04-23 11:34:09 +02:00
void setURL ( const std : : string & url , const std : : string & description ) { initializeFlags ( url ) ; m_url = url ; m_urlDescription = description ; }
2010-11-10 10:06:07 +01:00
virtual std : : string getURL ( ) { return m_url ; }
2014-04-23 11:34:09 +02:00
std : : string getURLDescription ( ) { return m_urlDescription ; }
2010-11-10 10:06:07 +01:00
2010-10-08 15:10:40 +02:00
virtual bool verifySSLHost ( )
{
return ! m_context | | m_context - > getSSLVerifyHost ( ) ;
}
virtual bool verifySSLCertificate ( )
{
return ! m_context | | m_context - > getSSLVerifyServer ( ) ;
}
2010-11-22 14:52:34 +01:00
virtual std : : string proxy ( )
{
if ( ! m_context | |
! m_context - > getUseProxy ( ) ) {
return " " ;
} else {
return m_context - > getProxyHost ( ) ;
}
}
2011-11-07 22:06:07 +01:00
bool noCTag ( ) const { return m_noCTag ; }
2010-11-30 17:45:40 +01:00
virtual bool googleUpdateHack ( ) const { return m_googleUpdateHack ; }
virtual bool googleChildHack ( ) const { return m_googleChildHack ; }
virtual bool googleAlarmHack ( ) const { return m_googleChildHack ; }
2010-11-10 10:06:07 +01:00
2011-02-09 15:47:41 +01:00
virtual int timeoutSeconds ( ) const { return m_context - > getRetryDuration ( ) ; }
2011-04-15 13:30:51 +02:00
virtual int retrySeconds ( ) const {
int seconds = m_context - > getRetryInterval ( ) ;
if ( seconds > = 0 ) {
seconds / = ( 120 / 5 ) ; // default: 2min => 5s
}
return seconds ;
}
2011-02-09 15:47:41 +01:00
2010-10-08 15:10:40 +02:00
virtual void getCredentials ( const std : : string & realm ,
std : : string & username ,
2013-07-30 10:19:31 +02:00
std : : string & password ) ;
virtual boost : : shared_ptr < AuthProvider > getAuthProvider ( ) ;
2013-08-02 22:02:03 +02:00
2013-07-26 10:22:11 +02:00
std : : string getUsername ( )
{
2013-07-30 10:19:31 +02:00
lookupAuthProvider ( ) ;
return m_authProvider - > getUsername ( ) ;
2010-10-08 15:10:40 +02:00
}
2011-04-15 13:30:51 +02:00
virtual bool getCredentialsOkay ( ) { return m_credentialsOkay ; }
virtual void setCredentialsOkay ( bool okay ) {
2011-04-27 16:48:49 +02:00
if ( m_credentialsOkay ! = okay & & m_context ) {
2011-08-12 21:03:34 +02:00
boost : : shared_ptr < FilterConfigNode > node = m_context - > getNode ( WebDAVCredentialsOkay ( ) ) ;
2011-08-01 13:05:04 +02:00
if ( ! node - > isReadOnly ( ) ) {
2011-08-12 21:03:34 +02:00
WebDAVCredentialsOkay ( ) . setProperty ( * node , okay ) ;
2011-08-01 13:05:04 +02:00
node - > flush ( ) ;
}
2011-06-29 16:12:47 +02:00
m_credentialsOkay = okay ;
2011-04-15 13:30:51 +02:00
}
}
2010-10-08 15:10:40 +02:00
virtual int logLevel ( )
{
return m_context ?
2011-11-21 16:37:53 +01:00
m_context - > getLogLevel ( ) . get ( ) :
2013-04-08 22:43:07 +02:00
Logger : : instance ( ) . getLevel ( ) ;
2010-10-08 15:10:40 +02:00
}
2012-03-12 18:17:13 +01:00
private :
void initializeFlags ( const std : : string & url ) ;
2013-07-30 10:19:31 +02:00
boost : : shared_ptr < AuthProvider > m_authProvider ;
void lookupAuthProvider ( ) ;
2010-10-08 15:10:40 +02:00
} ;
2013-07-30 10:19:31 +02:00
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 ( )
2013-07-26 10:22:11 +02:00
{
2013-07-30 10:19:31 +02:00
lookupAuthProvider ( ) ;
return m_authProvider ;
}
void ContextSettings : : lookupAuthProvider ( )
{
if ( m_authProvider ) {
2013-07-26 10:22:11 +02:00
return ;
}
UserIdentity identity ;
InitStateString password ;
// prefer source config if anything is set there
2014-01-07 10:36:16 +01:00
const char * credentialsFrom = " undefined " ;
2013-07-26 10:22:11 +02:00
if ( m_sourceConfig ) {
identity = m_sourceConfig - > getUser ( ) ;
password = m_sourceConfig - > getPassword ( ) ;
2013-09-04 17:02:14 +02:00
credentialsFrom = " source config " ;
2013-07-26 10:22:11 +02:00
}
// fall back to context
if ( m_context & & ! identity . wasSet ( ) & & ! password . wasSet ( ) ) {
identity = m_context - > getSyncUser ( ) ;
password = m_context - > getSyncPassword ( ) ;
2013-09-04 17:02:14 +02:00
credentialsFrom = " source context " ;
2013-07-26 10:22:11 +02:00
}
2013-09-04 17:02:14 +02:00
SE_LOG_DEBUG ( NULL , " using username '%s' from %s for WebDAV, password %s " ,
identity . toString ( ) . c_str ( ) ,
credentialsFrom ,
password . wasSet ( ) ? " was set " : " not set " ) ;
2013-07-26 10:22:11 +02:00
2013-07-29 16:51:26 +02:00
// lookup actual authentication method instead of assuming username/password
2013-07-30 10:19:31 +02:00
m_authProvider = AuthProvider : : create ( identity , password ) ;
2013-07-26 10:22:11 +02:00
}
2012-03-12 18:17:13 +01:00
void ContextSettings : : initializeFlags ( const std : : string & url )
{
bool googleUpdate = false ,
googleChild = 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 " ) ) {
googleChild = true ;
} else if ( boost : : iequals ( * flag , " AlarmHack " ) ) {
googleAlarm = true ;
} else if ( boost : : iequals ( * flag , " Google " ) ) {
googleUpdate =
googleChild =
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_googleChildHack = googleChild ;
m_googleAlarmHack = googleAlarm ;
m_noCTag = noCTag ;
}
2010-10-05 11:02:33 +02:00
WebDAVSource : : WebDAVSource ( const SyncSourceParams & params ,
const boost : : shared_ptr < Neon : : Settings > & settings ) :
TrackingSyncSource ( params ) ,
m_settings ( settings )
2010-10-04 15:06:18 +02:00
{
2010-10-08 15:10:40 +02:00
if ( ! m_settings ) {
2012-03-12 18:17:13 +01:00
m_contextSettings . reset ( new ContextSettings ( params . m_context , this ) ) ;
2011-01-25 17:07:07 +01:00
m_settings = m_contextSettings ;
2010-10-08 15:10:40 +02:00
}
2011-06-20 10:48:00 +02:00
/* 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 ) ;
2011-10-10 13:36:04 +02:00
// 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 ( " ) ;
2010-10-04 15:06:18 +02:00
}
2012-06-15 10:55:01 +02:00
static const std : : string UID ( " \n UID: " ) ;
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 ( " \n END: " + 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 ( ) ) ;
}
2014-04-10 15:29:34 +02:00
// 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.
2012-06-29 11:32:21 +02:00
size_t start , end ;
std : : string uid = extractUID ( item , & start , & end ) ;
2014-04-10 15:29:34 +02:00
if ( uid = = olduid | | ! uid . empty ( ) ) {
2012-06-15 10:55:01 +02:00
return & item ;
}
// insert or overwrite
buffer = item ;
2012-06-29 11:32:21 +02:00
if ( start ! = std : : string : : npos ) {
// overwrite
buffer . replace ( start , end - start , olduid ) ;
2012-06-15 10:55:01 +02:00
} else {
// insert
start = buffer . find ( " \n END: " + getContent ( ) ) ;
if ( start ! = buffer . npos ) {
start + + ;
buffer . insert ( start , StringPrintf ( " UID:%s \n " , olduid . c_str ( ) ) ) ;
}
}
return & buffer ;
}
2012-06-29 11:32:21 +02:00
std : : string WebDAVSource : : extractUID ( const std : : string & item , size_t * startp , size_t * endp )
2012-06-15 10:55:01 +02:00
{
std : : string luid ;
2012-06-29 11:32:21 +02:00
if ( startp ) {
* startp = std : : string : : npos ;
}
if ( endp ) {
* endp = std : : string : : npos ;
}
2012-06-15 10:55:01 +02:00
// 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 ) {
2012-06-29 11:32:21 +02:00
if ( startp ) {
* startp = start ;
}
2012-06-15 10:55:01 +02:00
luid = item . substr ( start , end - start ) ;
if ( boost : : ends_with ( luid , " \r " ) ) {
luid . resize ( luid . size ( ) - 1 ) ;
}
2012-06-29 11:32:21 +02:00
// 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 ;
}
2012-06-15 10:55:01 +02:00
}
}
return luid ;
}
std : : string WebDAVSource : : getSuffix ( ) const
{
return getContent ( ) = = " VCARD " ?
" .vcf " :
" .ics " ;
}
2011-01-26 14:40:12 +01:00
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 ;
}
}
}
2010-10-04 15:06:18 +02:00
void WebDAVSource : : open ( )
2011-05-13 11:51:53 +02:00
{
// Nothing to do here, expensive initialization is in contactServer().
}
2011-10-10 13:36:04 +02:00
static bool setFirstURL ( Neon : : URI & result ,
const std : : string & name ,
const Neon : : URI & uri )
{
result = uri ;
// stop
return false ;
}
2011-05-13 11:51:53 +02:00
void WebDAVSource : : contactServer ( )
2010-10-04 15:06:18 +02:00
{
2011-05-18 10:01:36 +02:00
if ( ! m_calendar . empty ( ) & &
m_session ) {
// we have done this work before, no need to repeat it
}
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " using libneon %s with %s " ,
2011-10-10 13:36:04 +02:00
ne_version_string ( ) , Neon : : features ( ) . c_str ( ) ) ;
2011-06-17 14:48:43 +02:00
// Can we skip auto-detection because a full resource URL is set?
std : : string database = getDatabaseID ( ) ;
if ( ! database . empty ( ) & &
m_contextSettings ) {
2012-02-20 17:25:36 +01:00
m_calendar = Neon : : URI : : parse ( database , true ) ;
2011-06-17 14:48:43 +02:00
// m_contextSettings = m_settings, so this sets m_settings->getURL()
2014-04-23 11:34:09 +02:00
m_contextSettings - > setURL ( database ,
StringPrintf ( " %s database=%s " ,
getDisplayName ( ) . c_str ( ) ,
database . c_str ( ) ) ) ;
2011-06-17 14:48:43 +02:00
// start talking to host defined by m_settings->getURL()
m_session = Neon : : Session : : create ( m_settings ) ;
2014-04-23 11:34:09 +02:00
SE_LOG_INFO ( getDisplayName ( ) , " using configured database=%s " , database . c_str ( ) ) ;
2013-07-30 10:19:31 +02:00
// force authentication via username/password or OAuth2
m_session - > forceAuthorization ( m_settings - > getAuthProvider ( ) ) ;
2011-06-17 14:48:43 +02:00
return ;
}
2011-10-10 13:36:04 +02:00
// Create session and find first collection (the default).
m_calendar = Neon : : URI ( ) ;
2014-04-23 11:34:09 +02:00
SE_LOG_INFO ( getDisplayName ( ) , " determine final URL based on %s " ,
m_contextSettings ? m_contextSettings - > getURLDescription ( ) . c_str ( ) : " " ) ;
2011-10-10 13:36:04 +02:00
findCollections ( boost : : bind ( setFirstURL ,
boost : : ref ( m_calendar ) ,
_1 , _2 ) ) ;
if ( m_calendar . empty ( ) ) {
2014-04-02 14:57:56 +02:00
throwError ( SE_HERE , " no database found " ) ;
2011-10-10 13:36:04 +02:00
}
2014-04-23 11:34:09 +02:00
SE_LOG_INFO ( getDisplayName ( ) , " final URL path %s " , m_calendar . m_path . c_str ( ) ) ;
2011-10-10 13:36:04 +02:00
// 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
2013-04-08 22:43:07 +02:00
if ( Logger : : instance ( ) . getLevel ( ) > = Logger : : DEV ) {
2011-10-10 13:36:04 +02:00
try {
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " read capabilities of %s " , m_calendar . toURL ( ) . c_str ( ) ) ;
2011-10-10 13:36:04 +02:00
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 }
} ;
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " %s WebDAV capabilities: %s " ,
2011-10-10 13:36:04 +02:00
m_session - > getURL ( ) . c_str ( ) ,
Flags2String ( caps , descr ) . c_str ( ) ) ;
2013-07-30 10:19:31 +02:00
} catch ( const Neon : : FatalException & ex ) {
throw ;
2011-10-10 13:36:04 +02:00
} catch ( . . . ) {
Exception : : handle ( ) ;
}
}
# endif // HAVE_LIBNEON_OPTIONS
}
bool WebDAVSource : : findCollections ( const boost : : function < bool ( const std : : string & ,
const Neon : : URI & ) > & storeResult )
{
bool res = true ; // completed
2011-04-14 10:10:17 +02:00
int timeoutSeconds = m_settings - > timeoutSeconds ( ) ;
int retrySeconds = m_settings - > retrySeconds ( ) ;
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( getDisplayName ( ) , " timout %ds, retry %ds => %s " ,
2011-04-14 10:10:17 +02:00
timeoutSeconds , retrySeconds ,
( timeoutSeconds < = 0 | |
retrySeconds < = 0 ) ? " resending disabled " : " resending allowed " ) ;
2013-07-30 10:19:31 +02:00
boost : : shared_ptr < AuthProvider > authProvider = m_contextSettings - > getAuthProvider ( ) ;
std : : string username = authProvider - > getUsername ( ) ;
2011-01-25 17:07:07 +01:00
// 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.
std : : string url = m_settings - > getURL ( ) ;
if ( url . empty ( ) & & m_contextSettings ) {
size_t pos = username . find ( ' @ ' ) ;
if ( pos = = username . npos ) {
2011-02-10 10:06:47 +01:00
// throw authentication error to indicate that the credentials are wrong
2014-04-02 14:57:56 +02:00
throwError ( SE_HERE , STATUS_UNAUTHORIZED , StringPrintf ( " syncURL not configured and username %s does not contain a domain " , username . c_str ( ) ) ) ;
2011-01-25 17:07:07 +01:00
}
std : : string domain = username . substr ( pos + 1 ) ;
FILE * in = NULL ;
try {
2011-04-14 10:10:17 +02:00
Timespec startTime = Timespec : : monotonic ( ) ;
retry :
2011-01-25 17:07:07 +01:00
in = popen ( StringPrintf ( " syncevo-webdav-lookup '%s' '%s' " ,
serviceType ( ) . c_str ( ) ,
domain . c_str ( ) ) . c_str ( ) ,
" r " ) ;
if ( ! in ) {
2014-04-02 14:57:56 +02:00
throwError ( SE_HERE , " syncURL not configured and starting syncevo-webdav-lookup for DNS SRV lookup failed " , errno ) ;
2011-01-25 17:07:07 +01:00
}
// 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 ;
2014-04-23 11:34:09 +02:00
m_contextSettings - > setURL ( buffer ,
StringPrintf ( " DNS SRV URL for domain %s and service %s " ,
domain . c_str ( ) ,
serviceType ( ) . c_str ( ) ) ) ;
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( getDisplayName ( ) , " found syncURL '%s' via DNS SRV " , buffer ) ;
2011-01-25 17:07:07 +01:00
int res = pclose ( in ) ;
in = NULL ;
switch ( res ) {
case 0 :
break ;
case 2 :
2014-04-02 14:57:56 +02:00
throwError ( SE_HERE , StringPrintf ( " syncURL not configured and syncevo-webdav-lookup did not find a DNS utility to search for %s in %s " , serviceType ( ) . c_str ( ) , domain . c_str ( ) ) ) ;
2011-01-25 17:07:07 +01:00
break ;
case 3 :
2014-04-02 14:57:56 +02:00
throwError ( SE_HERE , StringPrintf ( " syncURL not configured and DNS SRV search for %s in %s did not find the service " , serviceType ( ) . c_str ( ) , domain . c_str ( ) ) ) ;
2011-01-25 17:07:07 +01:00
break ;
2011-04-14 10:10:17 +02:00
default : {
Timespec now = Timespec : : monotonic ( ) ;
if ( retrySeconds > 0 & &
timeoutSeconds > 0 ) {
if ( now < startTime + timeoutSeconds ) {
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( getDisplayName ( ) , " DNS SRV search failed due to network issues, retry in %d seconds " ,
2011-04-14 10:10:17 +02:00
retrySeconds ) ;
Sleep ( retrySeconds ) ;
goto retry ;
} else {
2013-04-08 19:17:36 +02:00
SE_LOG_INFO ( getDisplayName ( ) , " DNS SRV search timed out after %d seconds " , timeoutSeconds ) ;
2011-04-14 10:10:17 +02:00
}
}
2011-02-10 10:06:47 +01:00
// probably network problem
2014-04-02 14:57:56 +02:00
throwError ( SE_HERE , STATUS_TRANSPORT_FAILURE , StringPrintf ( " syncURL not configured and DNS SRV search for %s in %s failed " , serviceType ( ) . c_str ( ) , domain . c_str ( ) ) ) ;
2011-01-25 17:07:07 +01:00
break ;
}
2011-04-14 10:10:17 +02:00
}
2011-01-25 17:07:07 +01:00
} catch ( . . . ) {
if ( in ) {
pclose ( in ) ;
}
throw ;
}
}
// start talking to host defined by m_settings->getURL()
m_session = Neon : : Session : : create ( m_settings ) ;
2014-04-23 11:34:09 +02:00
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 ( ) : " " ) ;
2010-10-05 11:02:33 +02:00
2010-11-25 10:08:07 +01:00
// Find default calendar. Same for address book, with slightly
// different parameters.
2010-10-05 13:17:06 +02:00
//
2010-11-09 14:16:02 +01:00
// Stops when:
// - current path is calendar collection (= contains VEVENTs)
// Gives up:
// - when running in circles
// - nothing else to try out
// - tried 10 times
// Follows:
2011-03-01 15:21:53 +01:00
// - current-user-principal
// - CalDAV calendar-home-set
2010-11-09 14:16:02 +01:00
// - collections
//
2011-03-01 15:21:53 +01:00
// TODO: hrefs and redirects are assumed to be on the same host - support switching host
2010-11-09 14:16:02 +01:00
// TODO: support more than one calendar. Instead of stopping at the first one,
// scan more throroughly, then decide deterministically.
int counter = 0 ;
2011-10-04 16:19:14 +02:00
const int limit = 1000 ;
2012-10-02 16:34:34 +02:00
// Keeps track of paths to look at and those
// which were already tested.
2011-10-05 10:31:36 +02:00
class Tried : public std : : set < std : : string > {
2012-10-02 16:34:34 +02:00
std : : list < std : : string > m_candidates ;
bool m_found ;
2011-10-05 10:31:36 +02:00
public :
2012-10-02 16:34:34 +02:00
Tried ( ) : m_found ( false ) { }
/** Was path not tested yet? */
2011-10-05 10:31:36 +02:00
bool isNew ( const std : : string & path ) {
return find ( Neon : : URI : : normalizePath ( path , true ) ) = = end ( ) ;
}
2012-10-02 16:34:34 +02:00
/** Hand over next candidate to caller, empty if none available. */
std : : string getNextCandidate ( ) {
if ( ! m_candidates . empty ( ) ) {
std : : string candidate = m_candidates . front ( ) ;
m_candidates . pop_front ( ) ;
return candidate ;
} else {
return " " ;
}
}
/** remember that path was tested */
2011-10-05 10:31:36 +02:00
std : : string insert ( const std : : string & path ) {
std : : string normal = Neon : : URI : : normalizePath ( path , true ) ;
std : : set < std : : string > : : insert ( normal ) ;
2012-10-02 16:34:34 +02:00
m_candidates . remove ( normal ) ;
2011-10-05 10:31:36 +02:00
return normal ;
}
2012-10-02 16:34:34 +02:00
enum Position {
FRONT ,
BACK
} ;
void addCandidate ( const std : : string & path , Position position ) {
std : : string normal = Neon : : URI : : normalizePath ( path , true ) ;
if ( isNew ( normal ) ) {
if ( position = = FRONT ) {
m_candidates . push_front ( normal ) ;
} else {
m_candidates . push_back ( normal ) ;
}
}
}
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 ; }
2011-10-05 10:31:36 +02:00
} tried ;
2010-11-09 14:16:02 +01:00
std : : string path = m_session - > getURI ( ) . m_path ;
2014-04-01 16:33:54 +02:00
Props_t davProps ;
2010-10-05 14:24:16 +02:00
Neon : : Session : : PropfindPropCallback_t callback =
boost : : bind ( & WebDAVSource : : openPropCallback ,
2014-04-01 16:33:54 +02:00
this , boost : : ref ( davProps ) , _1 , _2 , _3 , _4 ) ;
2010-10-05 13:17:06 +02:00
2011-04-14 10:10:17 +02:00
// 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
2014-04-01 16:33:54 +02:00
// will just overwrite previously found information in davProps.
2011-04-14 10:10:17 +02:00
// Therefore resending is okay.
Timespec finalDeadline = createDeadline ( ) ; // no resending if left empty
2011-10-04 15:59:41 +02:00
// 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 ( path . empty ( ) | | path = = " / " ) {
std : : string wellknown = wellKnownURL ( ) ;
if ( ! wellknown . empty ( ) ) {
2012-10-02 16:34:34 +02:00
tried . addCandidate ( wellknown , Tried : : BACK ) ;
2011-10-04 15:59:41 +02:00
}
}
2010-11-09 14:16:02 +01:00
while ( true ) {
2011-05-23 11:04:51 +02:00
bool usernameInserted = false ;
2011-01-25 17:07:07 +01:00
std : : string next ;
2011-05-23 11:04:51 +02:00
// 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 ( path , " %u " ) ) {
boost : : replace_all ( path , " %u " , Neon : : URI : : escape ( username ) ) ;
usernameInserted = true ;
}
2010-11-25 10:08:07 +01:00
// must normalize so that we can compare against results from server
2011-10-05 10:31:36 +02:00
path = tried . insert ( path ) ;
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " testing %s " , path . c_str ( ) ) ;
2010-11-09 14:16:02 +01:00
2011-01-25 17:07:07 +01:00
// 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
2011-04-14 10:10:17 +02:00
// the root path as fallback.
2011-03-01 17:53:15 +01:00
if ( path = = " /.well-known/caldav/ " | |
path = = " /.well-known/carddav/ " ) {
2011-01-25 17:07:07 +01:00
// remove trailing slash added by normalization, to be aligned with draft-daboo-srv-caldav-10
path . resize ( path . size ( ) - 1 ) ;
2011-03-01 17:53:15 +01:00
// 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.
2012-10-02 16:34:34 +02:00
tried . addCandidate ( " / " , Tried : : BACK ) ;
2011-01-25 17:07:07 +01:00
// TODO: Google Calendar, with workarounds
// candidates.push_back(StringPrintf("/calendar/dav/%s/user/", Neon::URI::escape(username).c_str()));
}
bool success = false ;
try {
2011-04-14 10:10:17 +02:00
// disable resending for some known cases where it never succeeds
Timespec deadline = finalDeadline ;
if ( boost : : starts_with ( path , " /.well-known " ) & &
m_settings - > getURL ( ) . find ( " yahoo.com " ) ! = string : : npos ) {
deadline = Timespec ( ) ;
}
2013-04-08 22:43:07 +02:00
if ( Logger : : instance ( ) . getLevel ( ) > = Logger : : DEV ) {
2011-03-01 15:21:53 +01:00
// First dump WebDAV "allprops" properties (does not contain
// properties which must be asked for explicitly!). Only
// relevant for debugging.
WebDAV: ignore Radicale PROPFIND + allprops problem
The debug request failed with Radicale (details below). Now the result
of executing the request is ignored instead of failing the entire
operation.
PROPFIND /public_user/calendar/ HTTP/1.1
[<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:"><allprop/></propfind>
]
HTTP/1.0 500 Internal Server Error
Traceback (most recent call last):
File "/usr/lib/python2.7/wsgiref/handlers.py", line 85, in run
self.result = application(self.environ, self.start_response)
File "/usr/lib/python2.7/dist-packages/radicale/__init__.py", line 183, in __call__
status, headers, answer = function(environ, items, content, None)
File "/usr/lib/python2.7/dist-packages/radicale/__init__.py", line 365, in propfind
environ["PATH_INFO"], content, calendars, user)
File "/usr/lib/python2.7/dist-packages/radicale/xmlutils.py", line 184, in propfind
props = [prop.tag for prop in prop_element]
TypeError: 'NoneType' object is not iterable
2012-02-20 17:43:55 +01:00
try {
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " debugging: read all WebDAV properties of %s " , path . c_str ( ) ) ;
2013-07-30 10:19:31 +02:00
// Use OAuth2, if available.
boost : : shared_ptr < AuthProvider > authProvider = m_settings - > getAuthProvider ( ) ;
if ( authProvider - > methodIsSupported ( AuthProvider : : AUTH_METHOD_OAUTH2 ) ) {
m_session - > forceAuthorization ( authProvider ) ;
}
WebDAV: ignore Radicale PROPFIND + allprops problem
The debug request failed with Radicale (details below). Now the result
of executing the request is ignored instead of failing the entire
operation.
PROPFIND /public_user/calendar/ HTTP/1.1
[<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:"><allprop/></propfind>
]
HTTP/1.0 500 Internal Server Error
Traceback (most recent call last):
File "/usr/lib/python2.7/wsgiref/handlers.py", line 85, in run
self.result = application(self.environ, self.start_response)
File "/usr/lib/python2.7/dist-packages/radicale/__init__.py", line 183, in __call__
status, headers, answer = function(environ, items, content, None)
File "/usr/lib/python2.7/dist-packages/radicale/__init__.py", line 365, in propfind
environ["PATH_INFO"], content, calendars, user)
File "/usr/lib/python2.7/dist-packages/radicale/xmlutils.py", line 184, in propfind
props = [prop.tag for prop in prop_element]
TypeError: 'NoneType' object is not iterable
2012-02-20 17:43:55 +01:00
Neon : : Session : : PropfindPropCallback_t callback =
boost : : bind ( & WebDAVSource : : openPropCallback ,
2014-04-01 16:33:54 +02:00
this , boost : : ref ( davProps ) , _1 , _2 , _3 , _4 ) ;
WebDAV: ignore Radicale PROPFIND + allprops problem
The debug request failed with Radicale (details below). Now the result
of executing the request is ignored instead of failing the entire
operation.
PROPFIND /public_user/calendar/ HTTP/1.1
[<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:"><allprop/></propfind>
]
HTTP/1.0 500 Internal Server Error
Traceback (most recent call last):
File "/usr/lib/python2.7/wsgiref/handlers.py", line 85, in run
self.result = application(self.environ, self.start_response)
File "/usr/lib/python2.7/dist-packages/radicale/__init__.py", line 183, in __call__
status, headers, answer = function(environ, items, content, None)
File "/usr/lib/python2.7/dist-packages/radicale/__init__.py", line 365, in propfind
environ["PATH_INFO"], content, calendars, user)
File "/usr/lib/python2.7/dist-packages/radicale/xmlutils.py", line 184, in propfind
props = [prop.tag for prop in prop_element]
TypeError: 'NoneType' object is not iterable
2012-02-20 17:43:55 +01:00
m_session - > propfindProp ( path , 0 , NULL , callback , Timespec ( ) ) ;
2013-07-30 10:19:31 +02:00
} catch ( const Neon : : FatalException & ex ) {
throw ;
WebDAV: ignore Radicale PROPFIND + allprops problem
The debug request failed with Radicale (details below). Now the result
of executing the request is ignored instead of failing the entire
operation.
PROPFIND /public_user/calendar/ HTTP/1.1
[<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:"><allprop/></propfind>
]
HTTP/1.0 500 Internal Server Error
Traceback (most recent call last):
File "/usr/lib/python2.7/wsgiref/handlers.py", line 85, in run
self.result = application(self.environ, self.start_response)
File "/usr/lib/python2.7/dist-packages/radicale/__init__.py", line 183, in __call__
status, headers, answer = function(environ, items, content, None)
File "/usr/lib/python2.7/dist-packages/radicale/__init__.py", line 365, in propfind
environ["PATH_INFO"], content, calendars, user)
File "/usr/lib/python2.7/dist-packages/radicale/xmlutils.py", line 184, in propfind
props = [prop.tag for prop in prop_element]
TypeError: 'NoneType' object is not iterable
2012-02-20 17:43:55 +01:00
} catch ( . . . ) {
2012-10-02 16:34:34 +02:00
handleException ( HANDLE_EXCEPTION_NO_ERROR ) ;
WebDAV: ignore Radicale PROPFIND + allprops problem
The debug request failed with Radicale (details below). Now the result
of executing the request is ignored instead of failing the entire
operation.
PROPFIND /public_user/calendar/ HTTP/1.1
[<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:"><allprop/></propfind>
]
HTTP/1.0 500 Internal Server Error
Traceback (most recent call last):
File "/usr/lib/python2.7/wsgiref/handlers.py", line 85, in run
self.result = application(self.environ, self.start_response)
File "/usr/lib/python2.7/dist-packages/radicale/__init__.py", line 183, in __call__
status, headers, answer = function(environ, items, content, None)
File "/usr/lib/python2.7/dist-packages/radicale/__init__.py", line 365, in propfind
environ["PATH_INFO"], content, calendars, user)
File "/usr/lib/python2.7/dist-packages/radicale/xmlutils.py", line 184, in propfind
props = [prop.tag for prop in prop_element]
TypeError: 'NoneType' object is not iterable
2012-02-20 17:43:55 +01:00
}
2011-01-25 17:07:07 +01:00
}
2013-07-30 10:19:31 +02:00
2011-01-25 17:07:07 +01:00
// 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.
2011-03-01 15:21:53 +01:00
//
// 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>
//
2013-07-30 10:19:31 +02:00
// We send valid credentials here, using Basic authorization,
// if configured to use credentials instead of something like OAuth2.
2011-03-01 15:21:53 +01:00
// The rationale is that this cuts down on the number of
// requests for https while still being secure. For
2013-07-30 10:19:31 +02:00
// http, our Neon wrapper is smart enough to ignore our request.
2011-03-01 15:21:53 +01:00
//
// 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
2013-07-30 10:19:31 +02:00
m_session - > forceAuthorization ( m_settings - > getAuthProvider ( ) ) ;
2014-04-01 16:33:54 +02:00
davProps . clear ( ) ;
2013-02-04 13:57:16 +01:00
// 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).
2011-01-25 17:07:07 +01:00
static const ne_propname caldav [ ] = {
// WebDAV ACL
{ " DAV: " , " alternate-URI-set " } ,
{ " DAV: " , " principal-URL " } ,
2011-03-01 15:21:53 +01:00
{ " DAV: " , " current-user-principal " } ,
2011-01-25 17:07:07 +01:00
{ " 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 " } ,
2013-02-04 13:57:16 +01:00
{ 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 " } ,
2011-01-25 17:07:07 +01:00
// 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 " } ,
{ NULL , NULL }
} ;
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " read relevant properties of %s " , path . c_str ( ) ) ;
2013-02-04 13:57:16 +01:00
m_session - > propfindProp ( path , 0 ,
getContent ( ) = = " VCARD " ? carddav : caldav ,
callback , deadline ) ;
2011-01-25 17:07:07 +01:00
success = true ;
2013-07-30 10:19:31 +02:00
} catch ( const Neon : : FatalException & ex ) {
throw ;
2011-03-01 15:21:53 +01:00
} catch ( const Neon : : RedirectException & ex ) {
// follow to new location
2012-02-20 17:25:36 +01:00
Neon : : URI next = Neon : : URI : : parse ( ex . getLocation ( ) , true ) ;
2011-03-01 15:21:53 +01:00
Neon : : URI old = m_session - > getURI ( ) ;
2011-10-04 16:04:34 +02:00
// keep old host + scheme + port if not set in next location
if ( next . m_scheme . empty ( ) ) {
next . m_scheme = old . m_scheme ;
}
if ( next . m_host . empty ( ) ) {
next . m_host = old . m_host ;
}
if ( ! next . m_port ) {
next . m_port = old . m_port ;
}
2011-03-01 15:21:53 +01:00
if ( next . m_scheme ! = old . m_scheme | |
next . m_host ! = old . m_host | |
next . m_port ! = old . m_port ) {
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " ignore redirection to different server (not implemented): %s " ,
2011-03-01 15:21:53 +01:00
ex . getLocation ( ) . c_str ( ) ) ;
2012-10-02 16:34:34 +02:00
if ( tried . errorIsFatal ( ) ) {
throw ;
2011-03-01 15:21:53 +01:00
}
2011-10-05 10:31:36 +02:00
} else if ( tried . isNew ( next . m_path ) ) {
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " new candidate from %s -> %s redirect " ,
2011-10-04 16:21:26 +02:00
old . m_path . c_str ( ) ,
next . m_path . c_str ( ) ) ;
2012-10-02 16:34:34 +02:00
tried . addCandidate ( next . m_path , Tried : : FRONT ) ;
2011-10-04 16:21:26 +02:00
} else {
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " already known candidate from %s -> %s redirect " ,
2011-10-04 16:21:26 +02:00
old . m_path . c_str ( ) ,
next . m_path . c_str ( ) ) ;
2011-03-01 15:21:53 +01:00
}
2011-05-23 11:04:51 +02:00
} catch ( const TransportStatusException & ex ) {
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " TransportStatusException: %s " , ex . what ( ) ) ;
2011-05-23 11:04:51 +02:00
if ( ex . syncMLStatus ( ) = = 404 & & boost : : find_first ( 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? " ,
path . c_str ( ) , username . c_str ( ) ) ;
int code = 401 ;
SE_THROW_EXCEPTION_STATUS ( TransportStatusException , descr , SyncMLStatus ( code ) ) ;
2011-06-29 02:57:38 +02:00
} else {
2012-10-02 16:34:34 +02:00
if ( tried . errorIsFatal ( ) ) {
2011-06-29 02:57:38 +02:00
throw ;
} else {
// ignore the error (whatever it was!), try next
// candidate; needed to handle 502 "Connection
// refused" for /.well-known/caldav/ from Yahoo!
// Calendar
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " ignore error for URI candidate: %s " , ex . what ( ) ) ;
2011-06-29 02:57:38 +02:00
}
2011-05-23 11:04:51 +02:00
}
2011-01-25 17:07:07 +01:00
} catch ( const Exception & ex ) {
2012-10-02 16:34:34 +02:00
if ( tried . errorIsFatal ( ) ) {
2011-01-25 17:07:07 +01:00
throw ;
} else {
// ignore the error (whatever it was!), try next
// candidate; needed to handle 502 "Connection
// refused" for /.well-known/caldav/ from Yahoo!
// Calendar
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " ignore error for URI candidate: %s " , ex . what ( ) ) ;
2011-01-25 17:07:07 +01:00
}
2010-11-09 14:16:02 +01:00
}
2011-01-25 17:07:07 +01:00
if ( success ) {
2014-04-01 16:33:54 +02:00
Props_t : : iterator pathProps = davProps . find ( path ) ;
if ( pathProps = = davProps . end ( ) ) {
2011-03-01 17:53:15 +01:00
// No reply for requested path? Happens with Yahoo Calendar server,
// which returns information about "/dav" when asked about "/".
// Move to that path.
2014-04-01 16:33:54 +02:00
if ( ! davProps . empty ( ) ) {
pathProps = davProps . begin ( ) ;
2012-02-21 13:10:42 +01:00
string newpath = pathProps - > first ;
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " use properties for '%s' instead of '%s' " ,
2011-03-01 17:53:15 +01:00
newpath . c_str ( ) , path . c_str ( ) ) ;
path = newpath ;
}
}
2014-04-01 16:33:54 +02:00
StringMap * props = pathProps = = davProps . end ( ) ? NULL : & pathProps - > second ;
2012-10-02 16:34:34 +02:00
bool isResult = false ;
WebDAV: avoid segfault during collection lookup
Avoid referencing pathProps->second when the set of paths that
PROPFINDs returns is empty. Apparently this can happen in combination
with Calypso.
The stack backtrace sent via email looked like this:
Program received signal SIGSEGV, Segmentation fault.
0x4031a1a0 in std::_Rb_tree<std::string, std::pair<std::string const, std::string>, std::_Select1st<std::pair<std::string const, std::string> >, std::less<std::string>, std::allocator<std::pair<std::string const, std::string> > >::find(std::string const&) const () from /usr/lib/libsyncevolution.so.0
0x4031a1a0 <_ZNKSt8_Rb_treeISsSt4pairIKSsSsESt10_Select1stIS2_ESt4lessISsESaIS2_EE4findERS1_+60>: ldr r4, [r0, #-12]
(gdb) bt
from /usr/lib/syncevolution/backends/syncdav.so
from /usr/lib/syncevolution/backends/syncdav.so
from /usr/lib/libsyncevolution.so.0
2013-05-13 17:37:50 +02:00
if ( props & & typeMatches ( * props ) ) {
2012-10-02 16:34:34 +02:00
isResult = true ;
2011-10-10 13:36:04 +02:00
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
2012-10-02 16:34:34 +02:00
tried . foundResult ( ) ;
WebDAV: avoid segfault during collection lookup
Avoid referencing pathProps->second when the set of paths that
PROPFINDs returns is empty. Apparently this can happen in combination
with Calypso.
The stack backtrace sent via email looked like this:
Program received signal SIGSEGV, Segmentation fault.
0x4031a1a0 in std::_Rb_tree<std::string, std::pair<std::string const, std::string>, std::_Select1st<std::pair<std::string const, std::string> >, std::less<std::string>, std::allocator<std::pair<std::string const, std::string> > >::find(std::string const&) const () from /usr/lib/libsyncevolution.so.0
0x4031a1a0 <_ZNKSt8_Rb_treeISsSt4pairIKSsSsESt10_Select1stIS2_ESt4lessISsESaIS2_EE4findERS1_+60>: ldr r4, [r0, #-12]
(gdb) bt
from /usr/lib/syncevolution/backends/syncdav.so
from /usr/lib/syncevolution/backends/syncdav.so
from /usr/lib/libsyncevolution.so.0
2013-05-13 17:37:50 +02:00
it = props - > find ( " DAV::displayname " ) ;
2011-10-10 13:36:04 +02:00
Neon : : URI uri = m_session - > getURI ( ) ;
uri . m_path = path ;
2011-10-04 16:21:26 +02:00
std : : string name ;
WebDAV: avoid segfault during collection lookup
Avoid referencing pathProps->second when the set of paths that
PROPFINDs returns is empty. Apparently this can happen in combination
with Calypso.
The stack backtrace sent via email looked like this:
Program received signal SIGSEGV, Segmentation fault.
0x4031a1a0 in std::_Rb_tree<std::string, std::pair<std::string const, std::string>, std::_Select1st<std::pair<std::string const, std::string> >, std::less<std::string>, std::allocator<std::pair<std::string const, std::string> > >::find(std::string const&) const () from /usr/lib/libsyncevolution.so.0
0x4031a1a0 <_ZNKSt8_Rb_treeISsSt4pairIKSsSsESt10_Select1stIS2_ESt4lessISsESaIS2_EE4findERS1_+60>: ldr r4, [r0, #-12]
(gdb) bt
from /usr/lib/syncevolution/backends/syncdav.so
from /usr/lib/syncevolution/backends/syncdav.so
from /usr/lib/libsyncevolution.so.0
2013-05-13 17:37:50 +02:00
if ( it ! = props - > end ( ) ) {
2011-10-04 16:21:26 +02:00
name = it - > second ;
}
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " found %s = %s " ,
2011-10-04 16:21:26 +02:00
name . c_str ( ) ,
uri . toURL ( ) . c_str ( ) ) ;
res = storeResult ( name ,
2011-10-10 13:36:04 +02:00
uri ) ;
if ( ! res ) {
// done
break ;
}
2011-01-25 17:07:07 +01:00
}
2011-03-01 15:21:53 +01:00
// find next path:
// prefer CardDAV:calendar-home-set or CalDAV:addressbook-home-set
WebDAV: avoid segfault during collection lookup
Avoid referencing pathProps->second when the set of paths that
PROPFINDs returns is empty. Apparently this can happen in combination
with Calypso.
The stack backtrace sent via email looked like this:
Program received signal SIGSEGV, Segmentation fault.
0x4031a1a0 in std::_Rb_tree<std::string, std::pair<std::string const, std::string>, std::_Select1st<std::pair<std::string const, std::string> >, std::less<std::string>, std::allocator<std::pair<std::string const, std::string> > >::find(std::string const&) const () from /usr/lib/libsyncevolution.so.0
0x4031a1a0 <_ZNKSt8_Rb_treeISsSt4pairIKSsSsESt10_Select1stIS2_ESt4lessISsESaIS2_EE4findERS1_+60>: ldr r4, [r0, #-12]
(gdb) bt
from /usr/lib/syncevolution/backends/syncdav.so
from /usr/lib/syncevolution/backends/syncdav.so
from /usr/lib/libsyncevolution.so.0
2013-05-13 17:37:50 +02:00
std : : list < std : : string > homes ;
if ( props ) {
homes = extractHREFs ( ( * props ) [ homeSetProp ( ) ] ) ;
}
2011-10-04 16:11:26 +02:00
BOOST_FOREACH ( const std : : string & home , homes ) {
if ( ! home . empty ( ) & &
2011-10-05 10:31:36 +02:00
tried . isNew ( home ) ) {
2011-10-04 16:11:26 +02:00
if ( next . empty ( ) ) {
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " follow home-set property to %s " , home . c_str ( ) ) ;
2011-10-04 16:11:26 +02:00
next = home ;
} else {
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " new candidate from home-set property %s " , home . c_str ( ) ) ;
2012-10-02 16:34:34 +02:00
tried . addCandidate ( home , Tried : : FRONT ) ;
2011-10-04 16:11:26 +02:00
}
}
2011-03-01 15:21:53 +01:00
}
// alternatively, follow principal URL
if ( next . empty ( ) ) {
WebDAV: avoid segfault during collection lookup
Avoid referencing pathProps->second when the set of paths that
PROPFINDs returns is empty. Apparently this can happen in combination
with Calypso.
The stack backtrace sent via email looked like this:
Program received signal SIGSEGV, Segmentation fault.
0x4031a1a0 in std::_Rb_tree<std::string, std::pair<std::string const, std::string>, std::_Select1st<std::pair<std::string const, std::string> >, std::less<std::string>, std::allocator<std::pair<std::string const, std::string> > >::find(std::string const&) const () from /usr/lib/libsyncevolution.so.0
0x4031a1a0 <_ZNKSt8_Rb_treeISsSt4pairIKSsSsESt10_Select1stIS2_ESt4lessISsESaIS2_EE4findERS1_+60>: ldr r4, [r0, #-12]
(gdb) bt
from /usr/lib/syncevolution/backends/syncdav.so
from /usr/lib/syncevolution/backends/syncdav.so
from /usr/lib/libsyncevolution.so.0
2013-05-13 17:37:50 +02:00
std : : string principal ;
if ( props ) {
principal = extractHREF ( ( * props ) [ " DAV::current-user-principal " ] ) ;
}
2012-10-02 16:34:34 +02:00
// TODO:
// xmlns:d="DAV:"
// <d:current-user-principal><d:href>/m8/carddav/principals/__uids__/patrick.ohly@googlemail.com/</d:href></d:current-user-principal>
2011-03-01 15:21:53 +01:00
if ( ! principal . empty ( ) & &
2011-10-05 10:31:36 +02:00
tried . isNew ( principal ) ) {
2011-03-01 15:21:53 +01:00
next = principal ;
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " follow current-user-prinicipal to %s " , next . c_str ( ) ) ;
2010-11-09 14:16:02 +01:00
}
}
2012-10-02 16:34:34 +02:00
// finally, recursively descend into collections,
// unless we identified it as a result (because those
// cannot be recursive)
if ( next . empty ( ) & & ! isResult ) {
WebDAV: avoid segfault during collection lookup
Avoid referencing pathProps->second when the set of paths that
PROPFINDs returns is empty. Apparently this can happen in combination
with Calypso.
The stack backtrace sent via email looked like this:
Program received signal SIGSEGV, Segmentation fault.
0x4031a1a0 in std::_Rb_tree<std::string, std::pair<std::string const, std::string>, std::_Select1st<std::pair<std::string const, std::string> >, std::less<std::string>, std::allocator<std::pair<std::string const, std::string> > >::find(std::string const&) const () from /usr/lib/libsyncevolution.so.0
0x4031a1a0 <_ZNKSt8_Rb_treeISsSt4pairIKSsSsESt10_Select1stIS2_ESt4lessISsESaIS2_EE4findERS1_+60>: ldr r4, [r0, #-12]
(gdb) bt
from /usr/lib/syncevolution/backends/syncdav.so
from /usr/lib/syncevolution/backends/syncdav.so
from /usr/lib/libsyncevolution.so.0
2013-05-13 17:37:50 +02:00
std : : string type ;
if ( props ) {
type = ( * props ) [ " DAV::resourcetype " ] ;
}
2014-04-02 11:05:26 +02:00
bool isCollection = type . find ( " <DAV:collection></DAV:collection> " ) ! = type . npos ;
if ( isCollection & & 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 " , path . c_str ( ) ) ;
} else if ( isCollection ) {
2011-01-25 17:07:07 +01:00
// 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.
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " list items in %s " , path . c_str ( ) ) ;
2013-02-04 13:57:16 +01:00
// See findCollections() for the reason why we are not mixing CalDAV and CardDAV
// properties.
static const ne_propname caldav [ ] = {
2011-01-25 17:07:07 +01:00
{ " 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 " } ,
2013-02-04 13:57:16 +01:00
{ NULL , NULL }
} ;
static const ne_propname carddav [ ] = {
{ " DAV: " , " displayname " } ,
{ " DAV: " , " resourcetype " } ,
2011-01-25 17:07:07 +01:00
{ " 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 }
} ;
2014-04-01 16:33:54 +02:00
davProps . clear ( ) ;
2013-02-04 13:57:16 +01:00
m_session - > propfindProp ( path , 1 ,
getContent ( ) = = " VCARD " ? carddav : caldav ,
callback , finalDeadline ) ;
2011-10-04 15:52:16 +02:00
std : : set < std : : string > subs ;
2014-04-01 16:33:54 +02:00
BOOST_FOREACH ( Props_t : : value_type & entry , davProps ) {
2011-01-25 17:07:07 +01:00
const std : : string & sub = entry . first ;
const std : : string & subType = entry . second [ " DAV::resourcetype " ] ;
// new candidates are:
// - untested
// - not already a candidate
2011-10-10 13:36:04 +02:00
// - a resource, but not the CalDAV schedule-inbox/outbox
2011-03-01 15:21:53 +01:00
// - 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 (?).
2014-04-02 11:05:26 +02:00
if ( ! tried . isNew ( sub ) ) {
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 {
2011-10-04 15:52:16 +02:00
subs . insert ( sub ) ;
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " new candidate: %s " , sub . c_str ( ) ) ;
2010-11-25 10:08:07 +01:00
}
2010-11-09 14:16:02 +01:00
}
2011-10-04 15:52:16 +02:00
// insert before other candidates, sorted
// alphabetically
2012-10-02 16:34:34 +02:00
BOOST_REVERSE_FOREACH ( const std : : string & path , subs ) {
tried . addCandidate ( path , Tried : : FRONT ) ;
}
2010-11-09 14:16:02 +01:00
}
}
}
2011-01-25 17:07:07 +01:00
2010-11-09 14:16:02 +01:00
if ( next . empty ( ) ) {
2011-10-04 16:03:07 +02:00
// use next untried candidate
2012-10-02 16:34:34 +02:00
next = tried . getNextCandidate ( ) ;
2011-10-04 16:03:07 +02:00
if ( next . empty ( ) ) {
// done searching
2011-10-10 13:36:04 +02:00
break ;
2010-11-09 14:16:02 +01:00
}
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " follow candidate %s " , next . c_str ( ) ) ;
2010-11-09 14:16:02 +01:00
}
counter + + ;
if ( counter > limit ) {
2014-04-02 14:57:56 +02:00
throwError ( SE_HERE , StringPrintf ( " giving up search for collection after %d attempts " , limit ) ) ;
2010-11-09 14:16:02 +01:00
}
path = next ;
2010-10-05 14:48:59 +02:00
}
2010-11-09 14:16:02 +01:00
2011-10-10 13:36:04 +02:00
return res ;
2010-10-05 13:17:06 +02:00
}
2011-03-01 15:21:53 +01:00
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 " " ;
}
2011-10-04 16:11:26 +02:00
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 ;
}
2014-04-01 16:33:54 +02:00
void WebDAVSource : : openPropCallback ( Props_t & davProps ,
const Neon : : URI & uri ,
2010-10-05 13:17:06 +02:00
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
2010-11-09 14:16:02 +01:00
std : : string name ;
if ( prop - > nspace ) {
name = prop - > nspace ;
}
name + = " : " ;
name + = prop - > name ;
if ( value ) {
2014-04-01 16:33:54 +02:00
davProps [ uri . m_path ] [ name ] = value ;
boost : : trim_if ( davProps [ uri . m_path ] [ name ] ,
2010-11-09 14:16:02 +01:00
boost : : is_space ( ) ) ;
}
2010-10-04 15:06:18 +02:00
}
bool WebDAVSource : : isEmpty ( )
{
2011-05-18 10:01:36 +02:00
contactServer ( ) ;
2010-10-05 16:02:58 +02:00
// listing all items is relatively efficient, let's use that
2010-11-25 10:08:07 +01:00
// TODO: use truncated result search
2010-10-05 16:02:58 +02:00
RevisionMap_t revisions ;
listAllItems ( revisions ) ;
return revisions . empty ( ) ;
2010-10-04 15:06:18 +02:00
}
void WebDAVSource : : close ( )
{
2010-10-05 11:02:33 +02:00
m_session . reset ( ) ;
2010-10-04 15:06:18 +02:00
}
2011-10-10 13:36:04 +02:00
static bool storeCollection ( SyncSource : : Databases & result ,
const std : : string & name ,
const Neon : : URI & uri )
{
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 ) ) ;
return true ;
}
2010-10-04 15:06:18 +02:00
WebDAVSource : : Databases WebDAVSource : : getDatabases ( )
{
Databases result ;
2013-07-30 10:19:31 +02:00
// do a scan if some kind of credentials were set
if ( m_contextSettings - > getAuthProvider ( ) - > wasConfigured ( ) ) {
2011-10-10 13:36:04 +02:00
findCollections ( boost : : bind ( storeCollection ,
boost : : ref ( result ) ,
_1 , _2 ) ) ;
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> " ) ) ;
}
2010-10-04 15:06:18 +02:00
return result ;
}
2011-01-25 19:29:33 +01:00
void WebDAVSource : : getSynthesisInfo ( SynthesisInfo & info ,
XMLConfigFragments & fragments )
{
TrackingSyncSource : : getSynthesisInfo ( info , fragments ) ;
2012-07-10 09:29:34 +02:00
// 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 " ;
}
2011-01-25 19:29:33 +01:00
// TODO: instead of identifying the peer based on the
// session URI, use some information gathered about
// it during open()
if ( m_session ) {
string host = m_session - > getURI ( ) . m_host ;
if ( host . find ( " google " ) ! = host . npos ) {
info . m_backendRule = " GOOGLE " ;
fragments . m_remoterules [ " GOOGLE " ] =
" <remoterule name='GOOGLE'> \n "
" <deviceid>none</deviceid> \n "
2011-04-20 12:50:02 +02:00
// enable extensions, just in case (not relevant yet for calendar)
" <include rule= \" ALL \" /> \n "
2011-01-25 19:29:33 +01:00
" </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 "
2011-01-27 14:02:10 +01:00
// BDAY is ignored if it has the compact 19991231 instead of
// 1999-12-31, although both are valid.
" <include rule='EXTENDED-DATE-FORMAT'/> \n "
2011-04-20 12:50:02 +02:00
// Yahoo accepts extensions, so send them. However, it
// doesn't seem to store the X-EVOLUTION-UI-SLOT parameter
// extensions.
" <include rule= \" ALL \" /> \n "
" </remoterule> " ;
} else {
// fallback: generic CalDAV/CardDAV, with all properties
// enabled (for example, X-EVOLUTION-UI-SLOT)
info . m_backendRule = " WEBDAV " ;
fragments . m_remoterules [ " WEBDAV " ] =
" <remoterule name='WEBDAV'> \n "
" <deviceid>none</deviceid> \n "
" <include rule= \" ALL \" /> \n "
2011-01-25 19:29:33 +01:00
" </remoterule> " ;
}
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( getDisplayName ( ) , " using data conversion rules for '%s' " , info . m_backendRule . c_str ( ) ) ;
2011-01-25 19:29:33 +01:00
}
}
2011-06-17 14:48:43 +02:00
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 ( ) ;
}
}
2014-04-01 16:37:31 +02:00
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 ( ) ) ;
}
2011-06-22 17:38:32 +02:00
/**
* 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 ( )
{
2011-11-07 22:06:07 +01:00
if ( m_contextSettings & & m_contextSettings - > noCTag ( ) ) {
// return empty string to disable usage of CTag
return " " ;
}
2012-06-29 08:41:31 +02:00
contactServer ( ) ;
2011-06-22 17:38:32 +02:00
Timespec deadline = createDeadline ( ) ;
2014-04-01 16:33:54 +02:00
Props_t davProps ;
2011-06-22 17:38:32 +02:00
Neon : : Session : : PropfindPropCallback_t callback =
boost : : bind ( & WebDAVSource : : openPropCallback ,
2014-04-01 16:33:54 +02:00
this , boost : : ref ( davProps ) , _1 , _2 , _3 , _4 ) ;
2014-04-01 16:35:38 +02:00
SE_LOG_DEBUG ( NULL , " read ctag of %s " , m_calendar . m_path . c_str ( ) ) ;
2011-06-22 17:38:32 +02:00
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".
2014-04-01 16:33:54 +02:00
string ctag = davProps [ m_calendar . m_path ] [ " http://calendarserver.org/ns/:getctag " ] ;
2011-06-22 17:38:32 +02:00
return ctag ;
}
2011-06-17 14:48:43 +02:00
2010-10-06 11:21:11 +02:00
static const ne_propname getetag [ ] = {
{ " DAV: " , " getetag " } ,
2010-11-25 10:08:07 +01:00
{ " DAV: " , " resourcetype " } ,
2010-10-06 11:21:11 +02:00
{ NULL , NULL }
} ;
2010-10-04 15:06:18 +02:00
void WebDAVSource : : listAllItems ( RevisionMap_t & revisions )
{
2012-11-15 02:49:02 +01:00
contactServer ( ) ;
2012-06-12 17:41:46 +02:00
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 ) ,
2012-07-02 14:50:57 +02:00
_1 , _2 , & data ) ) ;
2012-06-12 17:41:46 +02:00
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 ;
}
}
2010-10-05 16:02:58 +02:00
}
}
2012-07-02 14:50:57 +02:00
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 " " ;
}
2010-10-05 16:02:58 +02:00
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 "
} ;
2010-11-25 11:28:56 +01:00
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 ;
}
2010-11-09 17:16:43 +01:00
std : : string uid = path2luid ( uri . m_path ) ;
2010-10-05 16:02:58 +02:00
if ( uid . empty ( ) ) {
2010-11-25 11:28:56 +01:00
// skip collection itself (should have been detected as collection already)
2010-10-05 16:02:58 +02:00
return ;
}
2010-11-25 11:28:56 +01:00
const char * etag = ne_propset_value ( results , & prop ) ;
2010-10-05 16:02:58 +02:00
if ( etag ) {
2010-10-06 11:21:11 +02:00
std : : string rev = ETag2Rev ( etag ) ;
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " item %s = rev %s " ,
2010-10-06 11:21:11 +02:00
uid . c_str ( ) , rev . c_str ( ) ) ;
revisions [ uid ] = rev ;
2010-10-05 16:02:58 +02:00
} else {
failed = true ;
2013-04-08 19:17:36 +02:00
SE_LOG_ERROR ( NULL ,
2010-10-05 16:02:58 +02:00
" %s: %s " ,
uri . toURL ( ) . c_str ( ) ,
Neon : : Status2String ( ne_propset_status ( results , & prop ) ) . c_str ( ) ) ;
}
2010-10-04 15:06:18 +02:00
}
2012-06-12 17:41:46 +02:00
int WebDAVSource : : checkItem ( RevisionMap_t & revisions ,
const std : : string & href ,
const std : : string & etag ,
2012-07-02 14:50:57 +02:00
std : : string * data )
2012-06-12 17:41:46 +02:00
{
// 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.
2012-07-02 14:50:57 +02:00
if ( data & & data - > empty ( ) ) {
2012-06-12 17:41:46 +02:00
return 0 ;
}
// No need to parse, user content cannot start at start of line in
// iCalendar 2.0.
2012-07-02 14:50:57 +02:00
if ( ! data | |
data - > find ( " \n BEGIN: " + getContent ( ) ) ! = data - > npos ) {
2012-06-12 17:41:46 +02:00
std : : string davLUID = path2luid ( Neon : : URI : : parse ( href ) . m_path ) ;
std : : string rev = ETag2Rev ( etag ) ;
revisions [ davLUID ] = rev ;
}
// reset data for next item
2012-07-02 14:50:57 +02:00
if ( data ) {
data - > clear ( ) ;
}
2012-06-12 17:41:46 +02:00
return 0 ;
}
2010-10-06 11:21:11 +02:00
std : : string WebDAVSource : : path2luid ( const std : : string & path )
{
2012-02-20 17:04:42 +01:00
// 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 ( ) ) ) ;
2010-10-06 11:21:11 +02:00
} else {
2012-02-20 17:04:42 +01:00
// keep full, absolute path as LUID
2010-10-06 11:21:11 +02:00
}
2012-02-20 17:04:42 +01:00
return res ;
2010-10-06 11:21:11 +02:00
}
std : : string WebDAVSource : : luid2path ( const std : : string & luid )
{
2010-11-09 17:16:43 +01:00
if ( boost : : starts_with ( luid , " / " ) ) {
return luid ;
} else {
return m_calendar . resolve ( Neon : : URI : : escape ( luid ) ) . m_path ;
}
2010-10-06 11:21:11 +02:00
}
2010-10-04 15:06:18 +02:00
void WebDAVSource : : readItem ( const string & uid , std : : string & item , bool raw )
{
2011-04-14 10:10:17 +02:00
Timespec deadline = createDeadline ( ) ;
2011-04-15 13:30:51 +02:00
m_session - > startOperation ( " GET " , deadline ) ;
2011-04-14 10:10:17 +02:00
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 ( ) ) ;
2012-06-12 17:09:07 +02:00
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 ;
2011-04-14 10:10:17 +02:00
}
}
2010-10-04 15:06:18 +02:00
}
TrackingSyncSource : : InsertItemResult WebDAVSource : : insertItem ( const string & uid , const std : : string & item , bool raw )
{
2010-10-06 11:21:11 +02:00
std : : string new_uid ;
std : : string rev ;
2011-09-13 10:58:49 +02:00
InsertItemResultState state = ITEM_OKAY ;
2010-10-06 11:21:11 +02:00
2014-04-01 16:37:31 +02:00
// 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 ;
}
}
2011-04-14 10:10:17 +02:00
Timespec deadline = createDeadline ( ) ; // no resending if left empty
2014-04-01 16:37:31 +02:00
m_session - > startOperation ( operation , deadline ) ;
2010-10-06 11:21:11 +02:00
std : : string result ;
2011-04-14 10:10:17 +02:00
int counter = 0 ;
retry :
counter + + ;
result = " " ;
2010-10-06 11:21:11 +02:00
if ( uid . empty ( ) ) {
2010-11-25 10:08:07 +01:00
// Pick a resource name (done by derived classes, by default random),
2010-10-06 11:21:11 +02:00
// catch unexpected conflicts via If-None-Match: *.
2010-11-25 10:08:07 +01:00
std : : string buffer ;
const std : : string * data = createResourceName ( item , buffer , new_uid ) ;
2014-04-01 16:37:31 +02:00
Neon : : Request req ( * m_session , operation ,
operation = = postOperation ? m_postPath : luid2path ( new_uid ) ,
2010-11-25 10:08:07 +01:00
* data , result ) ;
2011-04-14 10:10:17 +02:00
// 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
2014-04-01 16:37:31 +02:00
// that in the first attempt. Only relevant for PUT.
if ( operation ! = postOperation & & counter = = 1 ) {
2011-04-14 10:10:17 +02:00
req . addHeader ( " If-None-Match " , " * " ) ;
}
2010-11-25 10:08:07 +01:00
req . addHeader ( " Content-Type " , contentType ( ) . c_str ( ) ) ;
2014-04-01 16:37:31 +02:00
static const std : : set < int > expected = boost : : assign : : list_of ( 412 ) ( 403 ) ;
2012-07-02 14:50:57 +02:00
if ( ! req . run ( & expected ) ) {
2011-04-14 10:10:17 +02:00
goto retry ;
}
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " add item status: %s " ,
2010-10-06 11:21:11 +02:00
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 ;
2014-04-01 16:37:31 +02:00
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 ;
2012-07-02 14:50:57 +02:00
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 ;
}
2010-10-06 11:21:11 +02:00
default :
2010-10-29 17:25:20 +02:00
SE_THROW_EXCEPTION_STATUS ( TransportStatusException ,
std : : string ( " unexpected status for insert: " ) +
Neon : : Status2String ( req . getStatus ( ) ) ,
SyncMLStatus ( req . getStatus ( ) - > code ) ) ;
2010-10-06 11:21:11 +02:00
break ;
}
rev = getETag ( req ) ;
std : : string real_luid = getLUID ( req ) ;
if ( ! real_luid . empty ( ) ) {
2010-10-08 15:13:58 +02:00
// 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...
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " new item mapped to %s " , real_luid . c_str ( ) ) ;
2010-10-06 11:21:11 +02:00
new_uid = real_luid ;
2010-10-08 15:13:58 +02:00
// TODO: find a better way of detecting unexpected updates.
2011-09-13 10:58:49 +02:00
// state = ...
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.
2013-09-17 09:35:00 +02:00
//
// 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.
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 ) ,
2011-04-14 10:10:17 +02:00
boost : : ref ( failed ) ) ,
deadline ) ;
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 ) {
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " %s mapped to %s by peer " ,
2010-11-25 10:08:07 +01:00
new_uid . c_str ( ) ,
revisions . begin ( ) - > first . c_str ( ) ) ;
new_uid = revisions . begin ( ) - > first ;
2013-09-17 09:35:00 +02:00
// This would have to be uncommented for Yahoo.
// state = ITEM_REPLACED;
2010-11-25 10:08:07 +01:00
}
2010-10-06 11:21:11 +02:00
}
} else {
new_uid = uid ;
2010-11-25 10:08:07 +01:00
std : : string buffer ;
const std : : string * data = setResourceName ( item , buffer , new_uid ) ;
2010-10-06 11:21:11 +02:00
Neon : : Request req ( * m_session , " PUT " , luid2path ( new_uid ) ,
2010-11-25 10:08:07 +01:00
* data , result ) ;
2011-04-14 10:10:17 +02:00
// See above for discussion of idempotent and PUT.
// req.setFlag(NE_REQFLAG_IDEMPOTENT, 0);
2010-11-25 10:08:07 +01:00
req . addHeader ( " Content-Type " , contentType ( ) ) ;
2010-10-06 11:21:11 +02:00
// 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);
2011-04-15 13:30:51 +02:00
if ( ! req . run ( ) ) {
2011-04-14 10:10:17 +02:00
goto retry ;
}
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " update item status: %s " ,
2010-10-06 11:21:11 +02:00
Neon : : Status2String ( req . getStatus ( ) ) . c_str ( ) ) ;
switch ( req . getStatusCode ( ) ) {
case 204 :
// the expected outcome, as we were asking for an overwrite
break ;
case 201 :
2010-11-04 15:59:52 +01:00
// Huh? Shouldn't happen, but Google sometimes reports it
// even when updating an item. Accept it.
// SE_THROW("unexpected creation instead of update");
2010-10-06 11:21:11 +02:00
break ;
default :
2010-10-29 17:25:20 +02:00
SE_THROW_EXCEPTION_STATUS ( TransportStatusException ,
std : : string ( " unexpected status for update: " ) +
Neon : : Status2String ( req . getStatus ( ) ) ,
SyncMLStatus ( req . getStatus ( ) - > code ) ) ;
2010-10-06 11:21:11 +02:00
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 ) ,
2011-04-14 10:10:17 +02:00
boost : : ref ( failed ) ) ,
deadline ) ;
2010-10-06 11:21:11 +02:00
rev = revisions [ new_uid ] ;
if ( failed | | rev . empty ( ) ) {
SE_THROW ( " could not retrieve ETag " ) ;
}
}
2011-09-13 10:58:49 +02:00
return InsertItemResult ( new_uid , rev , state ) ;
2010-10-04 15:06:18 +02:00
}
2010-10-06 11:21:11 +02:00
std : : string WebDAVSource : : ETag2Rev ( const std : : string & etag )
{
std : : string res = etag ;
if ( boost : : starts_with ( res , " W/ " ) ) {
res . erase ( 0 , 2 ) ;
}
2011-10-04 16:05:59 +02:00
if ( res . size ( ) > = 2 & &
res [ 0 ] = = ' " ' & &
res [ res . size ( ) - 1 ] = = ' " ' ) {
2010-10-06 11:21:11 +02:00
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 ) ;
}
}
2010-10-04 15:06:18 +02:00
2014-04-02 11:05:26 +02:00
bool WebDAVSource : : isLeafCollection ( const StringMap & props ) const
2011-10-04 15:52:16 +02:00
{
// 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 ;
}
2011-04-14 10:10:17 +02:00
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 ( ) ;
}
}
2010-10-04 15:06:18 +02:00
void WebDAVSource : : removeItem ( const string & uid )
{
2011-04-14 10:10:17 +02:00
Timespec deadline = createDeadline ( ) ;
2011-04-15 13:30:51 +02:00
m_session - > startOperation ( " DELETE " , deadline ) ;
2010-10-06 11:43:39 +02:00
std : : string item , result ;
2011-04-14 10:10:17 +02:00
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);
2012-07-02 14:07:51 +02:00
static const std : : set < int > expected = boost : : assign : : list_of ( 412 ) ;
if ( req - > run ( & expected ) ) {
break ;
2011-04-14 10:10:17 +02:00
}
}
2013-04-08 19:17:36 +02:00
SE_LOG_DEBUG ( NULL , " remove item status: %s " ,
2011-04-14 10:10:17 +02:00
Neon : : Status2String ( req - > getStatus ( ) ) . c_str ( ) ) ;
switch ( req - > getStatusCode ( ) ) {
2010-10-06 11:43:39 +02:00
case 204 :
// the expected outcome
break ;
2012-06-12 17:15:21 +02:00
case 200 :
// reported by Radicale, also okay
break ;
2012-07-02 14:07:51 +02:00
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 ;
2010-10-06 11:43:39 +02:00
default :
2010-10-29 17:25:20 +02:00
SE_THROW_EXCEPTION_STATUS ( TransportStatusException ,
std : : string ( " unexpected status for removal: " ) +
2011-04-14 10:10:17 +02:00
Neon : : Status2String ( req - > getStatus ( ) ) ,
SyncMLStatus ( req - > getStatus ( ) - > code ) ) ;
2010-10-06 11:43:39 +02:00
break ;
}
2010-10-04 15:06:18 +02:00
}
2011-04-20 18:13:16 +02:00
# endif /* ENABLE_DAV */
2010-10-04 15:06:18 +02:00
SE_END_CXX
# ifdef ENABLE_MODULES
# include "WebDAVSourceRegister.cpp"
# endif