/* * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * "Portions Copyright (c) 1999 Apple Computer, Inc. All Rights * Reserved. 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 1.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.apple.com/publicsource 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 OR NON-INFRINGEMENT. Please see the * License for the specific language governing rights and limitations * under the License." * * @APPLE_LICENSE_HEADER_END@ */ /* @(#)webdav_parse.c * * (c) 1999 Apple Computer, Inc. All Rights Reserved * * * webdav_parse.c -- WebDAV front end to CF XML parser * * MODIFICATION HISTORY: * 19-JUL-99 Clark Warner File Creation */ #include #include #include #include #include #include "webdav_parse.h" #include "webdav_allocator.h" #include "webdav_memcache.h" #include "webdav_inode.h" #include "fetch.h" #include "pathnames.h" #include "../webdav_fs.kextproj/webdav_fs.kmodproj/vnops.h" #include "../webdav_fs.kextproj/webdav_fs.kmodproj/webdav.h" /*****************************************************************************/ extern time_t parse_http_date(char *string); extern webdav_memcache_header_t gmemcache_header; extern void percent_decode_in_place (char *uri); /*****************************************************************************/ void *parser_lookup_create(CFXMLParserRef parser, CFXMLNodeRef node, void *context) { webdav_parse_lookup_element_t * element_ptr = NULL; webdav_parse_lookup_element_t * list_ptr; webdav_parse_lookup_struct_t * struct_ptr = (webdav_parse_lookup_struct_t *)context; CFRange comparison_range; /* See if this is the resource type. If it is, malloc a lookup element and add it to the list. If not, return something but keep parsing */ #ifdef DEBUG_PARSE char buffer[256]; switch (CFXMLNodeGetTypeCode(node)) { case kCFXMLNodeTypeElement: fprintf(stderr, "Element Type"); break; case kCFXMLNodeTypeDocument: fprintf(stderr, "Document Type"); break; case kCFXMLNodeTypeWhitespace: fprintf(stderr, "WhiteSpace"); break; case kCFXMLNodeTypeText: fprintf(stderr, "TextType"); break; default: fprintf(stderr, "Unknown Type"); } CFStringGetCString(CFXMLNodeGetString(node), buffer, (CFIndex)256, kCFStringEncodingUTF8); fprintf(stderr, ": Element string is: %s\n", buffer); #endif /* Skip the first two bytes in the comparison since the namespace stuff can vary */ if (CFXMLNodeGetTypeCode(node) == kCFXMLNodeTypeElement) { /* First skip past the name space part to get to the part of the string we actually want to compare */ CFStringRef str = CFXMLNodeGetString(node); comparison_range = CFStringFind(str, CFSTR(":"), 0); comparison_range.location++; comparison_range.length = CFStringGetLength(str) - comparison_range.location; if ((CFStringCompareWithOptions(str, CFSTR("resourcetype"), comparison_range, kCFCompareCaseInsensitive)) == kCFCompareEqualTo) { element_ptr = malloc(sizeof(webdav_parse_lookup_element_t)); #ifdef DEBUG_PARSE fprintf(stderr, "parser_lookup_create: malloc element_ptr %d\n", (int)element_ptr); #endif /* Got no storage, return null so that ultimately parse lookup will barf */ if (!element_ptr) { return (NULL); } element_ptr->file_type = WEBDAV_FILE_TYPE; element_ptr->next = NULL; if (struct_ptr->head == NULL) { struct_ptr->head = element_ptr; } else { list_ptr = struct_ptr->tail; list_ptr->next = element_ptr; } struct_ptr->tail = element_ptr; element_ptr->next = NULL; } else { if ((CFStringCompareWithOptions(str, CFSTR("collection"), comparison_range, kCFCompareCaseInsensitive)) == kCFCompareEqualTo) { /* If we have a collection property, we should have an element ptr in the context already. If not, its an error and we should barf */ element_ptr = struct_ptr->tail; if (element_ptr) { element_ptr->file_type = WEBDAV_DIR_TYPE; } else { return (NULL); } } /* end if collection */ } /* end if found */ } /* Return the element ptr if we are at the element we care about otherwise return the struct ptr just to return something */ if (element_ptr) { return ((void *)element_ptr); } else { return ((void *)struct_ptr); } } /*****************************************************************************/ #define SUBSTITUTE_PHYSICAL_ENTITY(node, text_ptr, size) \ { \ if (CFXMLNodeGetTypeCode(node) == kCFXMLNodeTypeEntityReference) \ { \ if (!strcmp(text_ptr, "amp")) \ strcpy(text_ptr, "&"); \ else if (!strcmp(text_ptr, "lt")) \ strcpy(text_ptr, "<"); \ else if (!strcmp(text_ptr, "gt")) \ strcpy(text_ptr, ">"); \ else if (!strcmp(text_ptr, "apos")) \ strcpy(text_ptr, "'"); \ else if (!strcmp(text_ptr, "quot")) \ strcpy(text_ptr, """"); \ size = 1; \ } \ } void *parser_opendir_create(CFXMLParserRef parser, CFXMLNodeRef node, void *context) { webdav_parse_opendir_element_t * element_ptr = NULL; webdav_parse_opendir_element_t * list_ptr; webdav_parse_opendir_struct_t * struct_ptr = (webdav_parse_opendir_struct_t *)context; webdav_parse_opendir_return_t * return_ptr; webdav_parse_opendir_text_t * text_ptr; void *return_value; CFRange comparison_range; CFStringRef nodeString = CFXMLNodeGetString(node); #ifdef DEBUG_PARSE char buffer[257]; switch (CFXMLNodeGetTypeCode(node)) { case kCFXMLNodeTypeElement: fprintf(stderr, "Element Type"); break; case kCFXMLNodeTypeDocument: fprintf(stderr, "Document Type"); break; case kCFXMLNodeTypeWhitespace: fprintf(stderr, "WhiteSpace"); break; case kCFXMLNOdeTypeText: fprintf(stderr, "TextType"); break; default: fprintf(stderr, "Unknown Type"); } CFStringGetCString(CFXMLNodeGetString(node), buffer, (CFIndex)256, kCFStringEncodingUTF8); fprintf(stderr, ": Element string is: %s\n", buffer); #endif /* set up our return */ return_ptr = malloc(sizeof(webdav_parse_opendir_return_t)); #ifdef DEBUG_PARSE fprintf(stderr, "parser_opendir_create: malloc return_ptr %d\n", (int)return_ptr); #endif /* XXX Need to set up generic error mechanism in case * we can only partially make the tree */ if (!return_ptr) { struct_ptr->error = ENOMEM; return (NULL); } return_ptr->id = WEBDAV_OPENDIR_IGNORE; return_ptr->data_ptr = (void *)NULL; return_value = (void *)return_ptr; /* See if this is the resource type. If it is, malloc a lookup element and add it to the list. If not, return something but keep parsing */ switch (CFXMLNodeGetTypeCode(node)) { case kCFXMLNodeTypeElement: comparison_range = CFStringFind(nodeString, CFSTR(":"), 0); comparison_range.location++; comparison_range.length = CFStringGetLength(nodeString) - comparison_range.location; if (((CFStringCompareWithOptions(nodeString, CFSTR("href"), comparison_range, kCFCompareCaseInsensitive)) == kCFCompareEqualTo)) { element_ptr = malloc(sizeof(webdav_parse_opendir_element_t)); #ifdef DEBUG_PARSE fprintf(stderr, "parser_opendir_create: malloc element_ptr %d\n", (int)element_ptr); #endif if (!element_ptr) { #ifdef DEBUG_PARSE fprintf(stderr, "parser_opendir_create: free return_ptr on failed element_ptr malloc %d\n", (int)return_ptr); #endif free(return_ptr); struct_ptr->error = ENOMEM; return (NULL); } bzero(element_ptr, sizeof(webdav_parse_opendir_element_t)); return_ptr->id = WEBDAV_OPENDIR_ELEMENT; return_ptr->data_ptr = (void *)element_ptr; element_ptr->dir_data.d_type = DT_REG; element_ptr->dir_data.d_namlen = 0; element_ptr->dir_data.d_name_URI_length = 0; element_ptr->dir_data.d_reclen = sizeof(struct dirent); element_ptr->next = NULL; if (struct_ptr->head == NULL) { struct_ptr->head = element_ptr; } else { list_ptr = struct_ptr->tail; list_ptr->next = element_ptr; } struct_ptr->tail = element_ptr; element_ptr->next = NULL; } else { if (((CFStringCompareWithOptions(nodeString, CFSTR("collection"), comparison_range, kCFCompareCaseInsensitive)) == kCFCompareEqualTo)) { /* If we have a collection property, we should have an * element ptr in the context already. If not, its * an error and we should barf. Since we are parsing an * xml tree, we can rest assured that the last element * found (href) is the one have just seen a resource type * on and hence the one where collection is found and * trapped here */ element_ptr = struct_ptr->tail; if (element_ptr) { element_ptr->dir_data.d_type = DT_DIR; /* not interested in child of collection element. We can * can and should free the return_ptr in this case.*/ return_value = NULL; #ifdef DEBUG_PARSE fprintf(stderr, "parser_opendir_create: free return_ptr on collection %d\n", (int)return_ptr); #endif free(return_ptr); } } else { if (((CFStringCompareWithOptions(nodeString, CFSTR("getcontentlength"), comparison_range, kCFCompareCaseInsensitive)) == kCFCompareEqualTo)) { /* If we have size then mark up the element pointer so that the * child will know to parse the upcoming text size and store it. */ element_ptr = struct_ptr->tail; if (element_ptr) { return_ptr->id = WEBDAV_OPENDIR_ELEMENT_LENGTH; return_ptr->data_ptr = (void *)element_ptr; } } else { if (((CFStringCompareWithOptions(nodeString, CFSTR("getlastmodified"), comparison_range, kCFCompareCaseInsensitive)) == kCFCompareEqualTo)) { /* If we have size then mark up the element pointer so that the * child will know to parse the upcoming text size and store it. */ element_ptr = struct_ptr->tail; if (element_ptr) { return_ptr->id = WEBDAV_OPENDIR_ELEMENT_MODDATE; return_ptr->data_ptr = (void *)element_ptr; } } /* end if modified */ } /* end if length */ } /* end if collection */ } /* end if href */ break; case kCFXMLNodeTypeEntityReference: case kCFXMLNodeTypeText: case kCFXMLNodeTypeCDATASection: /* If it is a text element, create a structure and pass it back * we have no way of knowing if this is the text of an href in the * xml (what we are looking for) or some other random text. Set it * up and then addchild will figure it out by checking the type of * the child to add and the type of the parent. */ text_ptr = malloc(sizeof(webdav_parse_opendir_text_t)); #ifdef DEBUG_PARSE fprintf(stderr, "parser_opendir_create: malloc text_ptr %d\n", (int)text_ptr); #endif if (!text_ptr) { #ifdef DEBUG_PARSE fprintf(stderr, "parser_opendir_create: free return_ptr on failed text_ptr malloc %d\n", (int)return_ptr); #endif free(return_ptr); struct_ptr->error = ENOMEM; return (NULL); } /* Get the bytes out in UTF8 form */ if (CFStringGetBytes(nodeString, CFRangeMake(0, CFStringGetLength(nodeString)), kCFStringEncodingUTF8, /*stop on loss */ 0,/*not ext*/ 0, text_ptr->name, sizeof(text_ptr->name) - 1, &text_ptr->size) != CFStringGetLength(nodeString)) { #ifdef DEBUG_PARSE fprintf(stderr, "Get Length Says %d\n", (int)CFStringGetLength(nodeString)); fprintf(stderr, "Get Bytes Says %d\n", (int)CFStringGetBytes(nodeString, CFRangeMake(0, CFStringGetLength(desc->dataString)), kCFStringEncodingUTF8, 0, 0, text_ptr->name, sizeof(text_ptr->name) - 1, &text_ptr->size)); fprintf(stderr, "parser_opendir_create: free return ptr on name too long %d\n", (int)return_ptr); #endif free(return_ptr); struct_ptr->error = ENAMETOOLONG; return (NULL); } text_ptr->name[text_ptr->size] = '\0'; /* Workaround for lack of support for kCFXMLParserReplacePhysicalEntities option *** */ SUBSTITUTE_PHYSICAL_ENTITY(node, text_ptr->name, text_ptr->size); return_ptr->id = WEBDAV_OPENDIR_TEXT; return_ptr->data_ptr = text_ptr; break; default: break; } /* end switch */ return (return_value); } /*****************************************************************************/ void *parser_file_count_create(CFXMLParserRef parser, CFXMLNodeRef node, void *context) { CFRange comparison_range; #ifdef DEBUG_PARSE char buffer[257]; switch (CFXMLNodeGetTypeCode(node)) { case kCFXMLDataTypeElement: fprintf(stderr, "Element Type"); break; case kCFXMLDataTypeDocument: fprintf(stderr, "Document Type"); break; case kCFXMLDataTypeWhitespace: fprintf(stderr, "WhiteSpace"); break; case kCFXMLDataTypeText: fprintf(stderr, "TextType"); break; default: fprintf(stderr, "Unknown Type"); } CFStringGetCString(CFXMLNodeGetString(node), buffer, (CFIndex)256, kCFStringEncodingUTF8); fprintf(stderr, ": Element string is: %s\n", buffer); #endif /* Look for hrefs & count them. */ switch (CFXMLNodeGetTypeCode(node)) { case kCFXMLNodeTypeElement: { CFStringRef str = CFXMLNodeGetString(node); comparison_range = CFStringFind(str, CFSTR(":"), 0); comparison_range.location++; comparison_range.length = CFStringGetLength(str) - comparison_range.location; if (((CFStringCompareWithOptions(str, CFSTR("href"), comparison_range, kCFCompareCaseInsensitive)) == kCFCompareEqualTo)) { *((int *)context) += 1; } /* end if href */ break; } default: break; } /* end switch */ return ((void *)1); /* have to return something or the parser will stop parsing */ } /*****************************************************************************/ void *parser_stat_create(CFXMLParserRef parser, CFXMLNodeRef node, void *context) { void *return_val = (void *)WEBDAV_STAT_IGNORE; char *text_ptr; size_t size; CFRange comparison_range; CFStringRef nodeString = CFXMLNodeGetString(node); /* See if this is a type we are interested in If it is, we'll return the appropriate constant */ switch (CFXMLNodeGetTypeCode(node)) { case kCFXMLNodeTypeElement: comparison_range = CFStringFind(nodeString, CFSTR(":"), 0); comparison_range.location++; comparison_range.length = CFStringGetLength(nodeString) - comparison_range.location; if (((CFStringCompareWithOptions(nodeString, CFSTR("getcontentlength"), comparison_range, kCFCompareCaseInsensitive)) == kCFCompareEqualTo)) { return_val = (void *)WEBDAV_STAT_LENGTH; } else { if (((CFStringCompareWithOptions(nodeString, CFSTR("getlastmodified"), comparison_range, kCFCompareCaseInsensitive)) == kCFCompareEqualTo)) { return_val = (void *)WEBDAV_STAT_MODDATE; } else { if (((CFStringCompareWithOptions(nodeString, CFSTR("collection"), comparison_range, kCFCompareCaseInsensitive)) == kCFCompareEqualTo)) { /* It's a collection so set the type as VDIR */ ((struct vattr *)context)->va_type = VDIR; } /* end if collection */ } /* end of if-else mod date */ } /* end if-else length*/ break; case kCFXMLNodeTypeEntityReference: case kCFXMLNodeTypeText: case kCFXMLNodeTypeCDATASection: text_ptr = malloc(WEBDAV_MAX_STAT_SIZE); /* Get the bytes out in UTF8 form */ if (CFStringGetBytes(nodeString, CFRangeMake(0, CFStringGetLength(nodeString)), kCFStringEncodingUTF8, /*stop on loss */ 0,/*not ext*/ 0, text_ptr, WEBDAV_MAX_STAT_SIZE - 1, &size) != CFStringGetLength(nodeString)) { /* we could not get the name in the buffer so return nothing */ free(text_ptr); return_val = NULL; } else { /* null terminate the string */ text_ptr[size] = '\0'; /* Workaround for lack of support for kCFXMLParserReplacePhysicalEntities option *** */ SUBSTITUTE_PHYSICAL_ENTITY(node, text_ptr, size); return_val = (void *)text_ptr; } default: break; } /* end switch */ return (return_val); } /*****************************************************************************/ void *parser_statfs_create(CFXMLParserRef parser, CFXMLNodeRef node, void *context) { void *return_val = (void *)WEBDAV_STATFS_IGNORE; char *text_ptr; size_t size; CFRange comparison_range; CFStringRef nodeString = CFXMLNodeGetString(node); /* See if this is a type we are interested in If it is, we'll return the appropriate constant */ switch (CFXMLNodeGetTypeCode(node)) { case kCFXMLNodeTypeElement: comparison_range = CFStringFind(nodeString, CFSTR(":"), 0); comparison_range.location++; comparison_range.length = CFStringGetLength(nodeString) - comparison_range.location; if (((CFStringCompareWithOptions(nodeString, CFSTR("quota"), comparison_range, kCFCompareCaseInsensitive)) == kCFCompareEqualTo)) { return_val = (void *)WEBDAV_STATFS_QUOTA; } else { if (((CFStringCompareWithOptions(nodeString, CFSTR("quotaused"), comparison_range, kCFCompareCaseInsensitive)) == kCFCompareEqualTo)) { return_val = (void *)WEBDAV_STATFS_QUOTAUSED; } } break; case kCFXMLNodeTypeEntityReference: case kCFXMLNodeTypeText: case kCFXMLNodeTypeCDATASection: text_ptr = malloc(WEBDAV_MAX_STATFS_SIZE); /* Get the bytes out in UTF8 form */ if (CFStringGetBytes(nodeString, CFRangeMake(0, CFStringGetLength(nodeString)), kCFStringEncodingUTF8, /*stop on loss */ 0,/*not ext*/ 0, text_ptr, WEBDAV_MAX_STATFS_SIZE - 1, &size) != CFStringGetLength(nodeString)) { /* we could not get the name in the buffer so return nothing */ free(text_ptr); return_val = NULL; } else { /* null terminate the string */ text_ptr[size] = '\0'; /* Workaround for lack of support for kCFXMLParserReplacePhysicalEntities option *** */ SUBSTITUTE_PHYSICAL_ENTITY(node, text_ptr, size); return_val = (void *)text_ptr; } break; default: break; } /* end switch */ return (return_val); } /*****************************************************************************/ void *parser_lock_create(CFXMLParserRef parser, CFXMLNodeRef node, void *context) { void *return_val = (void *)WEBDAV_LOCK_CONTINUE; char *text_ptr; size_t text_size, string_size; CFRange comparison_range; CFStringRef nodeString = CFXMLNodeGetString(node); /* See if this is a type we are interested in If it is, we'll return the appropriate constant */ switch (CFXMLNodeGetTypeCode(node)) { case kCFXMLNodeTypeElement: comparison_range = CFStringFind(nodeString, CFSTR(":"), 0); comparison_range.location++; comparison_range.length = CFStringGetLength(nodeString) - comparison_range.location; if (((CFStringCompareWithOptions(nodeString, CFSTR("locktoken"), comparison_range, kCFCompareCaseInsensitive)) == kCFCompareEqualTo)) { *((char **)context) = (char *)WEBDAV_LOCK_TOKEN; } else { if (((CFStringCompareWithOptions(nodeString, CFSTR("href"), comparison_range, kCFCompareCaseInsensitive)) == kCFCompareEqualTo)) { if (*((char **)context) == (char *)WEBDAV_LOCK_TOKEN) { *((char **)context) = (char *)WEBDAV_LOCK_HREF; } else { *((char **)context) = (char *)NULL; } } } /* end if-else locktoken*/ break; case kCFXMLNodeTypeEntityReference: case kCFXMLNodeTypeText: case kCFXMLNodeTypeCDATASection: if (*((char **)context) == (char *)WEBDAV_LOCK_HREF) { /* Since the context is set to WEBDAV_LOCK_HREF, we have * found the token and we have found the href indicating * that the locktoken is coming so squirrel it away. */ text_size = CFStringGetLength(nodeString); text_ptr = malloc(text_size + 1); /* Get the bytes out in UTF8 form */ if (CFStringGetBytes(nodeString, CFRangeMake(0, text_size), kCFStringEncodingUTF8, /*stop on loss */ 0,/*not ext*/ 0, text_ptr, WEBDAV_MAX_STAT_SIZE - 1, &string_size) != text_size) { /* we could not get the name in the buffer so return nothing */ free(text_ptr); return_val = NULL; } else { /* null terminate the string */ text_ptr[string_size] = '\0'; /* Workaround for lack of support for kCFXMLParserReplacePhysicalEntities option *** */ SUBSTITUTE_PHYSICAL_ENTITY(node, text_ptr, string_size); *((char **)context) = text_ptr; /* Since we found what we are looking for, return NULL * to stop the parse. */ return_val = NULL; } } default: break; } /* end switch */ return (return_val); } /*****************************************************************************/ void parser_add(CFXMLParserRef parser, void *parent, void *child, void *context) { /* Add nothing and do nothing. We are not actually creating the tree */ return; } /*****************************************************************************/ void parser_opendir_add(CFXMLParserRef parser, void *parent, void *child, void *context) { webdav_parse_opendir_element_t * element_ptr; webdav_parse_opendir_return_t * parent_ptr = (webdav_parse_opendir_return_t *)parent; webdav_parse_opendir_return_t * child_ptr = (webdav_parse_opendir_return_t *)child; webdav_parse_opendir_text_t * text_ptr; webdav_parse_opendir_struct_t * struct_ptr = (webdav_parse_opendir_struct_t *)context; /* If the parent is one of our returned directory elements, and if this is a * text element, than copy the text into the name buffer provided we have room */ if (child_ptr->id == WEBDAV_OPENDIR_TEXT) { text_ptr = (webdav_parse_opendir_text_t *)child_ptr->data_ptr; switch (parent_ptr->id) { case WEBDAV_OPENDIR_ELEMENT: element_ptr = (webdav_parse_opendir_element_t *)parent_ptr->data_ptr; /* make sure the complete name will fit in the structure */ if ((element_ptr->dir_data.d_name_URI_length + text_ptr->size) <= (sizeof(element_ptr->dir_data.d_name) - 1)) { bcopy(text_ptr->name, &element_ptr->dir_data.d_name[element_ptr->dir_data.d_name_URI_length], text_ptr->size); element_ptr->dir_data.d_name_URI_length += text_ptr->size; } else { struct_ptr->error = ENAMETOOLONG; } break; case WEBDAV_OPENDIR_ELEMENT_LENGTH: element_ptr = (webdav_parse_opendir_element_t *)parent_ptr->data_ptr; element_ptr->statsize = atol(text_ptr->name); break; case WEBDAV_OPENDIR_ELEMENT_MODDATE: element_ptr = (webdav_parse_opendir_element_t *)parent_ptr->data_ptr; element_ptr->stattime.tv_sec = parse_http_date(text_ptr->name); element_ptr->stattime.tv_nsec = 0; if (element_ptr->stattime.tv_sec == -1) { element_ptr->stattime.tv_sec = 0; } break; default: break; } /* end of switch statement */ /* The text pointer is not an element that persists in the context and is * thus not freed by parse_opendir like element pointers are. Since we * allocated it in the create routine, we must free it here */ #ifdef DEBUG_PARSE fprintf(stderr, "parser_opendir_add: free text_ptr %d\n", (int)text_ptr); #endif free(text_ptr); } /* end of if it is our text element */ } /*****************************************************************************/ void parser_stat_add(CFXMLParserRef parser, void *parent, void *child, void *context) { char *text_ptr = (char *)child; struct vattr *statbuf = (struct vattr *)context; /* If the parent reflects one of our properties than the child must be the text pointer with the data we want */ switch ((int)parent) { case WEBDAV_STAT_LENGTH: /* the text pointer is the lenth in bytes so fill that in in the stat buffer */ if (text_ptr && (text_ptr != (char *)WEBDAV_STAT_IGNORE)) { statbuf->va_size = atol(text_ptr); free(text_ptr); } else { /* If we got a string set to null we couldnot fit the value in our buffer so the length is longer than our max. Set the value to -1 then. */ statbuf->va_size = (u_quad_t) - 1; } break; case WEBDAV_STAT_MODDATE: /* the text pointer is the date so translate it and get it into the stat buffer */ if (text_ptr && (text_ptr != (char *)WEBDAV_STAT_IGNORE)) { statbuf->va_mtime.tv_sec = parse_http_date(text_ptr); if (statbuf->va_mtime.tv_sec == -1) { statbuf->va_mtime.tv_sec = 0; } statbuf->va_mtime.tv_nsec = 0; statbuf->va_atime = statbuf->va_mtime; statbuf->va_ctime = statbuf->va_mtime; free(text_ptr); } else { /* If we got a string set to null we could not fit the value in our buffer leave it blank and the kernel will fill in to the best of its ability */ } break; default: /* if it is ignore or something else, check the child. If it is not a return value and isn't null, it's a text ptr so free it */ if (text_ptr && text_ptr != (char *)WEBDAV_STAT_IGNORE && text_ptr != (char *)WEBDAV_STAT_LENGTH && text_ptr != (char *)WEBDAV_STAT_MODDATE) { free(text_ptr); } break; } return; } /*****************************************************************************/ void parser_statfs_add(CFXMLParserRef parser, void *parent, void *child, void *context) { char *text_ptr = (char *)child; struct statfs *statfsbuf = (struct statfs *)context; /* If the parent reflects one of our properties than the child must be the text pointer with the data we want */ switch ((int)parent) { case WEBDAV_STATFS_QUOTA: /* the text pointer is the lenth in blocks so fill that in in the stat buffer */ if (text_ptr && (text_ptr != (char *)WEBDAV_STATFS_IGNORE)) { statfsbuf->f_blocks = atol(text_ptr); free(text_ptr); } else { /* If we got a string set to null we couldnot fit the value in our buffer so the length is longer than our max. Set the value to 0 then. */ statfsbuf->f_blocks = 0; } break; case WEBDAV_STATFS_QUOTAUSED: /* the text pointer is the data so translate it and get it into the stat buffer */ if (text_ptr && (text_ptr != (char *)WEBDAV_STATFS_IGNORE)) { statfsbuf->f_bavail = statfsbuf->f_bfree = atol(text_ptr); free(text_ptr); } else { statfsbuf->f_bavail = statfsbuf->f_bfree = 0; /* If we got a string set to null we could not fit the value in our buffer leave it blank and the kernel will fill in to the best of its ability */ } break; default: /* if it is ignore or something else, check the child. If it is not a return value and isn't null, it's a text ptr so free it */ if (text_ptr && text_ptr != (char *)WEBDAV_STATFS_IGNORE && text_ptr != (char *)WEBDAV_STAT_LENGTH && text_ptr != (char *)WEBDAV_STAT_MODDATE) { free(text_ptr); } break; } return; } /*****************************************************************************/ void parser_end(CFXMLParserRef parser, void *xml_type, void *context) { return; /* deallocate the tree ? */ } /*****************************************************************************/ void parser_opendir_end(CFXMLParserRef parser, void *my_element, void *context) { if (my_element) { #ifdef DEBUG_PARSE fprintf(stderr, "parser_opendir_end: free return_ptr %d\n", (int)my_element); #endif free(my_element); /* free up the space we reserved for the return_ptr */ } return; } /*****************************************************************************/ CFDataRef parser_resolve(CFXMLParserRef parser, CFXMLExternalID *extID, void *context) { return (NULL); } /*****************************************************************************/ int parse_lookup(char *xmlp, int xmlp_len, webdav_filetype_t *a_file_type) { CFDataRef xml_dataref; CFXMLParserCallBacks callbacks = { 0, parser_lookup_create, parser_add, parser_end, parser_resolve, NULL }; webdav_parse_lookup_struct_t lookup_struct; webdav_parse_lookup_element_t * element_ptr; CFXMLParserOptions options = kCFXMLParserNoOptions; /* *** if supported, could use kCFXMLParserReplacePhysicalEntities *** */ CFXMLParserContext context = { 0, &lookup_struct, NULL, NULL, NULL }; CFXMLParserRef parser; CFURLRef fake_url; int error = 0; lookup_struct.head = lookup_struct.tail = NULL; /* Start by getting the data into a form Core Foundation can understand */ xml_dataref = CFDataCreateWithBytesNoCopy(kCFAllocatorSystemDefault, xmlp, (CFIndex)xmlp_len, kCFAllocatorNull); /* Fake up a URL ref since we don't have one */ /* This shouldn't be necessary any more -- REW, 3/20/2000 */ fake_url = CFURLCreateWithString(NULL, CFSTR("fakeurl"), NULL); /* Create the Parser, set it's call backs and away we go */ parser = CFXMLParserCreate(kCFAllocatorSystemDefault, xml_dataref, fake_url, options, kCFXMLNodeCurrentVersion, &callbacks, &context); CFXMLParserParse(parser); /* At this point, we should have one item with the file type, if we don't return an error */ element_ptr = lookup_struct.head; if (!element_ptr) { error = EFTYPE; *a_file_type = 0; } else { *a_file_type = element_ptr->file_type; if (element_ptr->next != NULL) { error = 1; } #ifdef DEBUG_PARSE fprintf(stderr, "parser_lookup: free element_ptr %d\n", (int)element_ptr); #endif free(element_ptr); } /* end if-else */ CFRelease(parser); CFRelease(xml_dataref); CFRelease(fake_url); return (error); } /*****************************************************************************/ int parse_opendir(char *xmlp, int xmlp_len, int fd, char *dir_ref, char *hostname, int uid) { CFDataRef xml_dataref; CFXMLParserCallBacks callbacks = { 0, parser_opendir_create, parser_opendir_add, parser_opendir_end, parser_resolve, NULL }; webdav_parse_opendir_struct_t opendir_struct; webdav_parse_opendir_element_t * element_ptr, *prev_element_ptr; CFXMLParserOptions options = kCFXMLParserNoOptions; /* *** if supported, could use kCFXMLParserReplacePhysicalEntities *** */ CFXMLParserContext context = { 0, &opendir_struct, NULL, NULL, NULL }; CFXMLParserRef parser; CFURLRef fake_url; int error = 0; int ignore_error; size_t size; char *name_ptr; char *decoded_dir_ref; char *after_dir_ref_hostname = NULL; /* used to point to part of dir_ref which is after the hostname */ char *after_hostname = NULL; /* used to point to part of entry name after the hoate name */ char *cache_uri; char *temp_uri; u_int32_t last_char; int name_len = 0; struct dirent dir_data[2]; struct vattr statstruct; opendir_struct.head = opendir_struct.tail = NULL; opendir_struct.error = 0; bzero(&statstruct, sizeof(statstruct)); /* Start by getting the data into a form Core Foundation can understand */ xml_dataref = CFDataCreateWithBytesNoCopy(kCFAllocatorSystemDefault, xmlp, (CFIndex)xmlp_len, kCFAllocatorNull); /* Fake up a URL ref since we don't have one */ fake_url = CFURLCreateWithString(NULL, CFSTR("fakeurl"), NULL); /* Create the Parser, set it's call backs and away we go */ parser = CFXMLParserCreate(kCFAllocatorSystemDefault, xml_dataref, fake_url, options, kCFXMLNodeCurrentVersion, &callbacks, &context); CFXMLParserParse(parser); CFRelease(parser); CFRelease(xml_dataref); CFRelease(fake_url); /* figure out what our actual name is */ /* If in proxy mode, the "http://" and the destination hostname need to be skipped. */ if (strncmp(dir_ref, _WEBDAVPREFIX, strlen(_WEBDAVPREFIX)) == 0) { decoded_dir_ref = percent_decode(dir_ref); if (!decoded_dir_ref) { return ENOMEM; } } else { /* dir_ref did not contain the prefix which meaans it is a partial uri * and won't contain the host either, but we need the host for the inode * cache, so we will build one */ after_dir_ref_hostname = dir_ref; error = reconstruct_url(hostname, dir_ref, &temp_uri); if (error) { return ENOMEM; } decoded_dir_ref = percent_decode(temp_uri); if (!decoded_dir_ref) { free(temp_uri); return ENOMEM; } else { free(temp_uri); } } /* Either way, decoded_dir_ref contains a full uri, utf8_decoded * now lets get past the prefix for the uri we will put in the inode * cache */ cache_uri = &decoded_dir_ref[strlen(_WEBDAVPREFIX)]; after_dir_ref_hostname = &cache_uri[strlen(_WEBDAVPREFIX)]; after_dir_ref_hostname = strchr(after_dir_ref_hostname, '/'); /* Now we'll write each element into the local file which is serving as our directory starting with . and ..*/ bzero(dir_data, sizeof(dir_data)); dir_data[0].d_namlen = 1; dir_data[0].d_reclen = sizeof(struct dirent); dir_data[0].d_name[0] = '.'; /* Now get our own inode. If we are the root we will get back * WEBDAV_ROOTFILEID becuase the table was preinitialized with that */ error = webdav_get_inode(cache_uri, strlen(cache_uri), TRUE, &dir_data[0].d_fileno); if (error) { goto free_decoded_dir_ref; } dir_data[0].d_type = DT_DIR; dir_data[1].d_namlen = 2; dir_data[1].d_reclen = sizeof(struct dirent); dir_data[1].d_name[0] = '.'; dir_data[1].d_name[1] = '.'; if (dir_data[0].d_fileno == WEBDAV_ROOTFILEID) { dir_data[1].d_fileno = 2; /* parent of the root always 2 */ } else { /* Ok, just lop of the last part of the name and calculate * the fileno. Unlike the above case, we have to special case the * root. Why ? becuase the root is cached with the trailing * slash. That's how the kernel keeps it. If the root is what we * are enumerating we will have done it right but genearlly we don't * include trailing slashes with directories so if we ae enumerating * one level below the root we have to special case. * To do that we'll do some fancy pointer math using afterhostname. * If we are enumerating one level below the root the url will be * of the form http://host/dir and afterhost name will point to d. * thus strrchar of afterhostname will come back null and voila, we have * our result. */ if (after_dir_ref_hostname && strchr(after_dir_ref_hostname, '/')) { error = webdav_get_inode(cache_uri, strrchr(cache_uri, '/') - cache_uri, TRUE, &dir_data[1].d_fileno); if (error) { goto free_decoded_dir_ref; } } else { dir_data[1].d_fileno = WEBDAV_ROOTFILEID; } } dir_data[1].d_type = DT_DIR; size = write(fd, (void *)dir_data, (sizeof(struct dirent)) * 2); /* If for some reason we did not get all the data into the file, report the error, but keep going to free all the records */ if (size != (sizeof(struct dirent)) * 2) { if (size == -1) { error = errno; } else { error = EIO; } } element_ptr = opendir_struct.head; if (!element_ptr && !opendir_struct.error) { /* It's ok for a directory to be empty, just return as * long as we didn't take a parse error */ error = 0; goto free_decoded_dir_ref; } else { while (element_ptr) { /* Fix up the element by truncating to just the name part of the URL * First we'll add the null byte. Note that we purposely made sure the * name buffer one character longer than the maximum length name we copy * into it so that we could always hold the null byte */ element_ptr->dir_data.d_name[element_ptr->dir_data.d_name_URI_length] = '\0'; /* Now get rid of trailing slashes if we have any */ if (element_ptr->dir_data.d_name[element_ptr->dir_data.d_name_URI_length - 1] == '/') { --element_ptr->dir_data.d_name_URI_length; element_ptr->dir_data.d_name[element_ptr->dir_data.d_name_URI_length] = '\0'; } /* decode the whole string and readjust the name length appopriately, also set last_char since we will be using it again and it needs to be right.*/ percent_decode_in_place(element_ptr->dir_data.d_name); element_ptr->dir_data.d_name_URI_length = strlen(element_ptr->dir_data.d_name); last_char = element_ptr->dir_data.d_name_URI_length; /* now figure out the post hostname part and compare it with our dirref to * see if we are looking at the directory. The xml we get back from the server * includes the info on the parent directory as well as all of the children */ after_hostname = element_ptr->dir_data.d_name; if (!(strncmp(after_hostname, _WEBDAVPREFIX, strlen(_WEBDAVPREFIX)))) { /* Get past the http: prefix */ after_hostname = &(after_hostname[strlen(_WEBDAVPREFIX)]); /* Since this was a full uri, get past the hostname */ after_hostname = strchr(after_hostname, '/'); } /* Ok, we now have the relative uri percent decoded in afterhostname and the * percent decoded directory reference in after_dir_ref_hostname. We can now * compare these uri's and make sure that we are not about to put in the * directory before proceeding. There are a number of possibilities for * the match and they are described in the following if statement */ if ((!after_hostname) || /* This entry is for the root */ (after_dir_ref_hostname && (!(strncmp(after_hostname, after_dir_ref_hostname, strlen(after_hostname)))))) { /* We don't want to include this one in the name cache or the directory * so do nothing */ } else { /* Since we have gotten rid of the trailing slash, and renormalized the name * to utf8 (that is to say got rid of the % escapted characters) we now have a name * suitable for entry in the stat cache, so let's do that now before * proceeding. Putting this stuff in the stat cache will make the stat * that is highly likely to follow this opendir happen quickly. */ if (element_ptr->dir_data.d_type == DT_DIR) { statstruct.va_type = VDIR; statstruct.va_size = WEBDAV_DIR_SIZE; } else { statstruct.va_type = VREG; statstruct.va_size = element_ptr->statsize; } statstruct.va_bytes = statstruct.va_size + WEBDAV_BLOCK_SIZE - ((statstruct.va_size) % WEBDAV_BLOCK_SIZE); statstruct.va_atime = statstruct.va_mtime = statstruct.va_ctime = element_ptr->stattime; /* Find the last '/', and make sure the string after that is not too long to fit into the d_name field in the dirent structure */ name_ptr = strrchr(element_ptr->dir_data.d_name, '/'); if (name_ptr) { name_len = (int)(((int)element_ptr->dir_data.d_name + last_char) - (((int)name_ptr) + 1)); if (name_len > MAXNAMLEN) { error = ENAMETOOLONG; goto free_decoded_dir_ref; } } /* Check to see if we have a relative uri. If we do, make an absolute uri * since we always cache complete url's minus the "http://" */ if (element_ptr->dir_data.d_name_URI_length && element_ptr->dir_data.d_name[0] == '/') { char *full_url; /* / as the first character indicates that this is a partial uri */ /* If in proxy mode, reconstructing from the hostname won't work, however the complete uri is in dir_ref. */ if (strncmp(dir_ref, _WEBDAVPREFIX, strlen(_WEBDAVPREFIX)) == 0) { int tmp_name_len = (int)(strlen(dir_ref) + (name_ptr ? strlen(name_ptr) : 0)); full_url = malloc(tmp_name_len); if (full_url) { strncpy(full_url, &dir_ref[strlen(_WEBDAVPREFIX)], tmp_name_len); if (name_ptr) { full_url = strncat(full_url, name_ptr, tmp_name_len); } } else { error = ENOMEM; goto free_decoded_dir_ref; } /* Now we have the uri, generate the inode number and put it in * the stat structure */ cache_uri = full_url; error = webdav_get_inode(cache_uri, strlen(cache_uri), TRUE, (u_int32_t *) & statstruct.va_fileid); if (error) { goto free_decoded_dir_ref; } /* Now cache the stat structure */ ignore_error = webdav_memcache_insert(uid, cache_uri, &gmemcache_header, &statstruct); free(full_url); } else { ignore_error = reconstruct_url(hostname, element_ptr->dir_data.d_name, &full_url); if (!ignore_error) { /* Now we have the uri, generate the inode number and put it in * the stat structure */ cache_uri = &full_url[strlen(_WEBDAVPREFIX)]; error = webdav_get_inode(cache_uri, strlen(cache_uri), TRUE, (u_int32_t *) & statstruct.va_fileid); if (error) { free(full_url); goto free_decoded_dir_ref; } /* Now cache the stat structure */ ignore_error = webdav_memcache_insert(uid, cache_uri, &gmemcache_header, &statstruct); /* reconstruct_url (if it succeeded) will have allocated the url which we must now free. */ free(full_url); } } } else { /* This is a full uri so just enter it directly */ cache_uri = &element_ptr->dir_data.d_name[strlen(_WEBDAVPREFIX)]; error = webdav_get_inode(cache_uri, strlen(cache_uri), TRUE, (u_int32_t *) & statstruct.va_fileid); if (error) { goto free_decoded_dir_ref; } ignore_error = webdav_memcache_insert(uid, cache_uri, &gmemcache_header, &statstruct); } /* Ok now we'll complete the task of getting the regular name into the dirent */ /* Complete the task of getting the regular name into the dirent */ /* if there's a '/', fix up element_ptr->dir_data.d_name so that it contains the name that starts right after the last '/'. */ if (name_ptr) { bcopy(name_ptr + 1, element_ptr->dir_data.d_name, name_len); element_ptr->dir_data.d_name[name_len] = '\0'; element_ptr->dir_data.d_namlen = name_len; element_ptr->dir_data.d_fileno = statstruct.va_fileid; } size = write(fd, (void *) & element_ptr->dir_data, element_ptr->dir_data.d_reclen); /* If for some reason we did not get all the data into the file, report the error, but keep going to free all the records */ if (size != element_ptr->dir_data.d_reclen) { if (size == -1) { error = errno; } else { error = EIO; } } } /* end if not ourselves */ prev_element_ptr = element_ptr; element_ptr = element_ptr->next; #ifdef DEBUG_PARSE fprintf(stderr, "parse_opendir: freeing previous element ptr %d\n", (int)prev_element_ptr); #endif free(prev_element_ptr); } /* while */ } /* Now that we've taken care of freeing all the elements, check for a * parser error. If we have one, it will overide any error found while * writing the file, since it will have happened first. */ if (opendir_struct.error) { error = opendir_struct.error; } free_decoded_dir_ref: free(decoded_dir_ref); return (error); } /*****************************************************************************/ int parse_file_count(char *xmlp, int xmlp_len, int *file_count) { CFDataRef xml_dataref; CFXMLParserCallBacks callbacks = { 0, parser_file_count_create, parser_add, parser_end, parser_resolve, NULL }; CFXMLParserOptions options = kCFXMLParserNoOptions; /* *** if supported, could use kCFXMLParserReplacePhysicalEntities *** */ CFXMLParserContext context = { 0, file_count, NULL, NULL, NULL }; CFXMLParserRef parser; CFURLRef fake_url; int error = 0; *file_count = 0; /* Start by getting the data into a form Core Foundation can understand */ xml_dataref = CFDataCreateWithBytesNoCopy(kCFAllocatorSystemDefault, xmlp, (CFIndex)xmlp_len, kCFAllocatorNull); /* Fake up a URL ref since we don't have one */ fake_url = CFURLCreateWithString(NULL, CFSTR("fakeurl"), NULL); /* Create the Parser, set it's call backs and away we go */ parser = CFXMLParserCreate(kCFAllocatorSystemDefault, xml_dataref, fake_url, options, kCFXMLNodeCurrentVersion, &callbacks, &context); CFXMLParserParse(parser); CFRelease(parser); CFRelease(xml_dataref); CFRelease(fake_url); return (error); } /*****************************************************************************/ int parse_stat(char *xmlp, int xmlp_len, const char *orig_uri, struct vattr *statbuf) { CFDataRef xml_dataref; CFXMLParserCallBacks callbacks = { 0, parser_stat_create, parser_stat_add, parser_end, parser_resolve, NULL }; CFXMLParserContext context = { 0, statbuf, NULL, NULL, NULL }; CFXMLParserOptions options = kCFXMLParserNoOptions; /* *** if supported, could use kCFXMLParserReplacePhysicalEntities *** */ CFXMLParserRef parser; CFURLRef fake_url; int error = 0; /* Start by getting the data into a form Core Foundation can understand */ xml_dataref = CFDataCreateWithBytesNoCopy(kCFAllocatorSystemDefault, xmlp, (CFIndex)xmlp_len, kCFAllocatorNull); /* Fake up a URL ref since we don't have one */ fake_url = CFURLCreateWithString(NULL, CFSTR("fakeurl"), NULL); /* Create the Parser, set it's call backs and away we go */ bzero((void *)statbuf, sizeof(struct vattr)); parser = CFXMLParserCreate(kCFAllocatorSystemDefault, xml_dataref, fake_url, options, kCFXMLNodeCurrentVersion, &callbacks, &context); CFXMLParserParse(parser); CFRelease(parser); CFRelease(xml_dataref); CFRelease(fake_url); /* Now we'll do our final size calculations. If this turned out to be a directory, we don't want the size of what would be returned by GET, we want it's size as a directory file (#of dirents). That will match the size of the directory if we actually open it. If it is not a directory than the size is correct so make size and bytes match. with bytes rounded up to the block size. There will be an entry for the url in the xml return which means va_bytes will always have be one short. That is to say that '.' is accounted for but not '..' so we add one here */ if (statbuf->va_type == VDIR) { statbuf->va_size = WEBDAV_DIR_SIZE; /* because that's how big webdav directories are */ } else { /* if the file turned out not to be a directory, mark it as a * mark it as a regular file. In WebDAV, for now, everything is * either a file or a directory */ statbuf->va_type = VREG; } if (statbuf->va_size) { statbuf->va_bytes = statbuf->va_size + WEBDAV_BLOCK_SIZE - ((statbuf->va_size) % WEBDAV_BLOCK_SIZE); } /* Now put the URI into the inode hash. Orig URI is the non decoded (pure utf8) URI which came from the kernel and was passed all the way from activate. */ if (strncmp(orig_uri, _WEBDAVPREFIX, strlen(_WEBDAVPREFIX)) == 0) { orig_uri += strlen(_WEBDAVPREFIX); } error = webdav_get_inode(orig_uri, strlen(orig_uri), TRUE, (u_int32_t *) & statbuf->va_fileid); if (error) { return (error); } return (error); } /*****************************************************************************/ int parse_statfs(char *xmlp, int xmlp_len, struct statfs *statfsbuf) { CFDataRef xml_dataref; CFXMLParserCallBacks callbacks = { 0, parser_statfs_create, parser_statfs_add, parser_end, parser_resolve, NULL }; CFXMLParserContext context = { 0, statfsbuf, NULL, NULL, NULL }; CFXMLParserOptions options = kCFXMLParserNoOptions; /* *** if supported, could use kCFXMLParserReplacePhysicalEntities *** */ CFXMLParserRef parser; CFURLRef fake_url; int error = 0; /* Start by getting the data into a form Core Foundation can understand */ xml_dataref = CFDataCreateWithBytesNoCopy(kCFAllocatorSystemDefault, xmlp, (CFIndex)xmlp_len, kCFAllocatorNull); /* Fake up a URL ref since we don't have one */ fake_url = CFURLCreateWithString(NULL, CFSTR("fakeurl"), NULL); /* Create the Parser, set it's call backs and away we go */ bzero((void *)statfsbuf, sizeof(struct statfs)); parser = CFXMLParserCreate(kCFAllocatorSystemDefault, xml_dataref, fake_url, options, kCFXMLNodeCurrentVersion, &callbacks, &context); CFXMLParserParse(parser); CFRelease(parser); CFRelease(xml_dataref); CFRelease(fake_url); /* Ok now turn quota-used, which we temporarily stored in f_bfree, into blocks-available, with a little subtraction */ if (statfsbuf->f_blocks && (statfsbuf->f_blocks > statfsbuf->f_bfree)) { statfsbuf->f_bavail = statfsbuf->f_bfree = (statfsbuf->f_blocks - statfsbuf->f_bfree); } else { statfsbuf->f_bavail = statfsbuf->f_bfree = 0; } return (error); } /*****************************************************************************/ int parse_lock(char *xmlp, int xmlp_len, char **locktoken) { CFDataRef xml_dataref; CFXMLParserCallBacks callbacks = { 0, parser_lock_create, parser_add, parser_end, parser_resolve, NULL }; CFXMLParserOptions options = kCFXMLParserNoOptions; /* *** if supported, could use kCFXMLParserReplacePhysicalEntities *** */ CFXMLParserContext context = { 0, locktoken, NULL, NULL, NULL }; CFXMLParserRef parser; CFURLRef fake_url; /* Start by getting the data into a form Core Foundation can understand */ xml_dataref = CFDataCreateWithBytesNoCopy(kCFAllocatorSystemDefault, xmlp, (CFIndex)xmlp_len, kCFAllocatorNull); /* Fake up a URL ref since we don't have one */ fake_url = CFURLCreateWithString(NULL, CFSTR("fakeurl"), NULL); /* Create the Parser, set it's call backs and away we go */ parser = CFXMLParserCreate(kCFAllocatorSystemDefault, xml_dataref, fake_url, options, kCFXMLNodeCurrentVersion, &callbacks, &context); CFXMLParserParse(parser); CFRelease(parser); CFRelease(xml_dataref); CFRelease(fake_url); /* * If we in the middle of processing we think we have found a lock * token but didn't actually get the text, set the output to NULL * so as not to confuse the caller. */ if (*locktoken == (char *)WEBDAV_LOCK_TOKEN || *locktoken == (char *)WEBDAV_LOCK_HREF) { *locktoken = NULL; } return (0); } /*****************************************************************************/