/* * Copyright (c) 2000-2004 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include "webdavd.h" #include #include #include #include #include #include #include #include #include "webdav_parse.h" #include "webdav_requestqueue.h" #include "webdav_authcache.h" #include "webdav_network.h" #include "EncodedSourceID.h" /******************************************************************************/ /* * BODY_BUFFER_SIZE is the initial size of the buffer used to read an * HTTP entity body. The largest bodies are typically the XML data * returned by the PROPFIND method for a large collection (directory). * 64K is large enough to handle directories with 100-150 items. */ #define BODY_BUFFER_SIZE 0x10000 /* 64K */ #define kSSLClientPropTLSServerCertificateChain CFSTR("TLSServerCertificateChain") /* array[data] */ #define kSSLClientPropTLSTrustClientStatus CFSTR("TLSTrustClientStatus") /* CFNumberRef of kCFNumberSInt32Type (errSSLxxxx) */ #define kSSLClientPropTLSServerHostName CFSTR("TLSServerHostName") /* CFString */ struct HeaderFieldValue { CFStringRef headerField; CFStringRef value; }; /******************************************************************************/ static CFURLRef baseURL = NULL; /* the base URL for this mount */ static CFStringRef userAgentHeaderValue = NULL; /* The User-Agent request-header value */ static CFIndex first_read_len = 4096; /* bytes. Amount to download at open so first read at offset 0 doesn't stall */ static CFStringRef X_Source_Id_HeaderValue = NULL; /* the X-Source-Id header value, or NULL if not iDisk */ static SCDynamicStoreRef gProxyStore; /******************************************************************************/ static pthread_mutex_t gNetworkGlobals_lock; /* these variables are protected by gNetworkGlobals_lock */ static CFDictionaryRef gProxyDict = NULL; static int gHttpProxyEnabled; static char gHttpProxyServer[MAXHOSTNAMELEN]; static int gHttpProxyPort; static int gHttpsProxyEnabled; static char gHttpsProxyServer[MAXHOSTNAMELEN]; static int gHttpsProxyPort; static CFMutableDictionaryRef gSSLPropertiesDict = NULL; static struct ReadStreamRec gReadStreams[WEBDAV_REQUEST_THREADS + 1]; /* one for every request thread plus one for the pulse thread */ /******************************************************************************/ static int network_stat( uid_t uid, /* -> uid of the user making the request */ CFURLRef urlRef, /* -> url to the resource */ struct stat *statbuf); /* <- stat information is returned in this buffer */ static int network_dir_is_empty( uid_t uid, /* -> uid of the user making the request */ CFURLRef urlRef); /* -> url to check */ static time_t DateStringToTime( /* <- time_t value; -1 if error */ CFStringRef str); /* -> CFString to parse */ static CFStringRef CFStringCreateRFC2616DateStringWithTimeT( /* <- CFString containing RFC 1123 date, NULL if error */ time_t clock); /* -> time_t value */ /*****************************************************************************/ /* * DateBytesToTime parses the RFC 850, RFC 1123, and asctime formatted * date/time bytes and returns time_t. If the parse fails, this function * returns -1. */ time_t DateBytesToTime( /* <- time_t value */ const UInt8 *bytes, /* -> pointer to bytes to parse */ CFIndex length) /* -> number of bytes to parse */ { const UInt8 *finish; CFGregorianDate gdate; struct tm tm_temp; time_t clock; /* parse the RFC 850, RFC 1123, and asctime formatted date/time CFString to get the Gregorian date */ finish = _CFGregorianDateCreateWithBytes(kCFAllocatorDefault, bytes, length, &gdate, NULL); require_action(finish != bytes, _CFGregorianDateCreateWithBytes, clock = -1); /* copy the Gregorian date into struct tm */ memset(&tm_temp, 0, sizeof(struct tm)); tm_temp.tm_sec = gdate.second; tm_temp.tm_min = gdate.minute; tm_temp.tm_hour = gdate.hour; tm_temp.tm_mday = gdate.day; tm_temp.tm_mon = gdate.month - 1; tm_temp.tm_year = gdate.year - 1900; clock = timegm(&tm_temp); _CFGregorianDateCreateWithBytes: return ( clock ); } /*****************************************************************************/ /* * DateStringToTime parses the RFC 850, RFC 1123, and asctime formatted * date/time CFString and returns time_t. If the parse fails, this function * returns -1. */ static time_t DateStringToTime( /* <- time_t value; -1 if error */ CFStringRef str) /* -> CFString to parse */ { CFIndex count; CFGregorianDate gdate; struct tm tm_temp; time_t clock; /* parse the RFC 850, RFC 1123, and asctime formatted date/time CFString to get the Gregorian date */ count = _CFGregorianDateCreateWithString(kCFAllocatorDefault, str, &gdate, NULL); require_action(count != 0, _CFGregorianDateCreateWithString, clock = -1); /* copy the Gregorian date into struct tm */ memset(&tm_temp, 0, sizeof(struct tm)); tm_temp.tm_sec = gdate.second; tm_temp.tm_min = gdate.minute; tm_temp.tm_hour = gdate.hour; tm_temp.tm_mday = gdate.day; tm_temp.tm_mon = gdate.month - 1; tm_temp.tm_year = gdate.year - 1900; clock = timegm(&tm_temp); _CFGregorianDateCreateWithString: return ( clock ); } /*****************************************************************************/ /* * CFStringCreateRFC2616DateStringWithTimeT creates a RFC 1123 date CFString from * a time_t time. */ static CFStringRef CFStringCreateRFC2616DateStringWithTimeT( /* <- CFString containing RFC 1123 date, NULL if error */ time_t clock) /* -> time_t value */ { struct tm *tm_temp; CFGregorianDate gdate; CFStringRef result; require_action_quiet(clock != -1, InvalidClock, result = NULL); /* get struct tm (in GMT) from time_t */ tm_temp = gmtime(&clock); /* copy the struct tm into CFGregorianDate */ gdate.second = tm_temp->tm_sec; gdate.minute = tm_temp->tm_min; gdate.hour = tm_temp->tm_hour; gdate.day = tm_temp->tm_mday; gdate.month = tm_temp->tm_mon + 1; gdate.year = tm_temp->tm_year + 1900; /* get a CFStringRef with the RFC 1123 date string */ result = _CFStringCreateRFC2616DateStringWithGregorianDate(kCFAllocatorDefault, &gdate, NULL); InvalidClock: return ( result ); } /*****************************************************************************/ /* * SkipCodedURL finds the end of a Coded-URL using the rules * (rfc 2518, section 9.4 and rfc 2396): * * Coded-URL = "<" absoluteURI ">" * * On input, the bytes parameter points to the character AFTER the initial * '<' character. The function result is a pointer to the '>' character that * terminates the Coded-URL or the end of the C string. */ static const char * SkipCodedURL(const char *bytes) { /* the end of the string or Coded-URL? */ while ( (*bytes != '\0') && (*bytes != '>') ) { /* skip character */ ++bytes; } return ( bytes ); } /*****************************************************************************/ /* * SkipToken finds the end of a token using the rules (rfc 2616, section 2.2): * * token = 1* * CTL = * separators = "(" | ")" | "<" | ">" | "@" * | "," | ";" | ":" | "\" | <"> * | "/" | "[" | "]" | "?" | "=" * | "{" | "}" | SP | HT * * The function result is a pointer to the first non token character or the * end of the C string. */ static const char * SkipToken(const char *bytes) { while ( *bytes != '\0' ) { /* CTL - US-ASCII control character (octets 0 - 31) */ if ( (unsigned char)*bytes <= 31 ) { /* not a token character - done */ goto Done; } else { switch ( *bytes ) { /* CTL - DEL (127) */ case '\x7f': /* separators */ case '(': case ')': case '<': case '>': case '@': case ',': case ';': case ':': case '\\': case '\"': case '/': case '[': case ']': case '\?': case '=': case '{': case '}': case ' ': case '\t': /* not a token character - done */ goto Done; break; default: /* skip token characters */ ++bytes; break; } } } Done: return (bytes); } /*****************************************************************************/ /* * SkipLWS finds the end of a run of LWS using the rule * (rfc 2616, section 2.2): * * LWS = [CRLF] 1*( SP | HT ) * * The function result is a pointer to the first non LWS character or the end * of the C string. */ static const char * SkipLWS(const char *bytes) { while ( *bytes != '\0' ) { if ( (*bytes == ' ') || (*bytes == '\t') ) { /* skip SP and HT characters */ ++bytes; continue; } /* * skip CRLF only if followed by SP or HT (in which case the SP * or HT can be skipped, too) */ else if ( *bytes == '\x0d' ) /* CR? */ { /* LF? */ if ( bytes[1] == '\x0a' ) { /* SP or HT? */ if ( (bytes[2] == ' ') || (bytes[2] == '\t') ) { /* skip CRLF followed by SP or HT */ bytes += 3; continue; } } } /* found the end of the LWS run */ break; } return ( bytes ); } /*****************************************************************************/ static int set_global_stream_properties(CFReadStreamRef readStreamRef) { int error, mutexerror; error = 0; mutexerror = pthread_mutex_lock(&gNetworkGlobals_lock); require_noerr_action(mutexerror, pthread_mutex_lock, error = mutexerror; webdav_kill(-1)); error = (CFReadStreamSetProperty(readStreamRef, kCFStreamPropertyHTTPProxy, gProxyDict) == TRUE) ? 0 : 1; mutexerror = pthread_mutex_unlock(&gNetworkGlobals_lock); require_noerr_action(mutexerror, pthread_mutex_unlock, error = mutexerror; webdav_kill(-1)); pthread_mutex_unlock: pthread_mutex_lock: return ( error ); } /******************************************************************************/ int network_get_proxy_settings( int *httpProxyEnabled, /* <- true if HTTP proxy is enabled */ char **httpProxyServer, /* <- HTTP proxy server name */ int *httpProxyPort, /* <- HTTP proxy port number */ int *httpsProxyEnabled, /* <- true if HTTPS proxy is enabled */ char **httpsProxyServer, /* <- HTTPS proxy server name */ int* httpsProxyPort) /* <- HTTPS proxy port number */ { int error, mutexerror; error = 0; mutexerror = pthread_mutex_lock(&gNetworkGlobals_lock); require_noerr_action(mutexerror, pthread_mutex_lock, error = mutexerror; webdav_kill(-1)); *httpProxyServer = malloc(MAXHOSTNAMELEN); require_action(*httpProxyServer != NULL, malloc_httpProxyServer, error = ENOMEM); *httpsProxyServer = malloc(MAXHOSTNAMELEN); require_action(*httpsProxyServer != NULL, malloc_httpsProxyServer, free(*httpProxyServer); error = ENOMEM); *httpProxyEnabled = gHttpProxyEnabled; memcpy(*httpProxyServer, gHttpProxyServer, MAXHOSTNAMELEN); *httpProxyPort = gHttpProxyPort; *httpsProxyEnabled = gHttpsProxyEnabled; memcpy(*httpsProxyServer, gHttpsProxyServer, MAXHOSTNAMELEN); *httpsProxyPort = gHttpsProxyPort; malloc_httpsProxyServer: malloc_httpProxyServer: mutexerror = pthread_mutex_unlock(&gNetworkGlobals_lock); require_noerr_action(mutexerror, pthread_mutex_unlock, error = mutexerror; webdav_kill(-1)); pthread_mutex_unlock: pthread_mutex_lock: return ( error ); } /******************************************************************************/ int network_update_proxy(void *arg) { #pragma unused(arg) int error, mutexerror; error = 0; mutexerror = pthread_mutex_lock(&gNetworkGlobals_lock); require_noerr_action(mutexerror, pthread_mutex_lock, error = mutexerror; webdav_kill(-1)); /* release the old proxies dictionary */ if ( gProxyDict ) { CFRelease(gProxyDict); } /* slam everything to default disabled state in case something fails */ gHttpProxyEnabled = 0; gHttpProxyServer[0] = '\0'; gHttpProxyPort = 0; gHttpsProxyEnabled = 0; gHttpsProxyServer[0] = '\0'; gHttpsProxyPort = 0; /* get the current internet proxy dictionary */ gProxyDict = SCDynamicStoreCopyProxies(gProxyStore); if ( gProxyDict != NULL ) { CFNumberRef cf_enabled; int enabled; CFStringRef cf_host; CFNumberRef cf_port; /* * take care of HTTP proxies */ /* are HTTP proxies enabled? */ cf_enabled = CFDictionaryGetValue(gProxyDict, kSCPropNetProxiesHTTPEnable); if ( (cf_enabled != NULL) && CFNumberGetValue(cf_enabled, kCFNumberIntType, &enabled) && enabled ) { /* get the HTTP proxy */ cf_host = CFDictionaryGetValue(gProxyDict, kSCPropNetProxiesHTTPProxy); if ( (cf_host != NULL) && CFStringGetCString(cf_host, gHttpProxyServer, sizeof(gHttpProxyServer), kCFStringEncodingUTF8) ) { /* get the HTTP proxy port */ cf_port = CFDictionaryGetValue(gProxyDict, kSCPropNetProxiesHTTPPort); if ( (cf_port != NULL) && CFNumberGetValue(cf_port, kCFNumberIntType, &gHttpProxyPort) ) { if ( gHttpProxyPort == 0 ) { /* no port specified so use the default HTTP port */ gHttpProxyPort = kHttpDefaultPort; } gHttpProxyEnabled = 1; } } } /* * take care of HTTPS proxies */ /* are HTTP proxies enabled? */ cf_enabled = CFDictionaryGetValue(gProxyDict, kSCPropNetProxiesHTTPSEnable); if ( (cf_enabled != NULL) && CFNumberGetValue(cf_enabled, kCFNumberIntType, &enabled) && enabled ) { /* get the HTTP proxy */ cf_host = CFDictionaryGetValue(gProxyDict, kSCPropNetProxiesHTTPSProxy); if ( (cf_host != NULL) && CFStringGetCString(cf_host, gHttpsProxyServer, sizeof(gHttpsProxyServer), kCFStringEncodingUTF8) ) { /* get the HTTP proxy port */ cf_port = CFDictionaryGetValue(gProxyDict, kSCPropNetProxiesHTTPSPort); if ( (cf_port != NULL) && CFNumberGetValue(cf_port, kCFNumberIntType, &gHttpsProxyPort) ) { if ( gHttpsProxyPort == 0 ) { /* no port specified so use the default HTTPS port */ gHttpsProxyPort = kHttpsDefaultPort; } gHttpsProxyEnabled = 1; } } } } mutexerror = pthread_mutex_unlock(&gNetworkGlobals_lock); require_noerr_action(mutexerror, pthread_mutex_unlock, error = mutexerror; webdav_kill(-1)); /* remove proxy authentications */ error = authcache_proxy_invalidate(); pthread_mutex_unlock: pthread_mutex_lock: return ( error ); } /*****************************************************************************/ /* The InitUserAgentHeaderValue initializes the string userAgentHeaderValue which is sent with every request to the server. The User-Agent request-header field is defined in RFC 2616, section 14.43 as: User-Agent = "User-Agent" ":" 1*( product | comment ) section 3.8 defines product as: product = token ["/" product-version] product-version = token section 2.2 defines comment as: comment = "(" *( ctext | quoted-pair | comment ) ")" ctext = quoted-pair = "\" CHAR We want our User-Agent request-header field to look something like: "User-Agent: WebDAVFS/1.1 (0110800000) Darwin/5.3 (Power Macintosh)" where: 1.1 = the CFBundleShortVersionString from webdav.fs bundle 0110800000 = webdav.fs bundle's numeric version Darwin = CTL_KERN/KERN_OSTYPE 5.3 = CTL_KERN/KERN_OSRELEASE Power Macintosh = CTL_HW/HW_MACHINE webdav.fs bundle is located at: /System/Library/Filesystems/webdav.fs If the data from webdav.fs bundle could not be obtained, then we'll fall back to the generic User-Agent request-header string WebDAV FS used to use. IMPORTANT: The user-agent header MUST start with the product token "WebDAVFS" because there are WebDAV servers that special case this client. */ static int InitUserAgentHeaderValue(int add_mirror_comment) { char buf[128]; int mib[2]; char ostype[128]; char osrelease[128]; char machine[128]; size_t len; CFURLRef url; CFBundleRef bundle; CFDictionaryRef dict; CFStringRef shortVersion; CFIndex shortVersionLen; char *webdavfsVersionStr; UInt32 webdavfsVersion; int result; /* Get the ostype, osrelease, and machine strings using sysctl*/ mib[0] = CTL_KERN; mib[1] = KERN_OSTYPE; len = sizeof ostype; if (sysctl(mib, 2, ostype, &len, 0, 0) < 0) { ostype[0] = '\0'; } mib[1] = KERN_OSRELEASE; len = sizeof osrelease; if (sysctl(mib, 2, osrelease, &len, 0, 0) < 0) { osrelease[0] = '\0'; } mib[0] = CTL_HW; mib[1] = HW_MACHINE; len = sizeof machine; if (sysctl(mib, 2, machine, &len, 0, 0) < 0) { machine[0] = '\0'; } /* We don't have it yet */ webdavfsVersionStr = NULL; webdavfsVersion = 0x010080000; /* 1.0 final */ /* Create the CFURLRef to the webdav.fs bundle's version.plist */ url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, CFSTR("/System/Library/Filesystems/webdav.fs"), kCFURLPOSIXPathStyle, true); if ( url != NULL ) { /* Create the bundle */ bundle = CFBundleCreate(kCFAllocatorDefault, url); if ( bundle != NULL ) { /* Get the bundle's numeric version */ webdavfsVersion = CFBundleGetVersionNumber(bundle); /* Get the Info dictionary */ dict = CFBundleGetInfoDictionary(bundle); if ( dict != NULL ) { /* Get the CFBundleShortVersionString (display string) */ shortVersion = CFDictionaryGetValue(dict, CFSTR("CFBundleShortVersionString")); if ( shortVersion != NULL ) { /* Get the bundleVersionStr */ shortVersionLen = CFStringGetLength(shortVersion) + 1; webdavfsVersionStr = malloc((size_t)shortVersionLen); if ( webdavfsVersionStr != NULL ) { /* Convert it to a C string */ if ( !CFStringGetCString(shortVersion, webdavfsVersionStr, shortVersionLen, kCFStringEncodingUTF8) ) { /* If we can't get it, free the memory */ free(webdavfsVersionStr); webdavfsVersionStr = NULL; } } } } /* release created bundle */ CFRelease(bundle); } /* release created url */ CFRelease(url); } if ( webdavfsVersionStr != NULL ) { /* if everything worked, use the new format User-Agent request-header string */ snprintf(buf, sizeof(buf), "WebDAVFS/%s (%.8lx) %s%s/%s (%s)", webdavfsVersionStr, webdavfsVersion, (add_mirror_comment ? "(mirrored) " : ""), ostype, osrelease, machine); free(webdavfsVersionStr); } else { /* create the generic User-Agent request-header string WebDAV FS used to use */ snprintf(buf, sizeof(buf), "WebDAVFS/1.3 %s/%s (%s)", ostype, osrelease, machine); } userAgentHeaderValue = CFStringCreateWithCString(kCFAllocatorDefault, buf, kCFStringEncodingUTF8); require_action(userAgentHeaderValue != NULL, CFStringCreateWithCString, result = ENOMEM); result = 0; CFStringCreateWithCString: return ( result ); } /*****************************************************************************/ /* * The get_first_read_len function sets the global * first_read_len. It is set to the system's page size so that if the * first read after an open starts at offset 0, that page will already be * downloaded into the cache file. */ static void get_first_read_len(void) { int mib[2]; size_t len; int result; int pagesize; /* get the hardware page size */ mib[0] = CTL_HW; mib[1] = HW_PAGESIZE; len = sizeof(int); result = sysctl(mib, 2, &pagesize, &len, 0, 0); if ( result < 0 ) { /* set first_read_len to PowerPC page size */ first_read_len = 4096; } else { first_read_len = pagesize; } } /******************************************************************************/ static void InitXSourceIdHeaderValue(void) { CFStringRef hostName; char encodedIdBuffer[32]; X_Source_Id_HeaderValue = NULL; /* get the host name */ hostName = CFURLCopyHostName(baseURL); require_quiet(hostName != NULL, CFURLCopyHostName); /* is it "idisk.mac.com" - if not, we don't want it */ require_quiet(CFStringCompare(hostName, CFSTR("idisk.mac.com"), kCFCompareCaseInsensitive) == kCFCompareEqualTo, NotIdisk); /* created the X-Source-Id string */ require_quiet(GetEncodedSourceID(encodedIdBuffer), GetEncodedSourceID); /* and make it a CFString */ X_Source_Id_HeaderValue = CFStringCreateWithCString(kCFAllocatorDefault, encodedIdBuffer, kCFStringEncodingUTF8); GetEncodedSourceID: NotIdisk: CFRelease(hostName); CFURLCopyHostName: return; } /******************************************************************************/ int network_init(const UInt8 *uri, CFIndex uriLength, int *store_notify_fd, int add_mirror_comment) { int error; pthread_mutexattr_t mutexattr; CFStringRef notification_string; CFArrayRef keys; int index; gProxyDict = NULL; error = 0; /* set up the lock on the queues */ error = pthread_mutexattr_init(&mutexattr); require_noerr(error, pthread_mutexattr_init); error = pthread_mutex_init(&gNetworkGlobals_lock, &mutexattr); require_noerr(error, pthread_mutex_init); /* create a dynnamic store */ gProxyStore = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("WebDAVFS"), NULL, NULL); require_action(gProxyStore != NULL, SCDynamicStoreCreate, error = ENOMEM); /* open a file descriptor to be notified on */ require_action(SCDynamicStoreNotifyFileDescriptor(gProxyStore, 0, store_notify_fd), SCDynamicStoreNotifyFileDescriptor, error = ENOMEM); /* create a keys for network proxy changes */ notification_string = SCDynamicStoreKeyCreateProxies(kCFAllocatorDefault); require_action(notification_string != NULL, SCDynamicStoreKeyCreateProxies, error = ENOMEM); keys = CFArrayCreate(kCFAllocatorDefault, (const void **)¬ification_string, 1, &kCFTypeArrayCallBacks); require_action(keys != NULL, CFArrayCreate, error = ENOMEM); CFRelease(notification_string); require_action(SCDynamicStoreSetNotificationKeys(gProxyStore, keys, NULL), SCDynamicStoreSetNotificationKeys, error = ENOMEM); CFRelease(keys); /* get the initial internet proxy settings */ error = network_update_proxy(NULL); require_noerr_quiet(error, network_update_proxy); /* create baseURL */ baseURL = CFURLCreateAbsoluteURLWithBytes(kCFAllocatorDefault, uri, uriLength, kCFStringEncodingUTF8, NULL, FALSE); require_action_string(baseURL != NULL, CFURLCreateAbsoluteURLWithBytes, error = ENOMEM, "name was not legal UTF8"); /* initialize first_read_len variable */ get_first_read_len(); /* init the X_Source_Id_HeaderValue string BEFORE InitUserAgentHeaderValue */ InitXSourceIdHeaderValue(); /* initialize userAgentHeaderValue */ error = InitUserAgentHeaderValue((X_Source_Id_HeaderValue != NULL) && add_mirror_comment); if ( error ) { /* not likely to fail, but just in case */ exit(error); } /* initialize the gReadStreams array */ for ( index = 0; index < (WEBDAV_REQUEST_THREADS + 1); ++index ) { gReadStreams[index].inUse = 0; /* not in use */ gReadStreams[index].readStreamRef = NULL; /* no stream */ gReadStreams[index].uniqueValue = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%d"), index); /* unique string */ } CFURLCreateAbsoluteURLWithBytes: network_update_proxy: SCDynamicStoreSetNotificationKeys: CFArrayCreate: SCDynamicStoreKeyCreateProxies: SCDynamicStoreNotifyFileDescriptor: SCDynamicStoreCreate: pthread_mutex_init: pthread_mutexattr_init: return ( error ); } /******************************************************************************/ /* * create_cfurl_from_node * * Creates a CFURL to the node if no name is provided, or to the node's named * child if a name is provided. * * The caller is responsible for releasing the CFURL returned. */ static CFURLRef create_cfurl_from_node( struct node_entry *node, char *name, size_t name_length) { CFURLRef tempUrlRef; CFURLRef urlRef; char *node_path; int error; urlRef = NULL; /* * Get the UT8 path (not percent escaped) from the root to the node (if any). * If the path is returned and it is to a directory, it will end with a slash. */ error = nodecache_get_path_from_node(node, &node_path); require_noerr_quiet(error, nodecache_get_path_from_node); /* append the name (if any) */ if ( name != NULL && name_length != 0 ) { strncat(node_path, name, name_length); } /* is there any relative path? */ if ( *node_path != '\0' ) { CFStringRef stringRef; CFStringRef escapedPathRef; /* convert the relative path to a CFString */ stringRef = CFStringCreateWithCString(kCFAllocatorDefault, node_path, kCFStringEncodingUTF8); require_string(stringRef != NULL, CFStringCreateWithCString, "name was not legal UTF8"); /* * Then percent escape everything that CFURLCreateStringByAddingPercentEscapes() * considers illegal URL characters plus the characters ";" and "?" which are * not legal pchar (see rfc2396) characters. */ escapedPathRef = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, stringRef, NULL, CFSTR(";?"), kCFStringEncodingUTF8); require(escapedPathRef != NULL, CFURLCreateStringByAddingPercentEscapes); /* create the relative URL */ urlRef = CFURLCreateWithString(kCFAllocatorDefault, escapedPathRef, baseURL); require(urlRef != NULL, CFURLCreateWithString); /* and then make an absolute copy of it */ tempUrlRef = urlRef; urlRef = CFURLCopyAbsoluteURL(tempUrlRef); CFRelease(tempUrlRef); CFURLCreateWithString: CFRelease(escapedPathRef); CFURLCreateStringByAddingPercentEscapes: CFRelease(stringRef); CFStringCreateWithCString: ; } else { /* no relative path -- use the base URL */ CFRetain(baseURL); urlRef = baseURL; } free(node_path); nodecache_get_path_from_node: return ( urlRef ); } /******************************************************************************/ static int translate_status_to_error(UInt32 statusCode) { int result; /* switch by Nxx range */ switch ( statusCode / 100 ) { case 1: /* Informational 1xx */ syslog(LOG_ERR,"unexpected statusCode %ld", statusCode); result = ENOENT; /* CFNetwork eats 1xx responses so this should never happen */ break; case 2: /* Successful 2xx */ result = 0; break; case 3: /* Redirection 3xx */ syslog(LOG_ERR,"unexpected statusCode %ld", statusCode); result = ENOENT; break; case 4: /* Client Error 4xx */ switch ( statusCode ) { case 401: /* 401 Unauthorized */ case 407: /* 407 Proxy Authentication Required */ result = EAUTH; break; case 402: /* Payment required */ case 403: /* Forbidden */ result = EPERM; break; case 404: /* Not Found */ case 409: /* Conflict (path prefix does not exist) */ case 410: /* Gone */ result = ENOENT; break; case 414: /* Request URI Too Long */ result = ENAMETOOLONG; case 423: /* Locked (WebDAV) */ case 424: /* Failed Dependency (WebDAV) (EBUSY when a directory cannot be MOVE'd) */ result = EBUSY; break; default: syslog(LOG_ERR,"unexpected statusCode %ld", statusCode); result = EINVAL; /* unexpected */ break; } break; case 5: /* Server Error 5xx */ if ( statusCode == 507 ) /* Insufficient Storage (WebDAV) */ { result = ENOSPC; } else { syslog(LOG_ERR,"unexpected statusCode %ld", statusCode); result = ENOENT; /* unexpected */ } break; default: /* only the 1xx through 5xx ranges are defined */ syslog(LOG_ERR,"unexpected statusCode %ld", statusCode); result = EIO; break; } return ( result ); } /******************************************************************************/ /* returns TRUE if SSL properties were correctly applied (or were not needed) */ static int ApplySSLProperties(CFReadStreamRef readStreamRef) { int result = TRUE; if ( gSSLPropertiesDict != NULL ) { result = (CFReadStreamSetProperty(readStreamRef, kCFStreamPropertySSLSettings, gSSLPropertiesDict) == TRUE); } return ( result ); } /******************************************************************************/ /* * get_ReadStreamRec * * Tries to return a ReadStreamRec that's not in use and open. If that's not * possible, returns the first ReadStreamRec that's not in use and closed. */ static struct ReadStreamRec *get_ReadStreamRec(void) { int index; struct ReadStreamRec *result; int mutexerror; result = NULL; /* grab gNetworkGlobals_lock */ mutexerror = pthread_mutex_lock(&gNetworkGlobals_lock); require_noerr_action(mutexerror, pthread_mutex_lock, webdav_kill(-1)); for ( index = 0; index < (WEBDAV_REQUEST_THREADS + 1); ++index ) { if ( !gReadStreams[index].inUse ) { /* found one not in use */ /* if it still looks open, grab it */ if ( gReadStreams[index].readStreamRef != NULL ) { result = &gReadStreams[index]; /* get pointer to it */ result->inUse = TRUE; /* mark it in use */ break; } else if ( result == NULL ) { /* else... keep track of the first closed one in case we don't find an open one */ result = &gReadStreams[index]; } } } if ( result != NULL ) { /* should always happen */ result->inUse = TRUE; /* mark it in use */ } /* release gNetworkGlobals_lock */ mutexerror = pthread_mutex_unlock(&gNetworkGlobals_lock); require_noerr_action(mutexerror, pthread_mutex_unlock, webdav_kill(-1)); pthread_mutex_unlock: pthread_mutex_lock: return ( result ); } /******************************************************************************/ /* * release_ReadStreamRec * * Release a ReadStreamRec. */ static void release_ReadStreamRec(struct ReadStreamRec *theReadStreamRec) { int mutexerror; /* grab gNetworkGlobals_lock */ mutexerror = pthread_mutex_lock(&gNetworkGlobals_lock); require_noerr_action(mutexerror, pthread_mutex_lock, webdav_kill(-1)); /* release theReadStreamRec */ theReadStreamRec->inUse = FALSE; /* release gNetworkGlobals_lock */ mutexerror = pthread_mutex_unlock(&gNetworkGlobals_lock); require_noerr_action(mutexerror, pthread_mutex_unlock, webdav_kill(-1)); pthread_mutex_unlock: pthread_mutex_lock: return; } /******************************************************************************/ /* * SecCertificateCreateCFData * * Creates a CFDataRef from a SecCertificateRef. */ static CFDataRef SecCertificateCreateCFData(SecCertificateRef cert) { CSSM_DATA cert_data; CFDataRef data; OSStatus status; status = SecCertificateGetData(cert, &cert_data); require_noerr_action(status, SecCertificateGetData, data = NULL); data = CFDataCreate(NULL, cert_data.Data, cert_data.Length); SecCertificateGetData: return (data); } /******************************************************************************/ /* * SecCertificateArrayCreateCFDataArray * * Convert a CFArray[SecCertificate] to CFArray[CFData]. */ static CFArrayRef SecCertificateArrayCreateCFDataArray(CFArrayRef certs) { CFMutableArrayRef array; int count; int i; const void *certRef; count = CFArrayGetCount(certs); array = CFArrayCreateMutable(NULL, count, &kCFTypeArrayCallBacks); require(array != NULL, CFArrayCreateMutable); for (i = 0; i < count; ++i) { SecCertificateRef cert; CFDataRef data; certRef = CFArrayGetValueAtIndex(certs, i); cert = *((SecCertificateRef*)((void*)&certRef)); /* ugly but it works */ require(cert != NULL, CFArrayGetValueAtIndex); data = SecCertificateCreateCFData(cert); require(data != NULL, SecCertificateCreateCFData); CFArrayAppendValue(array, data); CFRelease(data); } return (array); /************/ SecCertificateCreateCFData: CFArrayGetValueAtIndex: CFRelease(array); CFArrayCreateMutable: return (NULL); } /******************************************************************************/ /* returns TRUE if user asked to continue with this certificate problem; FALSE if not */ static int ConfirmCertificate(CFReadStreamRef readStreamRef, SInt32 error) { int result; CFMutableDictionaryRef dict; CFArrayRef certs; CFArrayRef certs_data; CFNumberRef error_number; CFStringRef host_name; CFDataRef theData; int fd[2]; int pid, terminated_pid; union wait status; result = FALSE; fd[0] = fd[1] = -1; /* create a dictionary to stuff things all in */ dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); require(dict != NULL, CFDictionaryCreateMutable); /* get the certificates from the stream and add it with the kSSLClientPropTLSServerCertificateChain key */ certs = (CFArrayRef)CFReadStreamCopyProperty(readStreamRef, kCFStreamPropertySSLPeerCertificates); require(certs != NULL, CFReadStreamCopyProperty); certs_data = SecCertificateArrayCreateCFDataArray(certs); CFRelease(certs); require(certs_data != NULL, CFReadStreamCopyProperty); CFDictionaryAddValue(dict, kSSLClientPropTLSServerCertificateChain, certs_data); CFRelease(certs_data); /* convert error to a CFNumberRef and add it with the kSSLClientPropTLSTrustClientStatus key */ error_number = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &error); require(error_number != NULL, CFNumberCreate); CFDictionaryAddValue(dict, kSSLClientPropTLSTrustClientStatus, error_number); CFRelease(error_number); /* get the host name from the base URL and add it with the kSSLClientPropTLSServerHostName key */ host_name = CFURLCopyHostName(baseURL); require(host_name != NULL, CFURLCopyHostName); CFDictionaryAddValue(dict, kSSLClientPropTLSServerHostName, host_name); CFRelease(host_name); /* flatten it */ theData = CFPropertyListCreateXMLData(kCFAllocatorDefault, dict); require(theData != NULL, CFPropertyListCreateXMLData); CFRelease(dict); dict = NULL; /* open a pipe */ require(pipe(fd) >= 0, pipe); pid = fork(); require (pid >= 0, fork); if ( pid > 0 ) { /* parent */ size_t length; ssize_t bytes_written; close(fd[0]); /* close read end */ fd[0] = -1; length = CFDataGetLength(theData); bytes_written = write(fd[1], CFDataGetBytePtr(theData), length); require(bytes_written == (ssize_t)length, write); close(fd[1]); /* close write end */ fd[1] = -1; /* Parent waits for child's completion here */ while ( (terminated_pid = wait4(pid, (int *)&status, 0, NULL)) < 0 ) { /* retry if EINTR, else break out with error */ if ( errno != EINTR ) { break; } } /* we'll get here when the child completes */ if ( (terminated_pid == pid) && (WIFEXITED(status)) ) { result = WEXITSTATUS(status) == 0; } else { result = FALSE; } } else { /* child */ close(fd[1]); /* close write end */ fd[1] = -1; if ( fd[0] != STDIN_FILENO ) { require(dup2(fd[0], STDIN_FILENO) == STDIN_FILENO, dup2); close(fd[0]); /* not needed after dup2 */ fd[0] = -1; } require(execl(PRIVATE_CERT_UI_COMMAND, PRIVATE_CERT_UI_COMMAND, NULL) >= 0, execl); } return ( result ); /************/ execl: dup2: write: fork: if (fd[0] != -1) { close(fd[0]); } if (fd[1] != -1) { close(fd[1]); } pipe: CFPropertyListCreateXMLData: CFURLCopyHostName: CFNumberCreate: CFReadStreamCopyProperty: if ( dict != NULL ) { CFRelease(dict); } CFDictionaryCreateMutable: return ( FALSE ); } /******************************************************************************/ /* * returns EAGAIN if entire transaction should be retried * returns ECANCELED if the user clicked cancel in the certificate UI * returns EIO if this was not an SSL error */ static int HandleSSLErrors(CFReadStreamRef readStreamRef) { CFStreamError streamError; SInt32 error; int result; result = EIO; streamError = CFReadStreamGetError(readStreamRef); if ( streamError.domain == kCFStreamErrorDomainSSL ) { error = streamError.error; if ( gSSLPropertiesDict == NULL ) { gSSLPropertiesDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); require(gSSLPropertiesDict != NULL, CFDictionaryCreateMutable); } /* if we haven't tried falling back from TLS to SSL and the errror indicates that might work... */ if ( (CFDictionaryGetValue(gSSLPropertiesDict, kCFStreamSSLLevel) == NULL) && (((error <= errSSLProtocol) && (error > errSSLXCertChainInvalid)) || ((error <= errSSLCrypto) && (error > errSSLUnknownRootCert)) || ((error <= errSSLClosedNoNotify) && (error > errSSLPeerBadCert)) || (error == errSSLIllegalParam) || ((error <= errSSLPeerAccessDenied) && (error > errSSLLast))) ) { /* retry with fall back from TLS to SSL */ CFDictionarySetValue(gSSLPropertiesDict, kCFStreamSSLLevel, kCFStreamSocketSecurityLevelSSLv3); result = EAGAIN; } else { switch ( error ) { case errSSLCertExpired: case errSSLCertNotYetValid: /* The certificate for this server has expired or is not yet valid */ if ( (CFDictionaryGetValue(gSSLPropertiesDict, kCFStreamSSLAllowsExpiredCertificates) == NULL) ) { if ( ConfirmCertificate(readStreamRef, error) ) { result = EAGAIN; CFDictionarySetValue(gSSLPropertiesDict, kCFStreamSSLAllowsExpiredCertificates, kCFBooleanTrue); CFDictionarySetValue(gSSLPropertiesDict, kCFStreamSSLAllowsExpiredRoots, kCFBooleanTrue); } else { result = ECANCELED; } } break; case errSSLBadCert: case errSSLXCertChainInvalid: case errSSLHostNameMismatch: /* The certificate for this server is invalid */ if ( (CFDictionaryGetValue(gSSLPropertiesDict, kCFStreamSSLValidatesCertificateChain) == NULL) ) { if ( ConfirmCertificate(readStreamRef, error) ) { result = EAGAIN; CFDictionarySetValue(gSSLPropertiesDict, kCFStreamSSLValidatesCertificateChain, kCFBooleanFalse); } else { result = ECANCELED; } } break; case errSSLUnknownRootCert: case errSSLNoRootCert: /* The certificate for this server was signed by an unknown certifying authority */ if ( (CFDictionaryGetValue(gSSLPropertiesDict, kCFStreamSSLAllowsAnyRoot) == NULL) ) { if ( ConfirmCertificate(readStreamRef, error) ) { result = EAGAIN; CFDictionarySetValue(gSSLPropertiesDict, kCFStreamSSLAllowsAnyRoot, kCFBooleanTrue); } else { result = ECANCELED; } } break; default: result = EIO; break; } } } CFDictionaryCreateMutable: return ( result ); } /******************************************************************************/ /* create the HTTP read stream with CFReadStreamCreateForHTTPRequest */ /* turn on automatic redirection */ /* add proxies (if any) */ /* apply any SSL properties we've already negotiated with the server */ /* open the read stream */ /* set the file position to 0 */ /* create a stream from the file */ /* create the HTTP read stream with CFReadStreamCreateForStreamedHTTPRequest */ /* Note: PUTs cannot be automatically redirected -- see rfc2616 section 10.3.x */ /* add proxies (if any) */ /* apply any SSL properties we've already negotiated with the server */ /* open the read stream */ /* create the HTTP read stream with CFReadStreamCreateForHTTPRequest */ /* (conditional) turn on automatic redirection */ /* add proxies (if any) */ /* apply any SSL properties we've already negotiated with the server */ /* open the read stream and handle SSL errors */ static int open_stream_for_transaction( CFHTTPMessageRef request, /* -> the request to send */ CFReadStreamRef fdStream, /* -> if not NULL, the file stream */ int auto_redirect, /* -> if TRUE, set kCFStreamPropertyHTTPShouldAutoredirect on stream */ int *retryTransaction, /* -> if TRUE, return EAGAIN on errors when streamError is kCFStreamErrorDomainPOSIX/EPIPE and set retryTransaction to FALSE */ struct ReadStreamRec **readStreamRecPtr) /* <- pointer to the ReadStreamRec in use */ { int result; struct ReadStreamRec *theReadStreamRec; CFReadStreamRef newReadStreamRef; result = 0; *readStreamRecPtr = NULL; /* create the HTTP read stream */ if ( fdStream != NULL ) { newReadStreamRef = CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request, fdStream); require(newReadStreamRef != NULL, CFReadStreamCreateForStreamedHTTPRequest); } else { newReadStreamRef = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request); require(newReadStreamRef != NULL, CFReadStreamCreateForHTTPRequest); } /* add persistent property */ CFReadStreamSetProperty(newReadStreamRef, kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue); /* turn on automatic redirection */ if ( auto_redirect ) { require(CFReadStreamSetProperty(newReadStreamRef, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue) != FALSE, SetAutoredirectProperty); } /* add proxies (if any) */ require_quiet(set_global_stream_properties(newReadStreamRef) == 0, set_global_stream_properties); /* apply any SSL properties we've already negotiated with the server */ ApplySSLProperties(newReadStreamRef); /* get a ReadStreamRec that was not in use */ theReadStreamRec = get_ReadStreamRec(); /* (after unlocking) make sure we got a ReadStreamRec */ require(theReadStreamRec != NULL, get_ReadStreamRec); /* add the unique property from the ReadStreamRec to newReadStreamRef */ require(CFReadStreamSetProperty(newReadStreamRef, CFSTR("WebdavConnectionNumber"), theReadStreamRec->uniqueValue) != FALSE, SetWebdavConnectionNumberProperty); /* open the read stream and handle SSL errors */ if ( CFReadStreamOpen(newReadStreamRef) == FALSE ) { result = HandleSSLErrors(newReadStreamRef); if ( result != EAGAIN ) { CFStreamError streamError; streamError = CFReadStreamGetError(newReadStreamRef); if ( *retryTransaction && streamError.domain == kCFStreamErrorDomainPOSIX && streamError.error == EPIPE) { /* if we get a POSIX errror back from the stream, retry the transaction once */ syslog(LOG_INFO,"open_stream_for_transaction: CFStreamError: domain %ld, error %ld -- retrying", streamError.domain, streamError.error); *retryTransaction = FALSE; result = EAGAIN; } else { if ( get_connectionstate() == WEBDAV_CONNECTION_UP ) { syslog(LOG_ERR,"open_stream_for_transaction: CFStreamError: domain %ld, error %ld", streamError.domain, streamError.error); } set_connectionstate(WEBDAV_CONNECTION_DOWN); result = ENXIO; } } goto CFReadStreamOpen; } /* close and release old read stream */ if ( theReadStreamRec->readStreamRef != NULL ) { CFReadStreamClose(theReadStreamRec->readStreamRef); CFRelease(theReadStreamRec->readStreamRef); } /* save new read stream */ theReadStreamRec->readStreamRef = newReadStreamRef; /* return the ReadStreamRec to the caller */ *readStreamRecPtr = theReadStreamRec; return ( 0 ); /**********************/ CFReadStreamOpen: SetWebdavConnectionNumberProperty: release_ReadStreamRec(theReadStreamRec); get_ReadStreamRec: set_global_stream_properties: SetAutoredirectProperty: CFRelease(newReadStreamRef); CFReadStreamCreateForHTTPRequest: CFReadStreamCreateForStreamedHTTPRequest: *readStreamRecPtr = NULL; if ( result == 0 ) { result = EIO; } return ( result ); } /******************************************************************************/ /* * stream_get_transaction * * Creates an HTTP stream, sends the request and returns the response and response body. */ static int stream_get_transaction( CFHTTPMessageRef request, /* -> the request to send */ int *retryTransaction, /* -> if TRUE, return EAGAIN on errors when streamError is kCFStreamErrorDomainPOSIX/EPIPE and set retryTransaction to FALSE */ struct node_entry *node, /* -> node to get into */ CFHTTPMessageRef *response) { struct ReadStreamRec *readStreamRecPtr; void *buffer; CFIndex totalRead; CFIndex bytesRead; CFTypeRef theResponsePropertyRef; int background_load; CFStringRef connectionHeaderRef; CFHTTPMessageRef responseMessage; int result; result = 0; /* * If we're down and the mount is supposed to fail on disconnects * instead of retrying, just return an error. */ require_quiet(!gSuppressAllUI || (get_connectionstate() == WEBDAV_CONNECTION_UP), connection_down); result = open_stream_for_transaction(request, NULL, TRUE, retryTransaction, &readStreamRecPtr); require_noerr_quiet(result, open_stream_for_transaction); /* malloc a buffer big enough for first read */ buffer = malloc(first_read_len); require(buffer != NULL, malloc_buffer); /* Send the message and get up to first_read_len bytes of response */ totalRead = 0; background_load = FALSE; while ( 1 ) { bytesRead = CFReadStreamRead(readStreamRecPtr->readStreamRef, (UInt8 *)buffer + totalRead, first_read_len - totalRead); if ( bytesRead > 0 ) { totalRead += bytesRead; if ( totalRead >= first_read_len ) { /* is there more data to read? */ if ( CFReadStreamGetStatus(readStreamRecPtr->readStreamRef) == kCFStreamStatusAtEnd ) { /* there are no more bytes to read */ background_load = FALSE; } else { /* we'll need to hand off to get the rest of the file data */ background_load = TRUE; } break; } } else if ( bytesRead == 0 ) { /* there are no more bytes to read */ background_load = FALSE; break; } else { CFStreamError streamError; streamError = CFReadStreamGetError(readStreamRecPtr->readStreamRef); if ( *retryTransaction && streamError.domain == kCFStreamErrorDomainPOSIX && streamError.error == EPIPE ) { /* if we get a POSIX errror back from the stream, retry the transaction once */ syslog(LOG_INFO,"stream_get_transaction: CFStreamError: domain %ld, error %ld -- retrying", streamError.domain, streamError.error); *retryTransaction = FALSE; result = EAGAIN; } else { if ( get_connectionstate() == WEBDAV_CONNECTION_UP ) { syslog(LOG_ERR,"stream_get_transaction: CFStreamError: domain %ld, error %ld", streamError.domain, streamError.error); } set_connectionstate(WEBDAV_CONNECTION_DOWN); result = ENXIO; } goto CFReadStreamRead; } }; /* get the response header */ theResponsePropertyRef = CFReadStreamCopyProperty(readStreamRecPtr->readStreamRef, kCFStreamPropertyHTTPResponseHeader); require(theResponsePropertyRef != NULL, GetResponseHeader); /* fun with casting a "const void *" CFTypeRef away */ responseMessage = *((CFHTTPMessageRef*)((void*)&theResponsePropertyRef)); /* * if WEBDAV_DOWNLOAD_NEVER * download entire file * else if WEBDAV_DOWNLOAD_FINISHED * then use If-Modified-Since: node->file_last_modified date * //and If-None-Match node->file_entity_tag to the cache file * 200 = getting whole file * 304 = not modified; current copy is OK * //else if has node->file_entity_tag and NOT weak * // download partial file with If-Range: node->file_entity_tag * // 206 = getting remainder * // 200 = getting whole file * else //if has node->file_last_modified * download partial file with If-Range: node->file_last_modified date * 206 = getting remainder * 200 = getting whole file */ /* get the status code */ switch ( CFHTTPMessageGetResponseStatusCode(responseMessage) ) { case 200: /* OK - download whole file from beginning */ /* clear the flags */ require(fchflags(node->file_fd, 0) == 0, fchflags); /* truncate the file */ require_action(ftruncate(node->file_fd, 0LL) != -1, ftruncate, syslog(LOG_ERR,"errno %d", errno)); /* reset the position to 0 */ require(lseek(node->file_fd, 0LL, SEEK_SET) >= 0, lseek); /* write the bytes in buffer to the cache file*/ require(write(node->file_fd, buffer, (size_t)totalRead) == (ssize_t)totalRead, write); break; case 206: /* Partial Content - download from EOF */ /* clear the flags */ require(fchflags(node->file_fd, 0) == 0, fchflags); /* seek to EOF */ require(lseek(node->file_fd, 0LL, SEEK_END) >= 0, lseek); /* write the bytes in buffer to the cache file*/ require(write(node->file_fd, buffer, (size_t)totalRead) >= 0, write); break; case 304: /* Not Modified - the cache file is still good */ background_load = FALSE; break; default: background_load = FALSE; break; } free(buffer); buffer = NULL; set_connectionstate(WEBDAV_CONNECTION_UP); /* Get the Connection header (if any) */ readStreamRecPtr->connectionClose = FALSE; connectionHeaderRef = CFHTTPMessageCopyHeaderFieldValue(responseMessage, CFSTR("Connection")); if ( connectionHeaderRef != NULL ) { /* is the connection-token is "close"? */ if ( CFStringCompare(connectionHeaderRef, CFSTR("close"), kCFCompareCaseInsensitive) == kCFCompareEqualTo ) { /* yes -- then the server closed this connection, so close and release the read stream now */ readStreamRecPtr->connectionClose = TRUE; } CFRelease(connectionHeaderRef); } if ( background_load ) { int error; /* * As a hack, set the NODUMP bit so that the kernel * knows that we are in the process of filling up the file */ require(fchflags(node->file_fd, UF_NODUMP) == 0, fchflags); node->file_status = WEBDAV_DOWNLOAD_IN_PROGRESS; /* pass the node and readStreamRef off to another thread to finish */ error = requestqueue_enqueue_download(node, readStreamRecPtr); require_noerr_quiet(error, webdav_requestqueue_enqueue_new_download); } else { node->file_status = WEBDAV_DOWNLOAD_FINISHED; if ( readStreamRecPtr->connectionClose ) { /* close and release the stream */ CFReadStreamClose(readStreamRecPtr->readStreamRef); CFRelease(readStreamRecPtr->readStreamRef); readStreamRecPtr->readStreamRef = NULL; } /* make this ReadStreamRec is available again */ release_ReadStreamRec(readStreamRecPtr); } *response = responseMessage; return ( 0 ); /**********************/ webdav_requestqueue_enqueue_new_download: fchflags: ftruncate: lseek: write: GetResponseHeader: CFReadStreamRead: if ( buffer != NULL ) { free(buffer); } malloc_buffer: /* close and release the read stream on errors */ CFReadStreamClose(readStreamRecPtr->readStreamRef); CFRelease(readStreamRecPtr->readStreamRef); readStreamRecPtr->readStreamRef = NULL; /* make this ReadStreamRec is available again */ release_ReadStreamRec(readStreamRecPtr); open_stream_for_transaction: connection_down: *response = NULL; if ( result == 0 ) { result = EIO; } return ( result ); } /******************************************************************************/ /* * stream_transaction_from_file * * Creates an HTTP stream with the read stream coming from file_fd, * sends the request and returns the response. The response body (if any) is * read and disposed. */ static int stream_transaction_from_file( CFHTTPMessageRef request, int file_fd, int *retryTransaction, /* -> if TRUE, return EAGAIN on errors when streamError is kCFStreamErrorDomainPOSIX/EPIPE and set retryTransaction to FALSE */ CFHTTPMessageRef *response) { CFReadStreamRef fdStream; struct ReadStreamRec *readStreamRecPtr; void *buffer; CFIndex bytesRead; CFTypeRef theResponsePropertyRef; off_t contentLength; CFStringRef contentLengthString; CFStringRef connectionHeaderRef; CFHTTPMessageRef responseMessage; int result; result = 0; /* * If we're down and the mount is supposed to fail on disconnects * instead of retrying, just return an error. */ require_quiet(!gSuppressAllUI || (get_connectionstate() == WEBDAV_CONNECTION_UP), connection_down); /* get the file length */ contentLength = lseek(file_fd, 0LL, SEEK_END); require(contentLength != -1, lseek); /* create a string with the file length for the Content-Length header */ contentLengthString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%qd"), contentLength); /* CFReadStreamCreateForStreamedHTTPRequest will use chunked transfer-encoding if the Content-Length header cannot be provided */ if ( contentLengthString != NULL ) { CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Content-Length"), contentLengthString); CFRelease(contentLengthString); } /* set the file position to 0 */ verify(lseek(file_fd, 0LL, SEEK_SET) != -1); /* create a stream from the file */ CFStreamCreatePairWithSocket(kCFAllocatorDefault, file_fd, &fdStream, NULL); require(fdStream != NULL, CFReadStreamCreateWithFile); result = open_stream_for_transaction(request, fdStream, FALSE, retryTransaction, &readStreamRecPtr); require_noerr_quiet(result, open_stream_for_transaction); /* malloc a buffer big enough for most responses */ buffer = malloc(BODY_BUFFER_SIZE); require(buffer != NULL, malloc_currentbuffer); /* Send the message and eat the response */ while ( 1 ) { bytesRead = CFReadStreamRead(readStreamRecPtr->readStreamRef, (UInt8 *)buffer, BODY_BUFFER_SIZE); if ( bytesRead > 0 ) { continue; } else if ( bytesRead == 0 ) { /* there are no more bytes to read */ break; } else { CFStreamError streamError; streamError = CFReadStreamGetError(readStreamRecPtr->readStreamRef); if ( *retryTransaction && streamError.domain == kCFStreamErrorDomainPOSIX && streamError.error == EPIPE ) { /* if we get a POSIX errror back from the stream, retry the transaction once */ syslog(LOG_INFO,"stream_transaction_from_file: CFStreamError: domain %ld, error %ld -- retrying", streamError.domain, streamError.error); *retryTransaction = FALSE; result = EAGAIN; } else { if ( get_connectionstate() == WEBDAV_CONNECTION_UP ) { syslog(LOG_ERR,"stream_transaction_from_file: CFStreamError: domain %ld, error %ld", streamError.domain, streamError.error); } set_connectionstate(WEBDAV_CONNECTION_DOWN); result = ENXIO; } goto CFReadStreamRead; } }; free(buffer); /* get the response header */ theResponsePropertyRef = CFReadStreamCopyProperty(readStreamRecPtr->readStreamRef, kCFStreamPropertyHTTPResponseHeader); require(theResponsePropertyRef != NULL, GetResponseHeader); /* fun with casting a "const void *" CFTypeRef away */ responseMessage = *((CFHTTPMessageRef*)((void*)&theResponsePropertyRef)); set_connectionstate(WEBDAV_CONNECTION_UP); /* Get the Connection header (if any) */ connectionHeaderRef = CFHTTPMessageCopyHeaderFieldValue(responseMessage, CFSTR("Connection")); /* is the connection-token is "close"? */ if ( connectionHeaderRef != NULL ) { if ( CFStringCompare(connectionHeaderRef, CFSTR("close"), kCFCompareCaseInsensitive) == kCFCompareEqualTo ) { /* yes -- then the server closed this connection, so close and release the read stream now */ CFReadStreamClose(readStreamRecPtr->readStreamRef); CFRelease(readStreamRecPtr->readStreamRef); readStreamRecPtr->readStreamRef = NULL; } CFRelease(connectionHeaderRef); } CFRelease(fdStream); /* make this ReadStreamRec is available again */ release_ReadStreamRec(readStreamRecPtr); /* fun with casting a "const void *" CFTypeRef away */ *response = responseMessage; return ( 0 ); /**********************/ GetResponseHeader: CFReadStreamRead: free(buffer); malloc_currentbuffer: CFReadStreamClose(readStreamRecPtr->readStreamRef); CFRelease(readStreamRecPtr->readStreamRef); readStreamRecPtr->readStreamRef = NULL; /* make this ReadStreamRec is available again */ release_ReadStreamRec(readStreamRecPtr); CFRelease(fdStream); CFReadStreamCreateWithFile: lseek: open_stream_for_transaction: connection_down: *response = NULL; if ( result == 0 ) { result = EIO; } return ( result ); } /******************************************************************************/ /* * stream_transaction * * Creates an HTTP stream, sends the request and returns the response and response body. */ static int stream_transaction( CFHTTPMessageRef request, /* -> the request to send */ int auto_redirect, /* -> if TRUE, set kCFStreamPropertyHTTPShouldAutoredirect on stream */ int *retryTransaction, /* -> if TRUE, return EAGAIN on errors when streamError is kCFStreamErrorDomainPOSIX/EPIPE and set retryTransaction to FALSE */ UInt8 **buffer, /* <- response data buffer (caller responsible for freeing) */ CFIndex *count, /* <- response data buffer length */ CFHTTPMessageRef *response) /* <- the response message */ { struct ReadStreamRec *readStreamRecPtr; CFIndex totalRead; void *currentbuffer; void *newBuffer; CFIndex bytesRead; CFIndex bytesToRead; CFIndex bufferSize; CFTypeRef theResponsePropertyRef; CFStringRef connectionHeaderRef; CFHTTPMessageRef responseMessage; int result; result = 0; /* * If we're down and the mount is supposed to fail on disconnects * instead of retrying, just return an error. */ require_quiet(!gSuppressAllUI || (get_connectionstate() == WEBDAV_CONNECTION_UP), connection_down); /* get an open ReadStreamRec */ result = open_stream_for_transaction(request, NULL, auto_redirect, retryTransaction, &readStreamRecPtr); require_noerr_quiet(result, open_stream_for_transaction); /* malloc a buffer big enough for most responses */ bufferSize = BODY_BUFFER_SIZE; currentbuffer = malloc(bufferSize); require(currentbuffer != NULL, malloc_currentbuffer); /* Send the message and get the response */ totalRead = 0; while ( 1 ) { bytesToRead = bufferSize - totalRead; bytesRead = CFReadStreamRead(readStreamRecPtr->readStreamRef, (UInt8 *)currentbuffer + totalRead, bytesToRead); if ( bytesRead > 0 ) { totalRead += bytesRead; /* is currentbuffer getting close to full? */ if ( (bytesToRead - bytesRead) < (BODY_BUFFER_SIZE / 2) ) { /* yes, so get larger currentbuffer for next read */ bufferSize += BODY_BUFFER_SIZE; newBuffer = realloc(currentbuffer, bufferSize); require(newBuffer != NULL, realloc); currentbuffer = newBuffer; } } else if ( bytesRead == 0 ) { /* there are no more bytes to read */ break; } else { result = HandleSSLErrors(readStreamRecPtr->readStreamRef); if ( result != EAGAIN ) { if ( result != ECANCELED ) { CFStreamError streamError; streamError = CFReadStreamGetError(readStreamRecPtr->readStreamRef); if ( *retryTransaction && streamError.domain == kCFStreamErrorDomainPOSIX && streamError.error == EPIPE ) { /* if we get a POSIX errror back from the stream, retry the transaction once */ syslog(LOG_INFO,"stream_transaction: CFStreamError: domain %ld, error %ld -- retrying", streamError.domain, streamError.error); *retryTransaction = FALSE; result = EAGAIN; } else { if ( get_connectionstate() == WEBDAV_CONNECTION_UP ) { syslog(LOG_ERR,"stream_transaction: CFStreamError: domain %ld, error %ld", streamError.domain, streamError.error); } set_connectionstate(WEBDAV_CONNECTION_DOWN); result = ENXIO; } } } goto CFReadStreamRead; } }; /* get the response header */ theResponsePropertyRef = CFReadStreamCopyProperty(readStreamRecPtr->readStreamRef, kCFStreamPropertyHTTPResponseHeader); require(theResponsePropertyRef != NULL, GetResponseHeader); /* fun with casting a "const void *" CFTypeRef away */ responseMessage = *((CFHTTPMessageRef*)((void*)&theResponsePropertyRef)); set_connectionstate(WEBDAV_CONNECTION_UP); /* Get the Connection header (if any) */ connectionHeaderRef = CFHTTPMessageCopyHeaderFieldValue(responseMessage, CFSTR("Connection")); /* is the connection-token is "close"? */ if ( connectionHeaderRef != NULL ) { if ( CFStringCompare(connectionHeaderRef, CFSTR("close"), kCFCompareCaseInsensitive) == kCFCompareEqualTo ) { /* yes -- then the server closed this connection, so close and release the read stream now */ CFReadStreamClose(readStreamRecPtr->readStreamRef); CFRelease(readStreamRecPtr->readStreamRef); readStreamRecPtr->readStreamRef = NULL; } CFRelease(connectionHeaderRef); } /* make this ReadStreamRec is available again */ release_ReadStreamRec(readStreamRecPtr); *response = responseMessage; *count = totalRead; *buffer = currentbuffer; return ( 0 ); /**********************/ GetResponseHeader: CFReadStreamRead: realloc: free(currentbuffer); malloc_currentbuffer: /* close and release the read stream on errors */ CFReadStreamClose(readStreamRecPtr->readStreamRef); CFRelease(readStreamRecPtr->readStreamRef); readStreamRecPtr->readStreamRef = NULL; /* make this ReadStreamRec is available again */ release_ReadStreamRec(readStreamRecPtr); open_stream_for_transaction: connection_down: *response = NULL; *count = 0; *buffer = NULL; if ( result == 0 ) { result = EIO; } return ( result ); } /******************************************************************************/ /* * send_transaction * * Creates a request, adds the message body, headers and authentication if needed, * and then calls stream_transaction() to send the request to the server and get * the server's response. If the caller requests the response body and/or the * response message, they are returned. Otherwise, they are freed/released. */ static int send_transaction( uid_t uid, /* -> uid of the user making the request */ CFURLRef url, /* -> url to the resource */ CFStringRef requestMethod, /* -> the request method */ CFDataRef bodyData, /* -> message body data, or NULL if no body */ CFIndex headerCount, /* -> number of headers */ struct HeaderFieldValue *headers, /* -> pointer to array of struct HeaderFieldValue, or NULL if none */ int auto_redirect, /* -> if TRUE, set kCFStreamPropertyHTTPShouldAutoredirect on stream */ UInt8 **buffer, /* <- if not NULL, response data buffer is returned here (caller responsible for freeing) */ CFIndex *count, /* <- if not NULL, response data buffer length is returned here*/ CFHTTPMessageRef *response) /* <- if not NULL, response is returned here */ { int error; CFIndex i; struct HeaderFieldValue *headerPtr; CFHTTPMessageRef message; CFHTTPMessageRef responseRef; UInt32 statusCode; UInt32 auth_generation; UInt8 *responseBuffer; CFIndex responseBufferLength; int retryTransaction; error = 0; responseBuffer = NULL; responseBufferLength = 0; message = NULL; responseRef = NULL; statusCode = 0; auth_generation = 0; retryTransaction = TRUE; /* the transaction/authentication loop */ do { /* release message if left from previous loop */ if ( message != NULL ) { CFRelease(message); message = NULL; } /* create a CFHTTP message object */ message = CFHTTPMessageCreateRequest(kCFAllocatorDefault, requestMethod, url, kCFHTTPVersion1_1); require_action(message != NULL, CFHTTPMessageCreateRequest, error = EIO); /* set the message body (if any) */ if ( bodyData != NULL ) { CFHTTPMessageSetBody(message, bodyData); } /* change the User-Agent header */ CFHTTPMessageSetHeaderFieldValue(message, CFSTR("User-Agent"), userAgentHeaderValue); /* add the X-Source-Id header if needed */ if ( X_Source_Id_HeaderValue != NULL ) { CFHTTPMessageSetHeaderFieldValue(message, CFSTR("X-Source-Id"), X_Source_Id_HeaderValue); } /* add other HTTP headers (if any) */ for ( i = 0, headerPtr = headers, headers; i < headerCount; ++i, ++headerPtr ) { CFHTTPMessageSetHeaderFieldValue(message, headerPtr->headerField, headerPtr->value); } /* apply credentials (if any) */ /* * statusCode will be 401 or 407 and responseRef will not be NULL if we've already been through the loop; * statusCode will be 0 and responseRef will be NULL if this is the first time through. */ error = authcache_apply(uid, message, statusCode, responseRef, &auth_generation); if ( error != 0 ) { break; } /* stream_transaction returns responseRef and responseBuffer so release them if left from previous loop */ if ( responseBuffer != NULL ) { free(responseBuffer); responseBuffer = NULL; responseBufferLength = 0; } if ( responseRef != NULL ) { CFRelease(responseRef); responseRef = NULL; } /* now that everything's ready to send, send it */ error = stream_transaction(message, auto_redirect, &retryTransaction, &responseBuffer, &responseBufferLength, &responseRef); if ( error == EAGAIN ) { statusCode = 0; /* responseRef will be left NULL on retries */ } else { if ( error != 0 ) { break; } /* get the status code */ statusCode = CFHTTPMessageGetResponseStatusCode(responseRef); } } while ( error == EAGAIN || statusCode == 401 || statusCode == 407 ); CFHTTPMessageCreateRequest: if ( error == 0 ) { error = translate_status_to_error(statusCode); if ( error == 0 ) { /* * when we get here with no errors, then we need to tell the authcache the * transaction worked so it can mark the credentials valid and, if needed * add the credentials to the keychain. If the auth_generation changed, then * another transaction updated the authcache element after we got it. */ (void) authcache_valid(uid, message, auth_generation); } else { if ( responseBuffer != NULL ) { free(responseBuffer); responseBuffer = NULL; } } } if ( message != NULL ) { CFRelease(message); } /* return requested output parameters */ if ( buffer != NULL ) { *buffer = responseBuffer; } else { if ( responseBuffer != NULL ) { free(responseBuffer); } } if ( count != NULL ) { *count = responseBufferLength; } if ( response != NULL ) { /* the caller wants the response and will release it */ *response = responseRef; } else { if ( responseRef != NULL ) { CFRelease(responseRef); } } return ( error ); } /*****************************************************************************/ /* * ParseDAVLevel parses a DAV header's field-value (if any) to get the DAV level. * Input: * responsePropertyRef the response message * Outputs: * dav_level the dav_level (0 = DAV not supported) * * The rules for message headers are (rfc 2518, section 9.1): * * DAV = "DAV" ":" "1" ["," "2"] ["," 1#extend] * extend = Coded-URL | token * * (Note: The rules for extend are still not in the rfc - they were taken from * messages in the WebDAV discussion list and are needed to interoperability * with Apache 2.0 servers which put Coded-URLs in DAV headers.) */ static void ParseDAVLevel(CFHTTPMessageRef responsePropertyRef, int *dav_level) { CFStringRef davHeaderRef; const char *field_value; char buffer[4096]; const char *token; *dav_level = 0; davHeaderRef = CFHTTPMessageCopyHeaderFieldValue(responsePropertyRef, CFSTR("DAV")); if ( davHeaderRef ) { field_value = CFStringGetCStringPtr(davHeaderRef, kCFStringEncodingUTF8); if ( field_value == NULL ) { if ( CFStringGetCString(davHeaderRef, buffer, 4096, kCFStringEncodingUTF8) ) { field_value = buffer; } } CFRelease(davHeaderRef); if ( field_value != NULL ) { while ( *field_value != '\0' ) { /* find first non-LWS character */ field_value = SkipLWS(field_value); if ( *field_value == '\0' ) { /* if we're at end of string, break out of main while loop */ break; } /* is value a token or a Coded-URL? */ if ( *field_value == '<' ) { /* it's a Coded-URL, so eat it */ /* skip over '<' */ ++field_value; /* find '>"' marking the end of the quoted-string */ field_value = SkipCodedURL(field_value); if ( *field_value != '\0' ) { /* skip over '>' */ ++field_value; } } else { /* it's a token */ /* mark start of the value token */ token = field_value; /* find the end of the value token */ field_value = SkipToken(field_value); /* could this token be '1' or '2'? */ if ( (field_value - token) == 1 ) { if ( (*token == '1') && (*dav_level < 1) ) { *dav_level = 1; } else if ( *token == '2' && (*dav_level < 2) ) { *dav_level = 2; } } } /* skip over LWS (if any) */ field_value = SkipLWS(field_value); /* if there's any string left after the LWS... */ if ( *field_value != '\0' ) { /* we should have found a comma */ if ( *field_value != ',' ) { /* if not, break out of main while loop */ break; } /* skip over one or more commas */ while ( *field_value == ',' ) { ++field_value; } } /* * field_value is now pointing at first character after comma * delimiter, or at end of string */ } } } } /******************************************************************************/ static int network_getDAVLevel( uid_t uid, /* -> uid of the user making the request */ CFURLRef urlRef, /* -> url */ int *dav_level) /* <- DAV level */ { int error; CFHTTPMessageRef response; /* the 3 headers */ CFIndex headerCount = 1; struct HeaderFieldValue headers[] = { { CFSTR("Accept"), CFSTR("*/*") }, }; *dav_level = 0; /* send request to the server and get the response */ error = send_transaction(uid, urlRef, CFSTR("OPTIONS"), NULL, headerCount, headers, TRUE, NULL, NULL, &response); if ( !error ) { /* get the DAV level */ ParseDAVLevel(response, dav_level); /* release the response buffer */ CFRelease(response); } return ( error ); } /*****************************************************************************/ static int get_from_attributes_cache(struct node_entry *node, uid_t uid) { ssize_t size; int result; result = FALSE; /* default to FALSE */ if ( node_attributes_valid(node, uid) && (node->attr_appledoubleheader != NULL) ) { require(fchflags(node->file_fd, 0) == 0, fchflags); require(lseek(node->file_fd, (off_t)0, SEEK_SET) != -1, lseek); require(ftruncate(node->file_fd, 0LL) != -1, ftruncate); /* we found the AppleDouble header in memcache */ size = write(node->file_fd, (void *)node->attr_appledoubleheader, APPLEDOUBLEHEADER_LENGTH); if ( size != APPLEDOUBLEHEADER_LENGTH ) { debug_string("write failed"); /* attempt to seek back to start of file, make sure it's empty, and then reset its status */ (void) lseek(node->file_fd, (off_t)0, SEEK_SET); (void) ftruncate(node->file_fd, 0LL); node->file_status = WEBDAV_DOWNLOAD_NEVER; node->file_validated_time = 0; node->file_last_modified = -1; if ( node->file_entity_tag != NULL ) { free(node->file_entity_tag); node->file_entity_tag = NULL; } } else { node->file_status = WEBDAV_DOWNLOAD_FINISHED; node->file_validated_time = node->attr_time; node->file_last_modified = (node->attr_stat.st_mtimespec.tv_sec != 0) ? node->attr_stat.st_mtimespec.tv_sec : -1; /* ••••• should I get the etag when I get attr_appledoubleheader? probably */ if ( node->file_entity_tag != NULL ) { free(node->file_entity_tag); node->file_entity_tag = NULL; } result = TRUE; } } ftruncate: lseek: fchflags: return ( result ); } /******************************************************************************/ /* * network_stat handles requests from network_lookup, network_getattr * and network_mount. */ static int network_stat( uid_t uid, /* -> uid of the user making the request */ CFURLRef urlRef, /* -> url to the resource */ struct stat *statbuf) /* <- stat information is returned in this buffer */ { int error; UInt8 *responseBuffer; CFIndex count; CFDataRef bodyData; /* the xml for the message body */ const char xmlString[] = "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n"; /* the 3 headers */ CFIndex headerCount = 3; struct HeaderFieldValue headers[] = { { CFSTR("Accept"), CFSTR("*/*") }, { CFSTR("Content-Type"), CFSTR("text/xml") }, { CFSTR("Depth"), CFSTR("0") } }; /* create the message body with the xml */ bodyData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, xmlString, strlen(xmlString), kCFAllocatorNull); require_action(bodyData != NULL, CFDataCreateWithBytesNoCopy, error = EIO); /* send request to the server and get the response */ error = send_transaction(uid, urlRef, CFSTR("PROPFIND"), bodyData, headerCount, headers, TRUE, &responseBuffer, &count, NULL); if ( !error ) { /* parse the statbuf from the response buffer */ error = parse_stat((char *)responseBuffer, (int)count, statbuf); /* free the response buffer */ free(responseBuffer); } /* release the message body */ CFRelease(bodyData); CFDataCreateWithBytesNoCopy: return ( error ); } /******************************************************************************/ static int network_dir_is_empty( uid_t uid, /* -> uid of the user making the request */ CFURLRef urlRef) /* -> url to check */ { int error; UInt8 *responseBuffer; CFIndex count; CFDataRef bodyData; /* the xml for the message body */ const char xmlString[] = "\n" "\n" "\n" "\n" "\n" "\n"; /* the 3 headers */ CFIndex headerCount = 3; struct HeaderFieldValue headers[] = { { CFSTR("Accept"), CFSTR("*/*") }, { CFSTR("Content-Type"), CFSTR("text/xml") }, { CFSTR("Depth"), CFSTR("1") } }; error = 0; responseBuffer = NULL; /* create the message body with the xml */ bodyData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, xmlString, strlen(xmlString), kCFAllocatorNull); require_action(bodyData != NULL, CFDataCreateWithBytesNoCopy, error = EIO); /* send request to the server and get the response */ error = send_transaction(uid, urlRef, CFSTR("PROPFIND"), bodyData, headerCount, headers, TRUE, &responseBuffer, &count, NULL); if ( !error ) { int num_entries; /* parse responseBuffer to get the number of entries */ error = parse_file_count((char *)responseBuffer, (int)count, &num_entries); if ( !error ) { if (num_entries > 1) { /* * An empty directory will have just one entry for itself as far * as the server is concerned. If there is more than that we need * to return ENOTEMPTY since we don't allow deleting directories * which have anything in them. */ error = ENOTEMPTY; } } /* free the response buffer */ free(responseBuffer); } /* release the message body */ CFRelease(bodyData); CFDataCreateWithBytesNoCopy: return ( error ); } /******************************************************************************/ int network_lookup( uid_t uid, /* -> uid of the user making the request */ struct node_entry *node, /* -> parent node */ char *name, /* -> filename to find */ size_t name_length, /* -> length of name */ struct stat *statbuf) /* <- stat information is returned in this buffer */ { int error; CFURLRef urlRef; error = 0; /* create a CFURL to the node plus name */ urlRef = create_cfurl_from_node(node, name, name_length); require_action_quiet(urlRef != NULL, create_cfurl_from_node, error = EIO); /* let network_stat do the rest of the work */ error = network_stat(uid, urlRef, statbuf); CFRelease(urlRef); create_cfurl_from_node: return ( error ); } /******************************************************************************/ int network_getattr( uid_t uid, /* -> uid of the user making the request */ struct node_entry *node, /* -> node */ struct stat *statbuf) /* <- stat information is returned in this buffer */ { int error; CFURLRef urlRef; error = 0; /* create a CFURL to the node */ urlRef = create_cfurl_from_node(node, NULL, 0); require_action_quiet(urlRef != NULL, create_cfurl_from_node, error = EIO); /* let network_stat do the rest of the work */ error = network_stat(uid, urlRef, statbuf); CFRelease(urlRef); create_cfurl_from_node: return ( error ); } /******************************************************************************/ /* NOTE: this will do both the OPTIONS and the PROPFIND. */ /* NOTE: if webdavfs is changed to support advlocks, then * server_mount_flags parameter is not needed. */ /* * The only errors expected from network_mount are: * ECANCELED - the user could not authenticate and cancelled the mount * ENODEV - we could not connect to the server (bad URL, server down, etc.) */ int network_mount( uid_t uid, /* -> uid of the user making the request */ int *server_mount_flags) /* <- flags to OR in with mount flags (i.e., MNT_RDONLY) */ { int error; CFURLRef urlRef; int dav_level; struct stat statbuf; urlRef = baseURL; error = network_getDAVLevel(uid, urlRef, &dav_level); if ( error == 0 ) { if ( dav_level > 2 ) { /* pin it to 2 -- the highest we care about */ dav_level = 2; } switch (dav_level) { case 1: *server_mount_flags |= MNT_RDONLY; break; case 2: /* DAV supports LOCKs */ break; default: debug_string("network_mount: WebDAV protocol not supported"); error = ENODEV; break; } if ( error == 0 ) { error = network_stat(uid, urlRef, &statbuf); if ( error ) { if (error != EACCES) { debug_string("network_mount: PROPFIND failed"); error = ENODEV; } else { debug_string("network_mount: mount cancelled by user"); error = ECANCELED; } } else if ( !S_ISDIR(statbuf.st_mode) ) { /* the PROFIND was successful, but the URL was to a file, not a collection */ debug_string("network_mount: URL is not a collection resource (directory)"); error = ENODEV; } } } else { if ( error != EACCES ) { debug_string("network_mount: OPTIONS failed"); error = ENODEV; } else { debug_string("network_mount: mount cancelled by user"); error = ECANCELED; } } return ( error ); } /******************************************************************************/ int network_finish_download( struct node_entry *node, struct ReadStreamRec *readStreamRecPtr) { void * buffer; CFIndex bytesRead; /* malloc a buffer */ buffer = malloc(BODY_BUFFER_SIZE); require(buffer != NULL, malloc_buffer); while ( 1 ) { /* were we asked to terminate the download? */ if ( (node->file_status & WEBDAV_DOWNLOAD_TERMINATED) != 0 ) { /* * Call CFReadStreamRead one more time. This may block but this is * the only way to know at termination if the download was * finished, or if the download was incomplete. */ bytesRead = CFReadStreamRead(readStreamRecPtr->readStreamRef, (UInt8 *)buffer, 1); /* make it a small read */ if ( bytesRead == 0 ) { /* * The download was complete the last time through the loop. * Break and let the caller mark this download finished. */ break; } else { /* * The download wasn't complete the last time through the loop. * Throw out these bytes (we'll get them if the file is reopened) * and let the caller mark this download aborted. */ goto terminated; } } bytesRead = CFReadStreamRead(readStreamRecPtr->readStreamRef, (UInt8 *)buffer, BODY_BUFFER_SIZE); if ( bytesRead > 0 ) { require(write(node->file_fd, buffer, (size_t)bytesRead) == (ssize_t)bytesRead, write); } else if ( bytesRead == 0 ) { /* there are no more bytes to read */ break; } else { CFStreamError streamError; streamError = CFReadStreamGetError(readStreamRecPtr->readStreamRef); syslog(LOG_ERR,"network_finish_download: CFStreamError: domain %ld, error %ld", streamError.domain, streamError.error); goto CFReadStreamRead; break; } }; free(buffer); if ( readStreamRecPtr->connectionClose ) { /* close and release the stream */ CFReadStreamClose(readStreamRecPtr->readStreamRef); CFRelease(readStreamRecPtr->readStreamRef); readStreamRecPtr->readStreamRef = NULL; } /* make this ReadStreamRec is available again */ release_ReadStreamRec(readStreamRecPtr); return ( 0 ); terminated: write: CFReadStreamRead: free(buffer); malloc_buffer: /* close and release the read stream on errors */ CFReadStreamClose(readStreamRecPtr->readStreamRef); CFRelease(readStreamRecPtr->readStreamRef); readStreamRecPtr->readStreamRef = NULL; /* make this ReadStreamRec is available again */ release_ReadStreamRec(readStreamRecPtr); return ( EIO ); } /******************************************************************************/ int network_open( uid_t uid, /* -> uid of the user making the request */ struct node_entry *node, /* -> node to open */ int write_access) /* -> open requires write access */ { int error; int ask_server; if ( !write_access ) { if ( ((node->file_status & WEBDAV_DOWNLOAD_STATUS_MASK) == WEBDAV_DOWNLOAD_FINISHED) && !NODE_FILE_INVALID(node) ) { /* OK by our simple heuristics */ /* the file was completely downloaded very recently, skip the server check */ ask_server = FALSE; } else { /* attempt to retrieve file contents from the attributes_cache */ if ( get_from_attributes_cache(node, uid) ) { /* the file contents were retrieved from the attributes_cache */ ask_server = FALSE; } else { /* we need to ask the server */ ask_server = TRUE; } } } else { /* we must check with server when opening with write access */ ask_server = TRUE; } if ( ask_server ) { CFURLRef urlRef; CFHTTPMessageRef message; CFHTTPMessageRef responseRef; UInt32 statusCode; UInt32 auth_generation; int retryTransaction; error = 0; message = NULL; responseRef = NULL; statusCode = 0; auth_generation = 0; retryTransaction = TRUE; /* create a CFURL to the node */ urlRef = create_cfurl_from_node(node, NULL, 0); require_action_quiet(urlRef != NULL, create_cfurl_from_node, error = EIO); /* the transaction/authentication loop */ do { /* release message if left from previous loop */ if ( message != NULL ) { CFRelease(message); message = NULL; } /* create a CFHTTP message object */ message = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), urlRef, kCFHTTPVersion1_1); require_action(message != NULL, CFHTTPMessageCreateRequest, error = EIO); /* Change the User-Agent header */ CFHTTPMessageSetHeaderFieldValue(message, CFSTR("User-Agent"), userAgentHeaderValue); /* add the X-Source-Id header if needed */ if ( X_Source_Id_HeaderValue != NULL ) { CFHTTPMessageSetHeaderFieldValue(message, CFSTR("X-Source-Id"), X_Source_Id_HeaderValue); } /* add other HTTP headers */ CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Accept"), CFSTR("*/*")); /* * If the status isn't WEBDAV_DOWNLOAD_NEVER, we need to add some conditional headers. * If adding the headers fails, then we can continue without them -- it'll just * force the file to be downloaded. */ if ( (node->file_status & WEBDAV_DOWNLOAD_STATUS_MASK) != WEBDAV_DOWNLOAD_NEVER ) { CFStringRef httpDateString; httpDateString = CFStringCreateRFC2616DateStringWithTimeT(node->file_last_modified); if ( httpDateString != NULL ) { if ( (node->file_status & WEBDAV_DOWNLOAD_STATUS_MASK) == WEBDAV_DOWNLOAD_FINISHED ) { CFHTTPMessageSetHeaderFieldValue(message, CFSTR("If-Modified-Since"), httpDateString); } else { off_t currentLength; CFStringRef currentLengthString; currentLength = lseek(node->file_fd, 0LL, SEEK_END); if ( currentLength != -1 ) { /* create a string with the file length for the Range header */ currentLengthString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("bytes=%qd-"), currentLength); /* CFReadStreamCreateForStreamedHTTPRequest will use chunked transfer-encoding if the Content-Length header cannot be provided */ if ( currentLengthString != NULL ) { CFHTTPMessageSetHeaderFieldValue(message, CFSTR("If-Range"), httpDateString); CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Range"), currentLengthString); CFRelease(currentLengthString); } } } CFRelease(httpDateString); } } /* apply credentials (if any) */ /* * statusCode will be 401 or 407 and responseRef will not be NULL if we've already been through the loop; * statusCode will be 0 and responseRef will be NULL if this is the first time through. */ error = authcache_apply(uid, message, statusCode, responseRef, &auth_generation); if ( error != 0 ) { break; } /* stream_transaction returns responseRef so release it if left from previous loop */ if ( responseRef != NULL ) { CFRelease(responseRef); responseRef = NULL; } /* now that everything's ready to send, send it */ error = stream_get_transaction(message, &retryTransaction, node, &responseRef); if ( error == EAGAIN ) { statusCode = 0; /* responseRef will be left NULL on retries */ } else { if ( error != 0 ) { break; } /* get the status code */ statusCode = CFHTTPMessageGetResponseStatusCode(responseRef); } } while ( error == EAGAIN || statusCode == 401 || statusCode == 407 ); CFHTTPMessageCreateRequest: if ( error == 0 ) { /* 304 Not Modified means the cache file is still good, so make it 200 before translating */ if ( statusCode == 304 ) { statusCode = 200; } error = translate_status_to_error(statusCode); if ( error == 0 ) { /* * when we get here with no errors, then we need to tell the authcache the * transaction worked so it can mark the credentials valid and, if needed * add the credentials to the keychain. If the auth_generation changed, then * another transaction updated the authcache element after we got it. */ (void) authcache_valid(uid, message, auth_generation); time(&node->file_validated_time); { CFStringRef headerRef; const char *field_value; char buffer[4096]; char *file_entity_tag; headerRef = CFHTTPMessageCopyHeaderFieldValue(responseRef, CFSTR("Last-Modified")); if ( headerRef ) { node->file_last_modified = DateStringToTime(headerRef); CFRelease(headerRef); } headerRef = CFHTTPMessageCopyHeaderFieldValue(responseRef, CFSTR("ETag")); if ( headerRef ) { field_value = CFStringGetCStringPtr(headerRef, kCFStringEncodingUTF8); if ( field_value == NULL ) { if ( CFStringGetCString(headerRef, buffer, 4096, kCFStringEncodingUTF8) ) { field_value = buffer; } } if ( field_value != NULL ) { file_entity_tag = malloc(strlen(field_value) + 1); if ( file_entity_tag != NULL ) { strcpy(file_entity_tag, field_value); if ( node->file_entity_tag != NULL ) { free(node->file_entity_tag); } node->file_entity_tag = file_entity_tag; } } CFRelease(headerRef); } } } } if ( message != NULL ) { CFRelease(message); } if ( responseRef != NULL ) { CFRelease(responseRef); } CFRelease(urlRef); create_cfurl_from_node: ; } else { /* what we have cached is OK */ error = 0; } return ( error ); } /******************************************************************************/ int network_statfs( uid_t uid, /* -> uid of the user making the request */ struct node_entry *node, /* -> root node */ struct statfs *fs_attr) /* <- file system information */ { int error; CFURLRef urlRef; UInt8 *responseBuffer; CFIndex count; CFDataRef bodyData; const char xmlString[] = "\n" "\n" "\n" "\n" "\n" "\n" "\n"; /* the 3 headers */ CFIndex headerCount = 3; struct HeaderFieldValue headers[] = { { CFSTR("Accept"), CFSTR("*/*") }, { CFSTR("Content-Type"), CFSTR("text/xml") }, { CFSTR("Depth"), CFSTR("0") } }; /* create a CFURL to the node */ urlRef = create_cfurl_from_node(node, NULL, 0); require_action_quiet(urlRef != NULL, create_cfurl_from_node, error = EIO); /* create a CFDataRef with the xml that is our message body */ bodyData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, xmlString, strlen(xmlString), kCFAllocatorNull); require_action(bodyData != NULL, CFDataCreateWithBytesNoCopy, error = EIO); /* send request to the server and get the response */ error = send_transaction(uid, urlRef, CFSTR("PROPFIND"), bodyData, headerCount, headers, TRUE, &responseBuffer, &count, NULL); if ( !error ) { /* parse responseBuffer to get the statfsbuf */ error = parse_statfs((char *)responseBuffer, (int)count, fs_attr); /* free the response buffer */ free(responseBuffer); } /* release the message body */ CFRelease(bodyData); CFDataCreateWithBytesNoCopy: CFRelease(urlRef); create_cfurl_from_node: return ( error ); } /******************************************************************************/ int network_create( uid_t uid, /* -> uid of the user making the request */ struct node_entry *node, /* -> parent node */ char *name, /* -> file name to create */ size_t name_length, /* -> length of name */ time_t *creation_date) /* <- date of the creation */ { int error; CFURLRef urlRef; CFHTTPMessageRef response; /* the 1 header */ CFIndex headerCount = 1; struct HeaderFieldValue headers[] = { { CFSTR("Accept"), CFSTR("*/*") } }; *creation_date = -1; /* create a CFURL to the node plus name */ urlRef = create_cfurl_from_node(node, name, name_length); require_action_quiet(urlRef != NULL, create_cfurl_from_node, error = EIO); /* send request to the server and get the response */ error = send_transaction(uid, urlRef, CFSTR("PUT"), NULL, headerCount, headers, FALSE, NULL, NULL, &response); if ( !error ) { CFStringRef dateHeaderRef; dateHeaderRef = CFHTTPMessageCopyHeaderFieldValue(response, CFSTR("Date")); if ( dateHeaderRef != NULL ) { *creation_date = DateStringToTime(dateHeaderRef); CFRelease(dateHeaderRef); } /* release the response buffer */ CFRelease(response); } CFRelease(urlRef); create_cfurl_from_node: return ( error ); } /******************************************************************************/ int network_fsync( uid_t uid, /* -> uid of the user making the request */ struct node_entry *node) /* -> node to sync with server */ { int error; CFURLRef urlRef; CFHTTPMessageRef message; CFHTTPMessageRef responseRef; UInt32 statusCode; UInt32 auth_generation; CFStringRef lockTokenRef; time_t file_last_modified; char *file_entity_tag; int retryTransaction; error = 0; file_last_modified = -1; file_entity_tag = NULL; message = NULL; responseRef = NULL; statusCode = 0; auth_generation = 0; retryTransaction = TRUE; /* create a CFURL to the node */ urlRef = create_cfurl_from_node(node, NULL, 0); require_action_quiet(urlRef != NULL, create_cfurl_from_node, error = EIO); /* the transaction/authentication loop */ do { /* release message if left from previous loop */ if ( message != NULL ) { CFRelease(message); message = NULL; } /* create a CFHTTP message object */ message = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("PUT"), urlRef, kCFHTTPVersion1_1); require_action(message != NULL, CFHTTPMessageCreateRequest, error = EIO); /* Change the User-Agent header */ CFHTTPMessageSetHeaderFieldValue(message, CFSTR("User-Agent"), userAgentHeaderValue); /* add the X-Source-Id header if needed */ if ( X_Source_Id_HeaderValue != NULL ) { CFHTTPMessageSetHeaderFieldValue(message, CFSTR("X-Source-Id"), X_Source_Id_HeaderValue); } /* add other HTTP headers */ CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Accept"), CFSTR("*/*")); /* is there a lock token? */ if ( node->file_locktoken != NULL ) { /* in the unlikely event that this fails, the PUT may fail */ lockTokenRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("(<%s>)"), node->file_locktoken); if ( lockTokenRef != NULL ) { CFHTTPMessageSetHeaderFieldValue(message, CFSTR("If"), lockTokenRef ); CFRelease(lockTokenRef); lockTokenRef = NULL; } } else { lockTokenRef = NULL; } /* apply credentials (if any) */ /* * statusCode will be 401 or 407 and responseRef will not be NULL if we've already been through the loop; * statusCode will be 0 and responseRef will be NULL if this is the first time through. */ error = authcache_apply(uid, message, statusCode, responseRef, &auth_generation); if ( error != 0 ) { break; } /* stream_transaction returns responseRef so release it if left from previous loop */ if ( responseRef != NULL ) { CFRelease(responseRef); responseRef = NULL; } /* now that everything's ready to send, send it */ error = stream_transaction_from_file(message, node->file_fd, &retryTransaction, &responseRef); if ( error == EAGAIN ) { statusCode = 0; /* responseRef will be left NULL on retries */ } else { if ( error != 0 ) { break; } /* get the status code */ statusCode = CFHTTPMessageGetResponseStatusCode(responseRef); } } while ( error == EAGAIN || statusCode == 401 || statusCode == 407 ); CFHTTPMessageCreateRequest: if ( error == 0 ) { error = translate_status_to_error(statusCode); if ( error == 0 ) { /* * when we get here with no errors, then we need to tell the authcache the * transaction worked so it can mark the credentials valid and, if needed * add the credentials to the keychain. If the auth_generation changed, then * another transaction updated the authcache element after we got it. */ (void) authcache_valid(uid, message, auth_generation); { CFStringRef headerRef; const char *field_value; char buffer[4096]; headerRef = CFHTTPMessageCopyHeaderFieldValue(responseRef, CFSTR("Last-Modified")); if ( headerRef ) { file_last_modified = DateStringToTime(headerRef); CFRelease(headerRef); } headerRef = CFHTTPMessageCopyHeaderFieldValue(responseRef, CFSTR("ETag")); if ( headerRef ) { field_value = CFStringGetCStringPtr(headerRef, kCFStringEncodingUTF8); if ( field_value == NULL ) { if ( CFStringGetCString(headerRef, buffer, 4096, kCFStringEncodingUTF8) ) { field_value = buffer; } } if ( field_value != NULL ) { file_entity_tag = malloc(strlen(field_value) + 1); if ( file_entity_tag != NULL ) { strcpy(file_entity_tag, field_value); } } CFRelease(headerRef); } } } } if ( message != NULL ) { CFRelease(message); } if ( responseRef != NULL ) { CFRelease(responseRef); } if ( (file_last_modified == -1) && (file_entity_tag == NULL) ) { UInt8 *responseBuffer; CFIndex count; CFDataRef bodyData; /* the xml for the message body */ const char xmlString[] = "\n" "\n" "\n" "\n" "\n" "\n" "\n"; /* the 3 headers */ CFIndex headerCount = 3; struct HeaderFieldValue headers[] = { { CFSTR("Accept"), CFSTR("*/*") }, { CFSTR("Content-Type"), CFSTR("text/xml") }, { CFSTR("Depth"), CFSTR("0") } }; error = 0; responseBuffer = NULL; /* create the message body with the xml */ bodyData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, xmlString, strlen(xmlString), kCFAllocatorNull); require_action(bodyData != NULL, CFDataCreateWithBytesNoCopy, error = EIO); /* send request to the server and get the response */ error = send_transaction(uid, urlRef, CFSTR("PROPFIND"), bodyData, headerCount, headers, TRUE, &responseBuffer, &count, NULL); if ( !error ) { /* parse responseBuffer to get file_last_modified and/or file_entity_tag */ error = parse_cachevalidators((char *)responseBuffer, count, &file_last_modified, &file_entity_tag); /* free the response buffer */ free(responseBuffer); } /* release the message body */ CFRelease(bodyData); CFDataCreateWithBytesNoCopy: ; } CFRelease(urlRef); create_cfurl_from_node: if ( !error ) { node->file_last_modified = file_last_modified; if ( node->file_entity_tag != NULL ) { free(node->file_entity_tag); } node->file_entity_tag = file_entity_tag; } return ( error ); } /******************************************************************************/ static int network_delete( uid_t uid, /* -> uid of the user making the request */ CFURLRef urlRef, /* -> url to delete */ struct node_entry *node, /* -> node to remove on the server */ time_t *remove_date) /* <- date of the removal */ { int error; CFStringRef lockTokenRef; CFHTTPMessageRef response; /* possibly 2 headers */ CFIndex headerCount; struct HeaderFieldValue headers[] = { { CFSTR("Accept"), CFSTR("*/*") }, { CFSTR("If"), NULL } }; *remove_date = -1; if ( node->file_locktoken != NULL ) { /* in the unlikely event that this fails, the DELETE will fail */ lockTokenRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("(<%s>)"), node->file_locktoken); if ( lockTokenRef != NULL ) { headerCount = 2; headers[1].value = lockTokenRef; } else { headerCount = 1; } } else { lockTokenRef = NULL; headerCount = 1; } /* send request to the server and get the response */ error = send_transaction(uid, urlRef, CFSTR("DELETE"), NULL, headerCount, headers, FALSE, NULL, NULL, &response); if ( !error ) { CFStringRef dateHeaderRef; dateHeaderRef = CFHTTPMessageCopyHeaderFieldValue(response, CFSTR("Date")); if ( dateHeaderRef != NULL ) { *remove_date = DateStringToTime(dateHeaderRef); CFRelease(dateHeaderRef); } /* release the response buffer */ CFRelease(response); } if ( lockTokenRef != NULL ) { CFRelease(lockTokenRef); } return ( error ); } /******************************************************************************/ int network_remove( uid_t uid, /* -> uid of the user making the request */ struct node_entry *node, /* -> file node to remove on the server */ time_t *remove_date) /* <- date of the removal */ { int error; CFURLRef urlRef; error = 0; /* create a CFURL to the node */ urlRef = create_cfurl_from_node(node, NULL, 0); require_action_quiet(urlRef != NULL, create_cfurl_from_node, error = EIO); /* let network_delete do the rest of the work */ error = network_delete(uid, urlRef, node, remove_date); CFRelease(urlRef); create_cfurl_from_node: return ( error ); } /******************************************************************************/ int network_rmdir( uid_t uid, /* -> uid of the user making the request */ struct node_entry *node, /* -> directory node to remove on the server */ time_t *remove_date) /* <- date of the removal */ { int error; CFURLRef urlRef; error = 0; /* create a CFURL to the node */ urlRef = create_cfurl_from_node(node, NULL, 0); require_action_quiet(urlRef != NULL, create_cfurl_from_node, error = EIO); /* make sure the directory is empty */ error = network_dir_is_empty(uid, urlRef); if ( !error ) { /* let network_delete do the rest of the work */ error = network_delete(uid, urlRef, node, remove_date); } CFRelease(urlRef); create_cfurl_from_node: return ( error ); } /******************************************************************************/ /* NOTE: this will call network_dir_is_empty() if to_node is a valid and a directory. */ int network_rename( uid_t uid, /* -> uid of the user making the request */ struct node_entry *from_node, /* node to move */ struct node_entry *to_node, /* node to move over (ignored if NULL) */ struct node_entry *to_dir_node, /* directory node move into (ignored if to_node != NULL) */ char *to_name, /* new name for the object (ignored if to_node != NULL) */ size_t to_name_length, /* length of to_name (ignored if to_node != NULL) */ time_t *rename_date) /* <- date of the rename */ { int error; CFURLRef urlRef; CFURLRef destinationUrlRef; CFStringRef destinationRef; CFHTTPMessageRef response; /* the 2 headers */ CFIndex headerCount = 2; struct HeaderFieldValue headers[] = { { CFSTR("Accept"), CFSTR("*/*") }, { CFSTR("Destination"), NULL } }; *rename_date = -1; urlRef = NULL; destinationUrlRef = NULL; destinationRef = NULL; /* create a CFURL to the from_node */ urlRef = create_cfurl_from_node(from_node, NULL, 0); require_action_quiet(urlRef != NULL, exit, error = EIO); /* create the URL for the destination */ if ( to_node != NULL ) { /* use to_node */ /* create a CFURL to the to_node */ destinationUrlRef = create_cfurl_from_node(to_node, NULL, 0); require_action_quiet(destinationUrlRef != NULL, exit, error = EIO); /* if source and destination are equal, there's nothing to do so leave with no error */ require_action_quiet( !CFEqual(urlRef, destinationUrlRef), exit, error = 0); /* is the destination a directory? */ if ( to_node->node_type == WEBDAV_DIR_TYPE ) { /* make sure the directory is empty before attempting to move over it */ error = network_dir_is_empty(uid, destinationUrlRef); require_noerr_quiet(error, exit); } } else { /* use to_dir_node and to_name */ /* create a CFURL to the to_dir_node plus name */ destinationUrlRef = create_cfurl_from_node(to_dir_node, to_name, to_name_length); require_action_quiet(destinationUrlRef != NULL, exit, error = EIO); /* if source and destination are equal, there's nothing to do so leave with no error */ require_action_quiet( !CFEqual(urlRef, destinationUrlRef), exit, error = 0); } destinationRef = CFURLGetString(destinationUrlRef); require_action(destinationRef != NULL, exit, error = EIO); headers[1].value = destinationRef; /* done with that mess... */ /* send request to the server and get the response */ error = send_transaction(uid, urlRef, CFSTR("MOVE"), NULL, headerCount, headers, FALSE, NULL, NULL, &response); if ( !error ) { CFStringRef dateHeaderRef; dateHeaderRef = CFHTTPMessageCopyHeaderFieldValue(response, CFSTR("Date")); if ( dateHeaderRef != NULL ) { *rename_date = DateStringToTime(dateHeaderRef); CFRelease(dateHeaderRef); } /* release the response buffer */ CFRelease(response); } exit: if ( destinationUrlRef != NULL ) { CFRelease(destinationUrlRef); } if ( urlRef != NULL ) { CFRelease(urlRef); } return ( error ); } /******************************************************************************/ int network_lock( uid_t uid, /* -> uid of the user making the request (ignored if refreshing) */ int refresh, /* -> if FALSE, we're getting the lock (for uid); if TRUE, we're refreshing the lock */ struct node_entry *node) /* -> node to get/renew server lock on */ { int error; CFURLRef urlRef; UInt8 *responseBuffer; CFIndex count; CFDataRef bodyData; const char xmlString[] = "\n" "\n" "\n" "\n" "\n" "http://www.apple.com/webdav_fs/\n" /* this used to be "default-owner" instead of the url */ "\n" "\n"; /* the 3 headers */ CFIndex headerCount = 4; struct HeaderFieldValue headers[] = { { CFSTR("Accept"), CFSTR("*/*") }, { CFSTR("Depth"), CFSTR("0") }, { CFSTR("Timeout"), NULL }, { CFSTR("Content-Type"), NULL }, { CFSTR("If"), NULL } }; CFStringRef timeoutSpecifierRef; CFStringRef lockTokenRef; /* create a CFURL to the node */ urlRef = create_cfurl_from_node(node, NULL, 0); require_action_quiet(urlRef != NULL, create_cfurl_from_node, error = EIO); timeoutSpecifierRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("Second-%s"), gtimeout_string); require_action(timeoutSpecifierRef != NULL, CFStringCreateWithFormat_timeoutSpecifierRef, error = EIO); headers[2].value = timeoutSpecifierRef; if ( refresh ) { /* if refreshing, use the uid associated with the file_locktoken */ uid = node->file_locktoken_uid; /* if refreshing the lock, there's no message body */ bodyData = NULL; headerCount = 5; headers[3].value = CFSTR("text/xml"); lockTokenRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("(<%s>)"), node->file_locktoken); require_action(lockTokenRef != NULL, CFStringCreateWithFormat_lockTokenRef, error = EIO); headers[4].value = lockTokenRef; } else { lockTokenRef = NULL; /* create a CFDataRef with the xml that is our message body */ bodyData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, xmlString, strlen(xmlString), kCFAllocatorNull); require_action(bodyData != NULL, CFDataCreateWithBytesNoCopy, error = EIO); headerCount = 4; headers[3].value = CFSTR("text/xml; charset=\"utf-8\""); } /* send request to the server and get the response */ error = send_transaction(uid, urlRef, CFSTR("LOCK"), bodyData, headerCount, headers, FALSE, &responseBuffer, &count, NULL); if ( !error ) { char *locktoken; /* parse responseBuffer to get the lock token */ error = parse_lock(responseBuffer, count, &locktoken); if ( !error ) { char *old_locktoken; old_locktoken = node->file_locktoken; node->file_locktoken = locktoken; if ( old_locktoken != NULL ) { free(old_locktoken); } /* file_locktoken_uid is already set if refreshing */ if ( !refresh ) { node->file_locktoken_uid = uid; } } /* free the response buffer */ free(responseBuffer); } if ( bodyData != NULL ) { CFRelease(bodyData); } if ( lockTokenRef != NULL ) { CFRelease(lockTokenRef); } CFDataCreateWithBytesNoCopy: CFStringCreateWithFormat_lockTokenRef: CFRelease(timeoutSpecifierRef); CFStringCreateWithFormat_timeoutSpecifierRef: CFRelease(urlRef); create_cfurl_from_node: return ( error ); } /******************************************************************************/ int network_unlock( struct node_entry *node) /* -> node to unlock on server */ { int error; CFURLRef urlRef; CFStringRef lockTokenRef; /* the 2 headers */ CFIndex headerCount = 2; struct HeaderFieldValue headers[] = { { CFSTR("Accept"), CFSTR("*/*") }, { CFSTR("Lock-Token"), NULL } }; /* create a CFURL to the node */ urlRef = create_cfurl_from_node(node, NULL, 0); require_action_quiet(urlRef != NULL, create_cfurl_from_node, error = EIO); /* in the unlikely event that this fails, the DELETE will fail */ lockTokenRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("<%s>"), node->file_locktoken); require_action_quiet(lockTokenRef != NULL, CFStringCreateWithFormat, error = EIO); headers[1].value = lockTokenRef; /* send request to the server and get the response */ /* Note: we use the credentials of the user than obtained the LOCK */ error = send_transaction(node->file_locktoken_uid, urlRef, CFSTR("UNLOCK"), NULL, headerCount, headers, FALSE, NULL, NULL, NULL); CFRelease(lockTokenRef); CFStringCreateWithFormat: CFRelease(urlRef); create_cfurl_from_node: free(node->file_locktoken); node->file_locktoken_uid = 0; node->file_locktoken = NULL; return ( error ); } /******************************************************************************/ int network_readdir( uid_t uid, /* -> uid of the user making the request */ int cache, /* -> if TRUE, perform additional caching */ struct node_entry *node) /* -> directory node to read */ { int error; CFURLRef urlRef; UInt8 *responseBuffer; CFIndex count; CFDataRef bodyData; const char xmlString[] = "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n"; const char xmlStringCache[] = "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n"; /* the 3 headers */ CFIndex headerCount = 3; struct HeaderFieldValue headers[] = { { CFSTR("Accept"), CFSTR("*/*") }, { CFSTR("Content-Type"), CFSTR("text/xml") }, { CFSTR("Depth"), CFSTR("1") } }; /* create a CFURL to the node */ urlRef = create_cfurl_from_node(node, NULL, 0); require_action_quiet(urlRef != NULL, create_cfurl_from_node, error = EIO); /* create a CFDataRef with the xml that is our message body */ bodyData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (cache ? xmlStringCache : xmlString), strlen(cache ? xmlStringCache : xmlString), kCFAllocatorNull); require_action(bodyData != NULL, CFDataCreateWithBytesNoCopy, error = EIO); /* send request to the server and get the response */ error = send_transaction(uid, urlRef, CFSTR("PROPFIND"), bodyData, headerCount, headers, TRUE, &responseBuffer, &count, NULL); if ( !error ) { /* parse responseBuffer to create the directory file */ error = parse_opendir(responseBuffer, count, urlRef, uid, node); /* free the response buffer */ free(responseBuffer); } /* release the message body */ CFRelease(bodyData); CFDataCreateWithBytesNoCopy: CFRelease(urlRef); create_cfurl_from_node: return ( error ); } /******************************************************************************/ int network_mkdir( uid_t uid, /* -> uid of the user making the request */ struct node_entry *node, /* -> parent node */ char *name, /* -> directory name to create */ size_t name_length, /* -> length of name */ time_t *creation_date) /* <- date of the creation */ { int error; CFURLRef urlRef; CFHTTPMessageRef response; /* the 3 headers */ CFIndex headerCount = 1; struct HeaderFieldValue headers[] = { { CFSTR("Accept"), CFSTR("*/*") }, }; *creation_date = -1; /* create a CFURL to the node plus name */ urlRef = create_cfurl_from_node(node, name, name_length); require_action_quiet(urlRef != NULL, create_cfurl_from_node, error = EIO); /* send request to the server and get the response */ error = send_transaction(uid, urlRef, CFSTR("MKCOL"), NULL, headerCount, headers, FALSE, NULL, NULL, &response); if ( !error ) { CFStringRef dateHeaderRef; dateHeaderRef = CFHTTPMessageCopyHeaderFieldValue(response, CFSTR("Date")); if ( dateHeaderRef != NULL ) { *creation_date = DateStringToTime(dateHeaderRef); CFRelease(dateHeaderRef); } /* release the response buffer */ CFRelease(response); } CFRelease(urlRef); create_cfurl_from_node: return ( error ); } /******************************************************************************/ int network_read( uid_t uid, /* -> uid of the user making the request */ struct node_entry *node, /* -> node to read */ off_t offset, /* -> position within the file at which the read is to begin */ size_t count, /* -> number of bytes of data to be read */ char **buffer, /* <- buffer data was read into (allocated by network_read) */ size_t *actual_count) /* <- number of bytes actually read */ { int error; CFURLRef urlRef; UInt8 *responseBuffer; CFIndex responseCount; CFStringRef byteRangesSpecifierRef; /* the 2 headers -- the range value will be computed below */ CFIndex headerCount = 2; struct HeaderFieldValue headers[] = { { CFSTR("Accept"), CFSTR("*/*") }, { CFSTR("Range"), NULL } }; *buffer = NULL; *actual_count = 0; /* create a CFURL to the node */ urlRef = create_cfurl_from_node(node, NULL, 0); require_action_quiet(urlRef != NULL, create_cfurl_from_node, error = EIO); byteRangesSpecifierRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("bytes=%qd-%qd"), offset, offset + count - 1); require_action(byteRangesSpecifierRef != NULL, CFStringCreateWithFormat, error = EIO); headers[1].value = byteRangesSpecifierRef; /* send request to the server and get the response */ error = send_transaction(uid, urlRef, CFSTR("GET"), NULL, headerCount, headers, TRUE, &responseBuffer, &responseCount, NULL); if ( !error ) { if ( (size_t)responseCount > count ) { /* don't return more than we asked for */ responseCount = count; } *buffer = responseBuffer; *actual_count = responseCount; } CFRelease(byteRangesSpecifierRef); CFStringCreateWithFormat: CFRelease(urlRef); create_cfurl_from_node: return ( error ); } /******************************************************************************/