1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <ctype.h>
33
34 #include <cx/string.h>
35 #include <cx/utils.h>
36 #include <cx/printf.h>
37 #include <cx/hash_map.h>
38
39 #include <libidav/crypto.h>
40
41 #include "libxattr.h"
42
43 #include "tags.h"
44
45 #ifdef __APPLE__
46 #include <CoreFoundation/CoreFoundation.h>
47 #endif
48
49 void free_dav_tag(DavTag* tag) {
50 free(tag->name);
51 if(tag->color) {
52 free(tag->color);
53 }
54 free(tag);
55 }
56
57 void free_taglist(CxList *list) {
58 if(!list) {
59 return;
60 }
61 cxListDestroy(list);
62 }
63
64 int compare_tagname(DavTag* left, DavTag* right,
void* ignorecase) {
65 cxstring leftname = cx_str(left->name);
66 cxstring rightname = cx_str(right->name);
67 if (ignorecase && *((
int*) ignorecase)) {
68 return cx_strcasecmp(leftname, rightname);
69 }
else {
70 return cx_strcmp(leftname, rightname);
71 }
72 }
73
74 CxList* parse_text_taglist(
const char *buf,
size_t length) {
75 CxList *tags = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
76 tags->simple_destructor = (cx_destructor_func)free_dav_tag;
77
78 int line_start =
0;
79 for(
int i=
0;i<length;i++) {
80 if(buf[i] ==
'\n' || i == length-
1) {
81 cxstring line = cx_strtrim(cx_strn((
char*)buf + line_start, i - line_start));
82 if(line.length >
0) {
83 DavTag *tag = calloc(
1,
sizeof(DavTag));
84 cxstring color = cx_strchr(line,
'#');
85 if(color.length>
0) {
86 cxstring name = line;
87 name.length = (
int)(color.ptr-line.ptr);
88 if(name.length !=
0) {
89 tag->name = cx_strdup(name).ptr;
90 color.ptr++;
91 color.length--;
92 if(color.length >
0) {
93 tag->color = cx_strdup(color).ptr;
94 }
95 }
else {
96 free(tag);
97 }
98 }
else {
99 tag->name = cx_strdup(line).ptr;
100 tag->color =
NULL;
101 }
102
103 cxListAdd(tags, tag);
104 }
105 line_start = i+
1;
106 }
107 }
108
109 return tags;
110 }
111
112 CxMap* taglist2map(CxList *tags) {
113 if(!tags) {
114 return cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS,
8);
115 }
116
117 CxMap *map = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS, tags->size +
8);
118 CxIterator iter = cxListIterator(tags);
119 cx_foreach(DavTag*, t, iter) {
120 cxMapPut(map, cx_hash_key_str(t->name), t);
121 }
122 return map;
123 }
124
125 CxBuffer* create_text_taglist(CxList *tags) {
126 if(!tags) {
127 return NULL;
128 }
129
130 CxBuffer *buf = cxBufferCreate(
NULL,
128, cxDefaultAllocator,
CX_BUFFER_FREE_CONTENTS|
CX_BUFFER_AUTO_EXTEND);
131 CxIterator i = cxListIterator(tags);
132 cx_foreach(DavTag *, tag, i) {
133 if(tag->color) {
134 cx_bprintf(buf,
"%s#%s\n", tag->name, tag->color);
135 }
else {
136 cx_bprintf(buf,
"%s\n", tag->name);
137 }
138 }
139 return buf;
140 }
141
142
143 CxList* parse_csv_taglist(
const char *buf,
size_t length) {
144 CxList *taglist = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
145 taglist->simple_destructor = (cx_destructor_func)free_dav_tag;
146
147 cxstring str = cx_strn(buf, length);
148 CxStrtokCtx tags = cx_strtok(str,
CX_STR(
","),
INT_MAX);
149 cxstring tagstr;
150 while(cx_strtok_next(&tags, &tagstr)) {
151 cxstring trimmed_tag = cx_strtrim(tagstr);
152 if (trimmed_tag.length >
0) {
153 DavTag *tag = malloc(
sizeof(DavTag));
154 tag->name = cx_strdup(trimmed_tag).ptr;
155 tag->color =
NULL;
156 cxListAdd(taglist, tag);
157 }
158 }
159 return taglist;
160 }
161
162 CxBuffer* create_csv_taglist(CxList *tags) {
163 CxBuffer *buf = cxBufferCreate(
NULL,
128, cxDefaultAllocator,
CX_BUFFER_FREE_CONTENTS|
CX_BUFFER_AUTO_EXTEND);
164 int insertsep =
0;
165 CxIterator i = cxListIterator(tags);
166 cx_foreach(DavTag*, tag, i) {
167 if(insertsep) {
168 cxBufferPut(buf,
',');
169 }
170 cxBufferPutString(buf, tag->name);
171 insertsep =
1;
172 }
173 return buf;
174 }
175
176
177 static DavTag* parse_xml_dav_tag(DavXmlNode *node) {
178 char *name =
NULL;
179 char *color =
NULL;
180
181 DavXmlNode *c = node->children;
182 while(c) {
183 if(c->type ==
DAV_XML_ELEMENT) {
184 char *value = dav_xml_getstring(c->children);
185 if(value) {
186 if(!strcmp(c->namespace,
DAV_PROPS_NS)) {
187 if(!strcmp(c->name,
"name")) {
188 char *value = dav_xml_getstring(c->children);
189 if(value) {
190 name = value;
191 }
192 }
193 if(!strcmp(c->name,
"color")) {
194 char *value = dav_xml_getstring(c->children);
195 if(value) {
196 color = value;
197 }
198 }
199 }
200 }
201 }
202 c = c->next;
203 }
204
205 DavTag *tag =
NULL;
206 if(name) {
207 tag = malloc(
sizeof(DavTag));
208 tag->name = strdup(name);
209 tag->color = color ? strdup(color) :
NULL;
210 }
211 return tag;
212 }
213
214 CxList* parse_dav_xml_taglist(DavXmlNode *taglistnode) {
215 CxList *tags = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
216 tags->simple_destructor = (cx_destructor_func)free_dav_tag;
217
218 DavXmlNode *node = taglistnode;
219 while(node) {
220 if(node->type ==
DAV_XML_ELEMENT) {
221 if(!strcmp(node->namespace,
DAV_PROPS_NS) && !strcmp(node->name,
"tag")) {
222 DavTag *tag = parse_xml_dav_tag(node);
223 if(tag) {
224 cxListAdd(tags, tag);
225 }
226 }
227 }
228 node = node->next;
229 }
230
231 return tags;
232 }
233
234 DavXmlNode* create_xml_taglist(CxList *tags) {
235 DavXmlNode *tag1 =
NULL;
236 DavXmlNode *lasttag =
NULL;
237 CxIterator i = cxListIterator(tags);
238 cx_foreach(DavTag*, tag, i) {
239 DavXmlNode *tagelm = dav_xml_createnode(
DAV_PROPS_NS,
"tag");
240 DavXmlNode *tagname = dav_xml_createnode_with_text(
DAV_PROPS_NS,
"name", tag->name);
241 tagelm->children = tagname;
242 if(tag->color) {
243 DavXmlNode *tagcolor = dav_xml_createnode_with_text(
DAV_PROPS_NS,
"color", tag->color);
244 tagname->next = tagcolor;
245 }
246
247 if(lasttag) {
248 lasttag->next = tagelm;
249 tagelm->prev = lasttag;
250 }
else {
251 tag1 = tagelm;
252 }
253 lasttag = tagelm;
254 }
255 return tag1;
256 }
257
258
259 #ifdef __APPLE__
260 static DavTag* tagstr2davtag(
const char *str) {
261 const char *name = str;
262 const char *color =
NULL;
263 size_t len = strlen(str);
264 size_t namelen = len;
265
266 if(len ==
0) {
267 return NULL;
268 }
269
270
271 for(
int i=
1;i<len;i++) {
272 if(str[i] ==
'\n') {
273 if(!color) {
274 color = str + i +
1;
275 namelen = i;
276 }
277 }
278 }
279 int colorlen = len - namelen -
1;
280
281 DavTag *tag = malloc(
sizeof(DavTag));
282 tag->name = malloc(namelen +
1);
283 memcpy(tag->name, name, namelen);
284 tag->name[namelen] =
0;
285 if(colorlen >
0) {
286 tag->color = malloc(colorlen +
1);
287 memcpy(tag->color, color, colorlen);
288 tag->color[colorlen] =
0;
289 }
else {
290 tag->color =
NULL;
291 }
292
293 return tag;
294 }
295
296 CxList* parse_macos_taglist(
const char *buf,
size_t length) {
297 CxList *taglist = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
298 taglist->simple_destructor = (cx_destructor_func)free_dav_tag;
299
300 CFDataRef data = CFDataCreateWithBytesNoCopy(
301 kCFAllocatorDefault,
302 (
const UInt8*)buf,
303 length,
304 kCFAllocatorNull);
305 CFPropertyListRef propertylist = CFPropertyListCreateWithData(kCFAllocatorDefault, data,
0,
NULL,
NULL);
306 CFArrayRef array = propertylist;
307 int count = CFArrayGetCount(array);
308 for(
int i=
0;i<count;i++) {
309 CFStringRef str = CFArrayGetValueAtIndex(array, i);
310 int slen = CFStringGetLength(str);
311 size_t cstrbuflen = slen *
4 +
4;
312 char *cstr = malloc(cstrbuflen);
313 if(CFStringGetCString(str, cstr, cstrbuflen, kCFStringEncodingUTF8)) {
314 DavTag *tag = tagstr2davtag(cstr);
315 if(tag) {
316 cxListAdd(taglist, tag);
317 }
318 }
319 free(cstr);
320 }
321
322 CFRelease(propertylist);
323 CFRelease(data);
324
325 return taglist;
326 }
327
328 CxBuffer* create_macos_taglist(CxList *tags) {
329 size_t count = tags->size;
330 if(count ==
0) {
331 return NULL;
332 }
333
334 CFStringRef *strings = calloc(
sizeof(CFStringRef), count);
335 int i =
0;
336 CxIterator iter = cxListIterator(tags);
337 cx_foreach(DavTag*, tag, iter) {
338 CFStringRef str =
NULL;
339 if(tag->color) {
340 cxmutstr s = cx_strcat(
3, cx_mutstr(tag->name),
CX_STR(
"\n"), cx_str(tag->color));
341 str = CFStringCreateWithCString(kCFAllocatorDefault, s.ptr, kCFStringEncodingUTF8);
342 free(s.ptr);
343 }
else {
344 str = CFStringCreateWithCString(kCFAllocatorDefault, tag->name, kCFStringEncodingUTF8);
345 }
346 strings[i] = str;
347 i++;
348 }
349
350 CFPropertyListRef array = CFArrayCreate(kCFAllocatorDefault, (
const void**)strings, count, &kCFTypeArrayCallBacks);
351 CFDataRef data = CFPropertyListCreateData(kCFAllocatorDefault, array, kCFPropertyListBinaryFormat_v1_0,
0,
NULL);
352
353 CxBuffer *buf =
NULL;
354 if(data) {
355 int datalen = CFDataGetLength(data);
356 CFRange range;
357 range.location =
0;
358 range.length = datalen;
359 buf = cxBufferCreate(
NULL, datalen, cxDefaultAllocator,
0);
360 CFDataGetBytes(data, range, (UInt8*)buf->space);
361 buf->size = datalen;
362 CFRelease(data);
363 }
364
365 for(
int i=
0;i<count;i++) {
366 CFRelease(strings[i]);
367 }
368 CFRelease(array);
369
370 return buf;
371 }
372
373 #else
374 CxList* parse_macos_taglist(
const char *buf,
size_t length) {
375 fprintf(stderr,
"Error: macos tags not supported on this platform.\n");
376 return NULL;
377 }
378 CxBuffer* create_macos_taglist(CxList *tags) {
379 fprintf(stderr,
"Error: macos tags not supported on this platform.\n");
380 return NULL;
381 }
382 #endif
383
384
385 int compare_taglists(CxList *tags1, CxList *tags2) {
386 if(!tags1) {
387 return tags2 ?
0 :
1;
388 }
389 if(!tags2) {
390 return tags1 ?
0 :
1;
391 }
392
393 CxMap *map1 = taglist2map(tags1);
394
395 int equal =
1;
396 int i =
0;
397 CxIterator iter = cxListIterator(tags2);
398 cx_foreach(DavTag*, t, iter) {
399 if(!cxMapGet(map1, cx_hash_key_str(t->name))) {
400 equal =
0;
401 break;
402 }
403 i++;
404 }
405
406 if(i != map1->size) {
407 equal =
0;
408 }
409 cxMapDestroy(map1);
410 return equal;
411 }
412
413 char* create_tags_hash(CxList *tags) {
414 if(!tags) {
415 return NULL;
416 }
417 CxBuffer *buf = create_text_taglist(tags);
418 char *hash = dav_create_hash(buf->space, buf->size);
419 cxBufferDestroy(buf);
420 return hash;
421 }
422
423 CxList* merge_tags(CxList *tags1, CxList *tags2) {
424
425 CxMap *tag_map = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS,
32);
426
427 CxList *new_tags = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
428 new_tags->simple_destructor = (cx_destructor_func)free_dav_tag;
429
430
431 if(tags1) {
432 CxIterator iter = cxListIterator(tags1);
433 cx_foreach(DavTag*, t, iter) {
434 cxMapPut(tag_map, cx_hash_key_str(t->name), t);
435 DavTag *newt = calloc(
1,
sizeof(DavTag));
436 newt->color = t->color ? strdup(t->color) :
NULL;
437 newt->name = strdup(t->name);
438 cxListAdd(new_tags, newt);
439 }
440 }
441
442
443
444 if(tags2) {
445 CxIterator iter = cxListIterator(tags2);
446 cx_foreach(DavTag*, t, iter) {
447 if(!cxMapGet(tag_map, cx_hash_key_str(t->name))) {
448 DavTag *newt = calloc(
1,
sizeof(DavTag));
449 newt->color = t->color ? strdup(t->color) :
NULL;
450 newt->name = strdup(t->name);
451 cxListAdd(new_tags, newt);
452 }
453 }
454 }
455
456 cxMapDestroy(tag_map);
457
458 return new_tags;
459 }
460
461 void add_tag_colors(CxList *taglist, CxList *colored) {
462 CxMap *tagmap = taglist2map(taglist);
463
464 CxIterator i = cxListIterator(colored);
465 cx_foreach(DavTag*, colored_tag, i) {
466 if(colored_tag->color) {
467 DavTag *tag = cxMapGet(tagmap, cx_hash_key_str(colored_tag->name));
468 if(tag && !tag->color) {
469 tag->color = strdup(colored_tag->color);
470 }
471 }
472 }
473
474 cxMapDestroy(tagmap);
475 }
476
477
478
479 static size_t rtrimskip(cxstring str,
size_t skip) {
480 while (skip < str.length && isspace(str.ptr[skip])) skip++;
481 return skip;
482 }
483
484 static size_t parse_tagfilter_taglist(cxstring fs, SyncTagFilter* tagfilter) {
485 size_t csvlen;
486 for (csvlen =
0 ; csvlen < fs.length ; ++csvlen) {
487 if (fs.ptr[csvlen] ==
')')
break;
488 }
489 fs.length = csvlen;
490
491 tagfilter->tags = parse_csv_taglist(fs.ptr, fs.length);
492
493 return csvlen;
494 }
495
496 static size_t parse_tagfilter_subfilters(cxstring fs, SyncTagFilter* tagfilter);
497
498 static size_t parse_tagfilter_filter(cxstring fs, SyncTagFilter* tagfilter) {
499
500 size_t consumed = rtrimskip(fs,
0);
501 fs = cx_strsubs(fs, consumed);
502
503 if (fs.length ==
0) {
504 return consumed;
505 }
else {
506
507
508 int hasop =
0;
509 if (fs.ptr[
0] ==
'&') {
510 tagfilter->mode =
DAV_SYNC_TAGFILTER_AND;
511 hasop =
1;
512 }
else if (fs.ptr[
0] ==
'|') {
513 tagfilter->mode =
DAV_SYNC_TAGFILTER_OR;
514 hasop =
1;
515 }
else if (fs.ptr[
0] ==
'0') {
516 tagfilter->mode =
DAV_SYNC_TAGFILTER_NONE;
517 hasop =
1;
518 }
else if (fs.ptr[
0] ==
'1') {
519 tagfilter->mode =
DAV_SYNC_TAGFILTER_ONE;
520 hasop =
1;
521 }
else {
522
523 tagfilter->mode =
DAV_SYNC_TAGFILTER_AND;
524 }
525
526 if (hasop) {
527 size_t skip = rtrimskip(fs,
1);
528 consumed += skip;
529 fs = cx_strsubs(fs, skip);
530 }
531
532 if (fs.length >
0 && fs.ptr[
0] ==
'(') {
533 size_t c = parse_tagfilter_subfilters(fs, tagfilter);
534 if (c) {
535 return consumed + c;
536 }
else {
537 return 0;
538 }
539 }
else {
540 tagfilter->subfilter_count =
0;
541 tagfilter->subfilters =
NULL;
542 return consumed + parse_tagfilter_taglist(fs, tagfilter);
543 }
544 }
545 }
546
547
548
549
550 static size_t parse_tagfilter_subfilters(cxstring fs, SyncTagFilter* f) {
551
552
553 size_t subfilter_cap =
8;
554 f->subfilters = calloc(subfilter_cap,
sizeof(SyncTagFilter*));
555 f->subfilter_count =
0;
556
557 size_t total_consumed =
0;
558 size_t c;
559 do {
560
561 c = rtrimskip(fs,
1);
562 fs = cx_strsubs(fs, c);
563 total_consumed += c;
564
565
566 if (f->subfilter_count >= subfilter_cap) {
567 subfilter_cap *=
2;
568 SyncTagFilter** newarr = realloc(f->subfilters,
569 subfilter_cap *
sizeof(SyncTagFilter*));
570 if (newarr) {
571 f->subfilters = newarr;
572 }
else {
573 abort();
574 }
575 }
576
577
578 SyncTagFilter* subf = calloc(
1,
sizeof(SyncTagFilter));
579
580
581 c = parse_tagfilter_filter(fs, subf);
582
583
584 if (c >
0 && fs.ptr[c] ==
')') {
585 f->subfilters[f->subfilter_count++] = subf;
586
587
588 c = rtrimskip(fs,
1+c);
589 fs = cx_strsubs(fs, c);
590 total_consumed += c;
591
592 if (fs.length ==
0 || fs.ptr[
0] ==
')') {
593
594 break;
595 }
else if (fs.ptr[
0] !=
'(') {
596
597 return 0;
598 }
599 }
else {
600 free(subf);
601 break;
602 }
603
604 }
while(
1);
605
606
607 if (f->subfilter_count >
0) {
608 SyncTagFilter** shrinked_array = realloc(f->subfilters,
609 f->subfilter_count *
sizeof(SyncTagFilter*));
610 if (shrinked_array) {
611 f->subfilters = shrinked_array;
612 }
613 }
else {
614 free(f->subfilters);
615 f->subfilters =
NULL;
616 }
617
618 return total_consumed;
619 }
620
621 SyncTagFilter* parse_tagfilter_string(
const char* filterstring,
int scope) {
622 SyncTagFilter* tagfilter = calloc(
1,
sizeof(SyncTagFilter));
623 tagfilter->scope = scope;
624 if (!filterstring) {
625 return tagfilter;
626 }
627
628 cxstring fs = cx_str(filterstring);
629 size_t consumed = parse_tagfilter_filter(fs, tagfilter);
630 if (!consumed) {
631 free_tagfilter(tagfilter);
632 return NULL;
633 }
634
635
636 consumed = rtrimskip(fs, consumed);
637
638
639 if (consumed != fs.length) {
640 free_tagfilter(tagfilter);
641 return NULL;
642 }
643
644 return tagfilter;
645 }
646
647 void free_tagfilter(SyncTagFilter* filter) {
648 for (
size_t i =
0 ; i < filter->subfilter_count ; i++) {
649 free_tagfilter(filter->subfilters[i]);
650 }
651 free(filter->subfilters);
652 free(filter);
653 }
654
655
656 static int matches_tags_and(CxList *dav_tags, CxList *tags,
int ignorecase) {
657
658 int ret =
1;
659 CxMap *tagmap = taglist2map(dav_tags);
660 CxIterator i = cxListIterator(tags);
661 cx_foreach(DavTag *, tag, i) {
662 if (cxMapGet(tagmap, cx_hash_key_str(tag->name)) ==
NULL) {
663 ret =
0;
664 break;
665 }
666 }
667 cxMapDestroy(tagmap);
668 return ret;
669 }
670
671 static int matches_tags_or(CxList *dav_tags, CxList *tags,
int ignorecase) {
672
673 int ret =
0;
674 CxMap *tagmap = taglist2map(dav_tags);
675 CxIterator i = cxListIterator(tags);
676 cx_foreach(DavTag *, tag, i) {
677 if (cxMapGet(tagmap, cx_hash_key_str(tag->name))) {
678 ret =
1;
679 break;
680 }
681 }
682 cxMapDestroy(tagmap);
683 return ret;
684 }
685
686 static int matches_tags_one(CxList *dav_tags, CxList *tags,
int ignorecase) {
687 int matches_exactly_one =
0;
688 CxMap *tagmap = taglist2map(dav_tags);
689 CxIterator i = cxListIterator(tags);
690 cx_foreach(DavTag *, tag, i) {
691 if (cxMapGet(tagmap, cx_hash_key_str(tag->name))) {
692 if (matches_exactly_one) {
693 cxMapDestroy(tagmap);
694 return 0;
695 }
else {
696 matches_exactly_one =
1;
697 }
698 }
699 }
700 cxMapDestroy(tagmap);
701 return matches_exactly_one;
702 }
703
704 static int matches_subfilters_and(CxList *dav_tags, SyncTagFilter *filter) {
705 int ret =
1;
706 for (
size_t i =
0 ; i < filter->subfilter_count ; i++) {
707 ret &= matches_tagfilter(dav_tags, filter->subfilters[i]);
708 }
709 return ret;
710 }
711
712 static int matches_subfilters_or(CxList *dav_tags, SyncTagFilter *filter) {
713 int ret =
0;
714 for (
size_t i =
0 ; i < filter->subfilter_count ; i++) {
715 ret |= matches_tagfilter(dav_tags, filter->subfilters[i]);
716 }
717 return ret;
718 }
719
720 static int matches_subfilters_one(CxList *dav_tags, SyncTagFilter *filter) {
721 int one =
0;
722 for (
size_t i =
0 ; i < filter->subfilter_count ; i++) {
723 if (matches_tagfilter(dav_tags, filter->subfilters[i])) {
724 if (one) {
725 return 0;
726 }
else {
727 one =
1;
728 }
729 }
730 }
731 return one;
732 }
733
734 int matches_tagfilter(CxList *dav_tags, SyncTagFilter *tagfilter) {
735
736 if (tagfilter->subfilter_count >
0) {
737 switch (tagfilter->mode) {
738 case DAV_SYNC_TAGFILTER_OR:
739 return matches_subfilters_or(dav_tags, tagfilter);
740 case DAV_SYNC_TAGFILTER_AND:
741 return matches_subfilters_and(dav_tags, tagfilter);
742 case DAV_SYNC_TAGFILTER_NONE:
743 return !matches_subfilters_or(dav_tags, tagfilter);
744 case DAV_SYNC_TAGFILTER_ONE:
745 return matches_subfilters_one(dav_tags, tagfilter);
746 default:
747 abort();
748 }
749 }
else {
750 int ignorecase =
0;
751 switch (tagfilter->mode) {
752 case DAV_SYNC_TAGFILTER_OR:
753 return matches_tags_or(dav_tags, tagfilter->tags, ignorecase);
754 case DAV_SYNC_TAGFILTER_AND:
755 return matches_tags_and(dav_tags, tagfilter->tags, ignorecase);
756 case DAV_SYNC_TAGFILTER_NONE:
757 return !matches_tags_or(dav_tags, tagfilter->tags, ignorecase);
758 case DAV_SYNC_TAGFILTER_ONE:
759 return matches_tags_one(dav_tags, tagfilter->tags, ignorecase);
760 default:
761 abort();
762 }
763 }
764 }
765
766