UNIXworkcode

/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2018 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <cx/string.h> #include <cx/utils.h> #include <cx/printf.h> #include <cx/hash_map.h> #include <libidav/crypto.h> #include "libxattr.h" #include "tags.h" #ifdef __APPLE__ #include <CoreFoundation/CoreFoundation.h> #endif void free_dav_tag(DavTag* tag) { free(tag->name); if(tag->color) { free(tag->color); } free(tag); } void free_taglist(CxList *list) { if(!list) { return; } cxListDestroy(list); } int compare_tagname(DavTag* left, DavTag* right, void* ignorecase) { cxstring leftname = cx_str(left->name); cxstring rightname = cx_str(right->name); if (ignorecase && *((int*) ignorecase)) { return cx_strcasecmp(leftname, rightname); } else { return cx_strcmp(leftname, rightname); } } CxList* parse_text_taglist(const char *buf, size_t length) { CxList *tags = cxLinkedListCreateSimple(CX_STORE_POINTERS); cxDefineDestructor(tags, free_dav_tag); int line_start = 0; for(int i=0;i<length;i++) { if(buf[i] == '\n' || i == length-1) { cxstring line = cx_strtrim(cx_strn((char*)buf + line_start, i - line_start)); if(line.length > 0) { DavTag *tag = calloc(1, sizeof(DavTag)); cxstring color = cx_strchr(line, '#'); if(color.length>0) { cxstring name = line; name.length = (int)(color.ptr-line.ptr); if(name.length != 0) { tag->name = cx_strdup(name).ptr; color.ptr++; color.length--; if(color.length > 0) { tag->color = cx_strdup(color).ptr; } } else { free(tag); } } else { tag->name = cx_strdup(line).ptr; tag->color = NULL; } cxListAdd(tags, tag); } line_start = i+1; } } return tags; } CxMap* taglist2map(CxList *tags) { if(!tags) { return cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 8); } CxMap *map = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, cxListSize(tags) + 8); CxIterator iter = cxListIterator(tags); cx_foreach(DavTag*, t, iter) { cxMapPut(map, cx_hash_key_str(t->name), t); } return map; } CxBuffer* create_text_taglist(CxList *tags) { if(!tags) { return NULL; } CxBuffer *buf = cxBufferCreate(NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); CxIterator i = cxListIterator(tags); cx_foreach(DavTag *, tag, i) { if(tag->color) { cx_bprintf(buf, "%s#%s\n", tag->name, tag->color); } else { cx_bprintf(buf, "%s\n", tag->name); } } return buf; } CxList* parse_csv_taglist(const char *buf, size_t length) { CxList *taglist = cxLinkedListCreateSimple(CX_STORE_POINTERS); cxDefineDestructor(taglist, free_dav_tag); cxstring str = cx_strn(buf, length); CxStrtokCtx tags = cx_strtok(str, CX_STR(","), INT_MAX); cxstring tagstr; while(cx_strtok_next(&tags, &tagstr)) { cxstring trimmed_tag = cx_strtrim(tagstr); if (trimmed_tag.length > 0) { DavTag *tag = malloc(sizeof(DavTag)); tag->name = cx_strdup(trimmed_tag).ptr; tag->color = NULL; cxListAdd(taglist, tag); } } return taglist; } CxBuffer* create_csv_taglist(CxList *tags) { CxBuffer *buf = cxBufferCreate(NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); int insertsep = 0; CxIterator i = cxListIterator(tags); cx_foreach(DavTag*, tag, i) { if(insertsep) { cxBufferPut(buf, ','); } cxBufferPutString(buf, tag->name); insertsep = 1; } return buf; } static DavTag* parse_xml_dav_tag(DavXmlNode *node) { char *name = NULL; char *color = NULL; DavXmlNode *c = node->children; while(c) { if(c->type == DAV_XML_ELEMENT) { char *value = dav_xml_getstring(c->children); if(value) { if(!strcmp(c->namespace, DAV_PROPS_NS)) { if(!strcmp(c->name, "name")) { name = value; } if(!strcmp(c->name, "color")) { color = value; } } } } c = c->next; } DavTag *tag = NULL; if(name) { tag = malloc(sizeof(DavTag)); tag->name = strdup(name); tag->color = color ? strdup(color) : NULL; } return tag; } CxList* parse_dav_xml_taglist(DavXmlNode *taglistnode) { CxList *tags = cxLinkedListCreateSimple(CX_STORE_POINTERS); cxDefineDestructor(tags, free_dav_tag); DavXmlNode *node = taglistnode; while(node) { if(node->type == DAV_XML_ELEMENT) { if(!strcmp(node->namespace, DAV_PROPS_NS) && !strcmp(node->name, "tag")) { DavTag *tag = parse_xml_dav_tag(node); if(tag) { cxListAdd(tags, tag); } } } node = node->next; } return tags; } DavXmlNode* create_xml_taglist(CxList *tags) { DavXmlNode *tag1 = NULL; DavXmlNode *lasttag = NULL; CxIterator i = cxListIterator(tags); cx_foreach(DavTag*, tag, i) { DavXmlNode *tagelm = dav_xml_createnode(DAV_PROPS_NS, "tag"); DavXmlNode *tagname = dav_xml_createnode_with_text(DAV_PROPS_NS, "name", tag->name); tagelm->children = tagname; if(tag->color) { DavXmlNode *tagcolor = dav_xml_createnode_with_text(DAV_PROPS_NS, "color", tag->color); tagname->next = tagcolor; } if(lasttag) { lasttag->next = tagelm; tagelm->prev = lasttag; } else { tag1 = tagelm; } lasttag = tagelm; } return tag1; } #ifdef __APPLE__ static DavTag* tagstr2davtag(const char *str) { const char *name = str; const char *color = NULL; size_t len = strlen(str); size_t namelen = len; if(len == 0) { return NULL; } // start with 1 because the first char should not be a linebreak for(int i=1;i<len;i++) { if(str[i] == '\n') { if(!color) { color = str + i + 1; namelen = i; } } } int colorlen = len - namelen - 1; DavTag *tag = malloc(sizeof(DavTag)); tag->name = malloc(namelen + 1); memcpy(tag->name, name, namelen); tag->name[namelen] = 0; if(colorlen > 0) { tag->color = malloc(colorlen + 1); memcpy(tag->color, color, colorlen); tag->color[colorlen] = 0; } else { tag->color = NULL; } return tag; } CxList* parse_macos_taglist(const char *buf, size_t length) { CxList *taglist = cxLinkedListCreateSimple(CX_STORE_POINTERS); taglist->simple_destructor = (cx_destructor_func)free_dav_tag; CFDataRef data = CFDataCreateWithBytesNoCopy( kCFAllocatorDefault, (const UInt8*)buf, length, kCFAllocatorNull); CFPropertyListRef propertylist = CFPropertyListCreateWithData(kCFAllocatorDefault, data, 0, NULL, NULL); CFArrayRef array = propertylist; int count = CFArrayGetCount(array); for(int i=0;i<count;i++) { CFStringRef str = CFArrayGetValueAtIndex(array, i); int slen = CFStringGetLength(str); size_t cstrbuflen = slen * 4 + 4; char *cstr = malloc(cstrbuflen); if(CFStringGetCString(str, cstr, cstrbuflen, kCFStringEncodingUTF8)) { DavTag *tag = tagstr2davtag(cstr); if(tag) { cxListAdd(taglist, tag); } } free(cstr); } CFRelease(propertylist); CFRelease(data); return taglist; } CxBuffer* create_macos_taglist(CxList *tags) { size_t count = tags->size; if(count == 0) { return NULL; } CFStringRef *strings = calloc(sizeof(CFStringRef), count); int i = 0; CxIterator iter = cxListIterator(tags); cx_foreach(DavTag*, tag, iter) { CFStringRef str = NULL; if(tag->color) { cxmutstr s = cx_strcat(3, cx_mutstr(tag->name), CX_STR("\n"), cx_str(tag->color)); str = CFStringCreateWithCString(kCFAllocatorDefault, s.ptr, kCFStringEncodingUTF8); free(s.ptr); } else { str = CFStringCreateWithCString(kCFAllocatorDefault, tag->name, kCFStringEncodingUTF8); } strings[i] = str; i++; } CFPropertyListRef array = CFArrayCreate(kCFAllocatorDefault, (const void**)strings, count, &kCFTypeArrayCallBacks); CFDataRef data = CFPropertyListCreateData(kCFAllocatorDefault, array, kCFPropertyListBinaryFormat_v1_0, 0, NULL); CxBuffer *buf = NULL; if(data) { int datalen = CFDataGetLength(data); CFRange range; range.location = 0; range.length = datalen; buf = cxBufferCreate(NULL, datalen, cxDefaultAllocator, 0); CFDataGetBytes(data, range, (UInt8*)buf->space); buf->size = datalen; CFRelease(data); } for(int i=0;i<count;i++) { CFRelease(strings[i]); } CFRelease(array); return buf; } #else CxList* parse_macos_taglist(const char *buf, size_t length) { fprintf(stderr, "Error: macos tags not supported on this platform.\n"); return NULL; } CxBuffer* create_macos_taglist(CxList *tags) { fprintf(stderr, "Error: macos tags not supported on this platform.\n"); return NULL; } #endif int compare_taglists(CxList *tags1, CxList *tags2) { if(!tags1) { return tags2 ? 0 : 1; } if(!tags2) { return tags1 ? 0 : 1; } CxMap *map1 = taglist2map(tags1); int equal = 1; int i = 0; CxIterator iter = cxListIterator(tags2); cx_foreach(DavTag*, t, iter) { if(!cxMapGet(map1, cx_hash_key_str(t->name))) { equal = 0; break; } i++; } if(i != cxMapSize(map1)) { equal = 0; } cxMapDestroy(map1); return equal; } char* create_tags_hash(CxList *tags) { if(!tags) { return NULL; } CxBuffer *buf = create_text_taglist(tags); char *hash = dav_create_hash(buf->space, buf->size); cxBufferDestroy(buf); return hash; } CxList* merge_tags(CxList *tags1, CxList *tags2) { // this map is used to check the existence of tags CxMap *tag_map = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32); // merged taglist CxList *new_tags = cxLinkedListCreateSimple(CX_STORE_POINTERS); cxDefineDestructor(new_tags, free_dav_tag); // add all local tags if(tags1) { CxIterator iter = cxListIterator(tags1); cx_foreach(DavTag*, t, iter) { cxMapPut(tag_map, cx_hash_key_str(t->name), t); DavTag *newt = calloc(1, sizeof(DavTag)); newt->color = t->color ? strdup(t->color) : NULL; newt->name = strdup(t->name); cxListAdd(new_tags, newt); } } // check if a remote tag is already in the map // and if not add it to the new taglist if(tags2) { CxIterator iter = cxListIterator(tags2); cx_foreach(DavTag*, t, iter) { if(!cxMapGet(tag_map, cx_hash_key_str(t->name))) { DavTag *newt = calloc(1, sizeof(DavTag)); newt->color = t->color ? strdup(t->color) : NULL; newt->name = strdup(t->name); cxListAdd(new_tags, newt); } } } cxMapDestroy(tag_map); return new_tags; } void add_tag_colors(CxList *taglist, CxList *colored) { CxMap *tagmap = taglist2map(taglist); CxIterator i = cxListIterator(colored); cx_foreach(DavTag*, colored_tag, i) { if(colored_tag->color) { DavTag *tag = cxMapGet(tagmap, cx_hash_key_str(colored_tag->name)); if(tag && !tag->color) { tag->color = strdup(colored_tag->color); } } } cxMapDestroy(tagmap); } /* ----------- ----------- tag filter ---------------------- */ static size_t rtrimskip(cxstring str, size_t skip) { while (skip < str.length && isspace(str.ptr[skip])) skip++; return skip; } static size_t parse_tagfilter_taglist(cxstring fs, SyncTagFilter* tagfilter) { size_t csvlen; for (csvlen = 0 ; csvlen < fs.length ; ++csvlen) { if (fs.ptr[csvlen] == ')') break; } fs.length = csvlen; tagfilter->tags = parse_csv_taglist(fs.ptr, fs.length); return csvlen; } static size_t parse_tagfilter_subfilters(cxstring fs, SyncTagFilter* tagfilter); static size_t parse_tagfilter_filter(cxstring fs, SyncTagFilter* tagfilter) { size_t consumed = rtrimskip(fs, 0); fs = cx_strsubs(fs, consumed); if (fs.length == 0) { return consumed; } else { // optional operator int hasop = 0; if (fs.ptr[0] == '&') { tagfilter->mode = DAV_SYNC_TAGFILTER_AND; hasop = 1; } else if (fs.ptr[0] == '|') { tagfilter->mode = DAV_SYNC_TAGFILTER_OR; hasop = 1; } else if (fs.ptr[0] == '0') { tagfilter->mode = DAV_SYNC_TAGFILTER_NONE; hasop = 1; } else if (fs.ptr[0] == '1') { tagfilter->mode = DAV_SYNC_TAGFILTER_ONE; hasop = 1; } else { // default operator is AND tagfilter->mode = DAV_SYNC_TAGFILTER_AND; } if (hasop) { size_t skip = rtrimskip(fs, 1); consumed += skip; fs = cx_strsubs(fs, skip); } if (fs.length > 0 && fs.ptr[0] == '(') { size_t c = parse_tagfilter_subfilters(fs, tagfilter); if (c) { return consumed + c; } else { return 0; } } else { tagfilter->subfilter_count = 0; tagfilter->subfilters = NULL; return consumed + parse_tagfilter_taglist(fs, tagfilter); } } } /* * Parses: ( "(" , filter , ")" )+ */ static size_t parse_tagfilter_subfilters(cxstring fs, SyncTagFilter* f) { // strategy: allocate much and give back later (instead of reallocs in loop) size_t subfilter_cap = 8; f->subfilters = calloc(subfilter_cap, sizeof(SyncTagFilter*)); f->subfilter_count = 0; size_t total_consumed = 0; size_t c; do { // skip leading parenthesis (and white spaces) c = rtrimskip(fs, 1); fs = cx_strsubs(fs, c); total_consumed += c; // increase array capacity, if necessary if (f->subfilter_count >= subfilter_cap) { subfilter_cap *= 2; SyncTagFilter** newarr = realloc(f->subfilters, subfilter_cap * sizeof(SyncTagFilter*)); if (newarr) { f->subfilters = newarr; } else { abort(); // no error handling reachable, so we are fucked } } // allocate space for a new filter SyncTagFilter* subf = calloc(1, sizeof(SyncTagFilter)); // parse that filter c = parse_tagfilter_filter(fs, subf); // sanity check: we must end with a closing parenthesis if (c > 0 && fs.ptr[c] == ')') { f->subfilters[f->subfilter_count++] = subf; // consume ')' and find the next parenthesis or the end-of-string c = rtrimskip(fs, 1+c); fs = cx_strsubs(fs, c); total_consumed += c; if (fs.length == 0 || fs.ptr[0] == ')') { // our job is done break; } else if (fs.ptr[0] != '(') { // anything else than a parenthesis or end-of-string is an error return 0; } } else { free(subf); break; } } while(1); // try to shrink the array if (f->subfilter_count > 0) { SyncTagFilter** shrinked_array = realloc(f->subfilters, f->subfilter_count * sizeof(SyncTagFilter*)); if (shrinked_array) { f->subfilters = shrinked_array; } } else { free(f->subfilters); f->subfilters = NULL; } return total_consumed; } SyncTagFilter* parse_tagfilter_string(const char* filterstring, int scope) { SyncTagFilter* tagfilter = calloc(1, sizeof(SyncTagFilter)); tagfilter->scope = scope; if (!filterstring) { return tagfilter; } cxstring fs = cx_str(filterstring); size_t consumed = parse_tagfilter_filter(fs, tagfilter); if (!consumed) { free_tagfilter(tagfilter); return NULL; } // consume trailing white spaces consumed = rtrimskip(fs, consumed); // sanity check: have we consumed the whole string? if (consumed != fs.length) { free_tagfilter(tagfilter); return NULL; } return tagfilter; } void free_tagfilter(SyncTagFilter* filter) { for (size_t i = 0 ; i < filter->subfilter_count ; i++) { free_tagfilter(filter->subfilters[i]); } free(filter->subfilters); free(filter); } static int matches_tags_and(CxList *dav_tags, CxList *tags, int ignorecase) { // ignorecase not supported yet int ret = 1; CxMap *tagmap = taglist2map(dav_tags); CxIterator i = cxListIterator(tags); cx_foreach(DavTag *, tag, i) { if (cxMapGet(tagmap, cx_hash_key_str(tag->name)) == NULL) { ret = 0; break; } } cxMapDestroy(tagmap); return ret; } static int matches_tags_or(CxList *dav_tags, CxList *tags, int ignorecase) { // ignorecase not supported yet int ret = 0; CxMap *tagmap = taglist2map(dav_tags); CxIterator i = cxListIterator(tags); cx_foreach(DavTag *, tag, i) { if (cxMapGet(tagmap, cx_hash_key_str(tag->name))) { ret = 1; break; } } cxMapDestroy(tagmap); return ret; } static int matches_tags_one(CxList *dav_tags, CxList *tags, int ignorecase) { int matches_exactly_one = 0; CxMap *tagmap = taglist2map(dav_tags); CxIterator i = cxListIterator(tags); cx_foreach(DavTag *, tag, i) { if (cxMapGet(tagmap, cx_hash_key_str(tag->name))) { if (matches_exactly_one) { cxMapDestroy(tagmap); return 0; } else { matches_exactly_one = 1; } } } cxMapDestroy(tagmap); return matches_exactly_one; } static int matches_subfilters_and(CxList *dav_tags, SyncTagFilter *filter) { int ret = 1; for (size_t i = 0 ; i < filter->subfilter_count ; i++) { ret &= matches_tagfilter(dav_tags, filter->subfilters[i]); } return ret; } static int matches_subfilters_or(CxList *dav_tags, SyncTagFilter *filter) { int ret = 0; for (size_t i = 0 ; i < filter->subfilter_count ; i++) { ret |= matches_tagfilter(dav_tags, filter->subfilters[i]); } return ret; } static int matches_subfilters_one(CxList *dav_tags, SyncTagFilter *filter) { int one = 0; for (size_t i = 0 ; i < filter->subfilter_count ; i++) { if (matches_tagfilter(dav_tags, filter->subfilters[i])) { if (one) { return 0; } else { one = 1; } } } return one; } int matches_tagfilter(CxList *dav_tags, SyncTagFilter *tagfilter) { if (tagfilter->subfilter_count > 0) { switch (tagfilter->mode) { case DAV_SYNC_TAGFILTER_OR: return matches_subfilters_or(dav_tags, tagfilter); case DAV_SYNC_TAGFILTER_AND: return matches_subfilters_and(dav_tags, tagfilter); case DAV_SYNC_TAGFILTER_NONE: return !matches_subfilters_or(dav_tags, tagfilter); case DAV_SYNC_TAGFILTER_ONE: return matches_subfilters_one(dav_tags, tagfilter); default: abort(); } } else { int ignorecase = 0; // TODO: maybe add support later switch (tagfilter->mode) { case DAV_SYNC_TAGFILTER_OR: return matches_tags_or(dav_tags, tagfilter->tags, ignorecase); case DAV_SYNC_TAGFILTER_AND: return matches_tags_and(dav_tags, tagfilter->tags, ignorecase); case DAV_SYNC_TAGFILTER_NONE: return !matches_tags_or(dav_tags, tagfilter->tags, ignorecase); case DAV_SYNC_TAGFILTER_ONE: return matches_tags_one(dav_tags, tagfilter->tags, ignorecase); default: abort(); } } }