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 cxDefineDestructor(tags, 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, cxListSize(tags) +
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 cxDefineDestructor(taglist, 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 name = value;
189 }
190 if(!strcmp(c->name,
"color")) {
191 color = value;
192 }
193 }
194 }
195 }
196 c = c->next;
197 }
198
199 DavTag *tag =
NULL;
200 if(name) {
201 tag = malloc(
sizeof(DavTag));
202 tag->name = strdup(name);
203 tag->color = color ? strdup(color) :
NULL;
204 }
205 return tag;
206 }
207
208 CxList* parse_dav_xml_taglist(DavXmlNode *taglistnode) {
209 CxList *tags = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
210 cxDefineDestructor(tags, free_dav_tag);
211
212 DavXmlNode *node = taglistnode;
213 while(node) {
214 if(node->type ==
DAV_XML_ELEMENT) {
215 if(!strcmp(node->namespace,
DAV_PROPS_NS) && !strcmp(node->name,
"tag")) {
216 DavTag *tag = parse_xml_dav_tag(node);
217 if(tag) {
218 cxListAdd(tags, tag);
219 }
220 }
221 }
222 node = node->next;
223 }
224
225 return tags;
226 }
227
228 DavXmlNode* create_xml_taglist(CxList *tags) {
229 DavXmlNode *tag1 =
NULL;
230 DavXmlNode *lasttag =
NULL;
231 CxIterator i = cxListIterator(tags);
232 cx_foreach(DavTag*, tag, i) {
233 DavXmlNode *tagelm = dav_xml_createnode(
DAV_PROPS_NS,
"tag");
234 DavXmlNode *tagname = dav_xml_createnode_with_text(
DAV_PROPS_NS,
"name", tag->name);
235 tagelm->children = tagname;
236 if(tag->color) {
237 DavXmlNode *tagcolor = dav_xml_createnode_with_text(
DAV_PROPS_NS,
"color", tag->color);
238 tagname->next = tagcolor;
239 }
240
241 if(lasttag) {
242 lasttag->next = tagelm;
243 tagelm->prev = lasttag;
244 }
else {
245 tag1 = tagelm;
246 }
247 lasttag = tagelm;
248 }
249 return tag1;
250 }
251
252
253 #ifdef __APPLE__
254 static DavTag* tagstr2davtag(
const char *str) {
255 const char *name = str;
256 const char *color =
NULL;
257 size_t len = strlen(str);
258 size_t namelen = len;
259
260 if(len ==
0) {
261 return NULL;
262 }
263
264
265 for(
int i=
1;i<len;i++) {
266 if(str[i] ==
'\n') {
267 if(!color) {
268 color = str + i +
1;
269 namelen = i;
270 }
271 }
272 }
273 int colorlen = len - namelen -
1;
274
275 DavTag *tag = malloc(
sizeof(DavTag));
276 tag->name = malloc(namelen +
1);
277 memcpy(tag->name, name, namelen);
278 tag->name[namelen] =
0;
279 if(colorlen >
0) {
280 tag->color = malloc(colorlen +
1);
281 memcpy(tag->color, color, colorlen);
282 tag->color[colorlen] =
0;
283 }
else {
284 tag->color =
NULL;
285 }
286
287 return tag;
288 }
289
290 CxList* parse_macos_taglist(
const char *buf,
size_t length) {
291 CxList *taglist = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
292 taglist->simple_destructor = (cx_destructor_func)free_dav_tag;
293
294 CFDataRef data = CFDataCreateWithBytesNoCopy(
295 kCFAllocatorDefault,
296 (
const UInt8*)buf,
297 length,
298 kCFAllocatorNull);
299 CFPropertyListRef propertylist = CFPropertyListCreateWithData(kCFAllocatorDefault, data,
0,
NULL,
NULL);
300 CFArrayRef array = propertylist;
301 int count = CFArrayGetCount(array);
302 for(
int i=
0;i<count;i++) {
303 CFStringRef str = CFArrayGetValueAtIndex(array, i);
304 int slen = CFStringGetLength(str);
305 size_t cstrbuflen = slen *
4 +
4;
306 char *cstr = malloc(cstrbuflen);
307 if(CFStringGetCString(str, cstr, cstrbuflen, kCFStringEncodingUTF8)) {
308 DavTag *tag = tagstr2davtag(cstr);
309 if(tag) {
310 cxListAdd(taglist, tag);
311 }
312 }
313 free(cstr);
314 }
315
316 CFRelease(propertylist);
317 CFRelease(data);
318
319 return taglist;
320 }
321
322 CxBuffer* create_macos_taglist(CxList *tags) {
323 size_t count = tags->size;
324 if(count ==
0) {
325 return NULL;
326 }
327
328 CFStringRef *strings = calloc(
sizeof(CFStringRef), count);
329 int i =
0;
330 CxIterator iter = cxListIterator(tags);
331 cx_foreach(DavTag*, tag, iter) {
332 CFStringRef str =
NULL;
333 if(tag->color) {
334 cxmutstr s = cx_strcat(
3, cx_mutstr(tag->name),
CX_STR(
"\n"), cx_str(tag->color));
335 str = CFStringCreateWithCString(kCFAllocatorDefault, s.ptr, kCFStringEncodingUTF8);
336 free(s.ptr);
337 }
else {
338 str = CFStringCreateWithCString(kCFAllocatorDefault, tag->name, kCFStringEncodingUTF8);
339 }
340 strings[i] = str;
341 i++;
342 }
343
344 CFPropertyListRef array = CFArrayCreate(kCFAllocatorDefault, (
const void**)strings, count, &kCFTypeArrayCallBacks);
345 CFDataRef data = CFPropertyListCreateData(kCFAllocatorDefault, array, kCFPropertyListBinaryFormat_v1_0,
0,
NULL);
346
347 CxBuffer *buf =
NULL;
348 if(data) {
349 int datalen = CFDataGetLength(data);
350 CFRange range;
351 range.location =
0;
352 range.length = datalen;
353 buf = cxBufferCreate(
NULL, datalen, cxDefaultAllocator,
0);
354 CFDataGetBytes(data, range, (UInt8*)buf->space);
355 buf->size = datalen;
356 CFRelease(data);
357 }
358
359 for(
int i=
0;i<count;i++) {
360 CFRelease(strings[i]);
361 }
362 CFRelease(array);
363
364 return buf;
365 }
366
367 #else
368 CxList* parse_macos_taglist(
const char *buf,
size_t length) {
369 fprintf(stderr,
"Error: macos tags not supported on this platform.\n");
370 return NULL;
371 }
372 CxBuffer* create_macos_taglist(CxList *tags) {
373 fprintf(stderr,
"Error: macos tags not supported on this platform.\n");
374 return NULL;
375 }
376 #endif
377
378
379 int compare_taglists(CxList *tags1, CxList *tags2) {
380 if(!tags1) {
381 return tags2 ?
0 :
1;
382 }
383 if(!tags2) {
384 return tags1 ?
0 :
1;
385 }
386
387 CxMap *map1 = taglist2map(tags1);
388
389 int equal =
1;
390 int i =
0;
391 CxIterator iter = cxListIterator(tags2);
392 cx_foreach(DavTag*, t, iter) {
393 if(!cxMapGet(map1, cx_hash_key_str(t->name))) {
394 equal =
0;
395 break;
396 }
397 i++;
398 }
399
400 if(i != cxMapSize(map1)) {
401 equal =
0;
402 }
403 cxMapDestroy(map1);
404 return equal;
405 }
406
407 char* create_tags_hash(CxList *tags) {
408 if(!tags) {
409 return NULL;
410 }
411 CxBuffer *buf = create_text_taglist(tags);
412 char *hash = dav_create_hash(buf->space, buf->size);
413 cxBufferDestroy(buf);
414 return hash;
415 }
416
417 CxList* merge_tags(CxList *tags1, CxList *tags2) {
418
419 CxMap *tag_map = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS,
32);
420
421 CxList *new_tags = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
422 cxDefineDestructor(new_tags, free_dav_tag);
423
424
425 if(tags1) {
426 CxIterator iter = cxListIterator(tags1);
427 cx_foreach(DavTag*, t, iter) {
428 cxMapPut(tag_map, cx_hash_key_str(t->name), t);
429 DavTag *newt = calloc(
1,
sizeof(DavTag));
430 newt->color = t->color ? strdup(t->color) :
NULL;
431 newt->name = strdup(t->name);
432 cxListAdd(new_tags, newt);
433 }
434 }
435
436
437
438 if(tags2) {
439 CxIterator iter = cxListIterator(tags2);
440 cx_foreach(DavTag*, t, iter) {
441 if(!cxMapGet(tag_map, cx_hash_key_str(t->name))) {
442 DavTag *newt = calloc(
1,
sizeof(DavTag));
443 newt->color = t->color ? strdup(t->color) :
NULL;
444 newt->name = strdup(t->name);
445 cxListAdd(new_tags, newt);
446 }
447 }
448 }
449
450 cxMapDestroy(tag_map);
451
452 return new_tags;
453 }
454
455 void add_tag_colors(CxList *taglist, CxList *colored) {
456 CxMap *tagmap = taglist2map(taglist);
457
458 CxIterator i = cxListIterator(colored);
459 cx_foreach(DavTag*, colored_tag, i) {
460 if(colored_tag->color) {
461 DavTag *tag = cxMapGet(tagmap, cx_hash_key_str(colored_tag->name));
462 if(tag && !tag->color) {
463 tag->color = strdup(colored_tag->color);
464 }
465 }
466 }
467
468 cxMapDestroy(tagmap);
469 }
470
471
472
473 static size_t rtrimskip(cxstring str,
size_t skip) {
474 while (skip < str.length && isspace(str.ptr[skip])) skip++;
475 return skip;
476 }
477
478 static size_t parse_tagfilter_taglist(cxstring fs, SyncTagFilter* tagfilter) {
479 size_t csvlen;
480 for (csvlen =
0 ; csvlen < fs.length ; ++csvlen) {
481 if (fs.ptr[csvlen] ==
')')
break;
482 }
483 fs.length = csvlen;
484
485 tagfilter->tags = parse_csv_taglist(fs.ptr, fs.length);
486
487 return csvlen;
488 }
489
490 static size_t parse_tagfilter_subfilters(cxstring fs, SyncTagFilter* tagfilter);
491
492 static size_t parse_tagfilter_filter(cxstring fs, SyncTagFilter* tagfilter) {
493
494 size_t consumed = rtrimskip(fs,
0);
495 fs = cx_strsubs(fs, consumed);
496
497 if (fs.length ==
0) {
498 return consumed;
499 }
else {
500
501
502 int hasop =
0;
503 if (fs.ptr[
0] ==
'&') {
504 tagfilter->mode =
DAV_SYNC_TAGFILTER_AND;
505 hasop =
1;
506 }
else if (fs.ptr[
0] ==
'|') {
507 tagfilter->mode =
DAV_SYNC_TAGFILTER_OR;
508 hasop =
1;
509 }
else if (fs.ptr[
0] ==
'0') {
510 tagfilter->mode =
DAV_SYNC_TAGFILTER_NONE;
511 hasop =
1;
512 }
else if (fs.ptr[
0] ==
'1') {
513 tagfilter->mode =
DAV_SYNC_TAGFILTER_ONE;
514 hasop =
1;
515 }
else {
516
517 tagfilter->mode =
DAV_SYNC_TAGFILTER_AND;
518 }
519
520 if (hasop) {
521 size_t skip = rtrimskip(fs,
1);
522 consumed += skip;
523 fs = cx_strsubs(fs, skip);
524 }
525
526 if (fs.length >
0 && fs.ptr[
0] ==
'(') {
527 size_t c = parse_tagfilter_subfilters(fs, tagfilter);
528 if (c) {
529 return consumed + c;
530 }
else {
531 return 0;
532 }
533 }
else {
534 tagfilter->subfilter_count =
0;
535 tagfilter->subfilters =
NULL;
536 return consumed + parse_tagfilter_taglist(fs, tagfilter);
537 }
538 }
539 }
540
541
542
543
544 static size_t parse_tagfilter_subfilters(cxstring fs, SyncTagFilter* f) {
545
546
547 size_t subfilter_cap =
8;
548 f->subfilters = calloc(subfilter_cap,
sizeof(SyncTagFilter*));
549 f->subfilter_count =
0;
550
551 size_t total_consumed =
0;
552 size_t c;
553 do {
554
555 c = rtrimskip(fs,
1);
556 fs = cx_strsubs(fs, c);
557 total_consumed += c;
558
559
560 if (f->subfilter_count >= subfilter_cap) {
561 subfilter_cap *=
2;
562 SyncTagFilter** newarr = realloc(f->subfilters,
563 subfilter_cap *
sizeof(SyncTagFilter*));
564 if (newarr) {
565 f->subfilters = newarr;
566 }
else {
567 abort();
568 }
569 }
570
571
572 SyncTagFilter* subf = calloc(
1,
sizeof(SyncTagFilter));
573
574
575 c = parse_tagfilter_filter(fs, subf);
576
577
578 if (c >
0 && fs.ptr[c] ==
')') {
579 f->subfilters[f->subfilter_count++] = subf;
580
581
582 c = rtrimskip(fs,
1+c);
583 fs = cx_strsubs(fs, c);
584 total_consumed += c;
585
586 if (fs.length ==
0 || fs.ptr[
0] ==
')') {
587
588 break;
589 }
else if (fs.ptr[
0] !=
'(') {
590
591 return 0;
592 }
593 }
else {
594 free(subf);
595 break;
596 }
597
598 }
while(
1);
599
600
601 if (f->subfilter_count >
0) {
602 SyncTagFilter** shrinked_array = realloc(f->subfilters,
603 f->subfilter_count *
sizeof(SyncTagFilter*));
604 if (shrinked_array) {
605 f->subfilters = shrinked_array;
606 }
607 }
else {
608 free(f->subfilters);
609 f->subfilters =
NULL;
610 }
611
612 return total_consumed;
613 }
614
615 SyncTagFilter* parse_tagfilter_string(
const char* filterstring,
int scope) {
616 SyncTagFilter* tagfilter = calloc(
1,
sizeof(SyncTagFilter));
617 tagfilter->scope = scope;
618 if (!filterstring) {
619 return tagfilter;
620 }
621
622 cxstring fs = cx_str(filterstring);
623 size_t consumed = parse_tagfilter_filter(fs, tagfilter);
624 if (!consumed) {
625 free_tagfilter(tagfilter);
626 return NULL;
627 }
628
629
630 consumed = rtrimskip(fs, consumed);
631
632
633 if (consumed != fs.length) {
634 free_tagfilter(tagfilter);
635 return NULL;
636 }
637
638 return tagfilter;
639 }
640
641 void free_tagfilter(SyncTagFilter* filter) {
642 for (
size_t i =
0 ; i < filter->subfilter_count ; i++) {
643 free_tagfilter(filter->subfilters[i]);
644 }
645 free(filter->subfilters);
646 free(filter);
647 }
648
649
650 static int matches_tags_and(CxList *dav_tags, CxList *tags,
int ignorecase) {
651
652 int ret =
1;
653 CxMap *tagmap = taglist2map(dav_tags);
654 CxIterator i = cxListIterator(tags);
655 cx_foreach(DavTag *, tag, i) {
656 if (cxMapGet(tagmap, cx_hash_key_str(tag->name)) ==
NULL) {
657 ret =
0;
658 break;
659 }
660 }
661 cxMapDestroy(tagmap);
662 return ret;
663 }
664
665 static int matches_tags_or(CxList *dav_tags, CxList *tags,
int ignorecase) {
666
667 int ret =
0;
668 CxMap *tagmap = taglist2map(dav_tags);
669 CxIterator i = cxListIterator(tags);
670 cx_foreach(DavTag *, tag, i) {
671 if (cxMapGet(tagmap, cx_hash_key_str(tag->name))) {
672 ret =
1;
673 break;
674 }
675 }
676 cxMapDestroy(tagmap);
677 return ret;
678 }
679
680 static int matches_tags_one(CxList *dav_tags, CxList *tags,
int ignorecase) {
681 int matches_exactly_one =
0;
682 CxMap *tagmap = taglist2map(dav_tags);
683 CxIterator i = cxListIterator(tags);
684 cx_foreach(DavTag *, tag, i) {
685 if (cxMapGet(tagmap, cx_hash_key_str(tag->name))) {
686 if (matches_exactly_one) {
687 cxMapDestroy(tagmap);
688 return 0;
689 }
else {
690 matches_exactly_one =
1;
691 }
692 }
693 }
694 cxMapDestroy(tagmap);
695 return matches_exactly_one;
696 }
697
698 static int matches_subfilters_and(CxList *dav_tags, SyncTagFilter *filter) {
699 int ret =
1;
700 for (
size_t i =
0 ; i < filter->subfilter_count ; i++) {
701 ret &= matches_tagfilter(dav_tags, filter->subfilters[i]);
702 }
703 return ret;
704 }
705
706 static int matches_subfilters_or(CxList *dav_tags, SyncTagFilter *filter) {
707 int ret =
0;
708 for (
size_t i =
0 ; i < filter->subfilter_count ; i++) {
709 ret |= matches_tagfilter(dav_tags, filter->subfilters[i]);
710 }
711 return ret;
712 }
713
714 static int matches_subfilters_one(CxList *dav_tags, SyncTagFilter *filter) {
715 int one =
0;
716 for (
size_t i =
0 ; i < filter->subfilter_count ; i++) {
717 if (matches_tagfilter(dav_tags, filter->subfilters[i])) {
718 if (one) {
719 return 0;
720 }
else {
721 one =
1;
722 }
723 }
724 }
725 return one;
726 }
727
728 int matches_tagfilter(CxList *dav_tags, SyncTagFilter *tagfilter) {
729
730 if (tagfilter->subfilter_count >
0) {
731 switch (tagfilter->mode) {
732 case DAV_SYNC_TAGFILTER_OR:
733 return matches_subfilters_or(dav_tags, tagfilter);
734 case DAV_SYNC_TAGFILTER_AND:
735 return matches_subfilters_and(dav_tags, tagfilter);
736 case DAV_SYNC_TAGFILTER_NONE:
737 return !matches_subfilters_or(dav_tags, tagfilter);
738 case DAV_SYNC_TAGFILTER_ONE:
739 return matches_subfilters_one(dav_tags, tagfilter);
740 default:
741 abort();
742 }
743 }
else {
744 int ignorecase =
0;
745 switch (tagfilter->mode) {
746 case DAV_SYNC_TAGFILTER_OR:
747 return matches_tags_or(dav_tags, tagfilter->tags, ignorecase);
748 case DAV_SYNC_TAGFILTER_AND:
749 return matches_tags_and(dav_tags, tagfilter->tags, ignorecase);
750 case DAV_SYNC_TAGFILTER_NONE:
751 return !matches_tags_or(dav_tags, tagfilter->tags, ignorecase);
752 case DAV_SYNC_TAGFILTER_ONE:
753 return matches_tags_one(dav_tags, tagfilter->tags, ignorecase);
754 default:
755 abort();
756 }
757 }
758 }
759
760