1 /* jmap_util.c -- Helper routines for JMAP
2  *
3  * Copyright (c) 1994-2018 Carnegie Mellon University.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in
14  *    the documentation and/or other materials provided with the
15  *    distribution.
16  *
17  * 3. The name "Carnegie Mellon University" must not be used to
18  *    endorse or promote products derived from this software without
19  *    prior written permission. For permission or any legal
20  *    details, please contact
21  *      Carnegie Mellon University
22  *      Center for Technology Transfer and Enterprise Creation
23  *      4615 Forbes Avenue
24  *      Suite 302
25  *      Pittsburgh, PA  15213
26  *      (412) 268-7393, fax: (412) 268-7395
27  *      innovation@andrew.cmu.edu
28  *
29  * 4. Redistributions of any form whatsoever must retain the following
30  *    acknowledgment:
31  *    "This product includes software developed by Computing Services
32  *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
33  *
34  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
35  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
36  * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
37  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
38  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
39  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
40  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
41  *
42  */
43 
44 #include <config.h>
45 
46 #include <string.h>
47 #include <syslog.h>
48 #include <assert.h>
49 
50 #include <sasl/saslutil.h>
51 
52 #include "annotate.h"
53 #include "carddav_db.h"
54 #include "global.h"
55 #include "hash.h"
56 #include "index.h"
57 #include "jmap_util.h"
58 #include "json_support.h"
59 #include "search_query.h"
60 #include "times.h"
61 #include "xapian_wrap.h"
62 
63 #ifdef HAVE_LIBCHARDET
64 #include <chardet/chardet.h>
65 #endif
66 
67 /* generated headers are not necessarily in current directory */
68 #include "imap/imap_err.h"
69 
jmap_readprop_full(json_t * root,const char * prefix,const char * name,int mandatory,json_t * invalid,const char * fmt,void * dst)70 EXPORTED int jmap_readprop_full(json_t *root, const char *prefix, const char *name,
71                               int mandatory, json_t *invalid, const char *fmt,
72                               void *dst)
73 {
74     int r = 0;
75     json_t *jval = json_object_get(root, name);
76     if (!jval && mandatory) {
77         r = -1;
78     } else if (jval) {
79         json_error_t err;
80         if (json_unpack_ex(jval, &err, 0, fmt, dst)) {
81             r = -2;
82         } else {
83             r = 1;
84         }
85     }
86     if (r < 0 && prefix) {
87         struct buf buf = BUF_INITIALIZER;
88         buf_printf(&buf, "%s.%s", prefix, name);
89         json_array_append_new(invalid, json_string(buf_cstring(&buf)));
90         buf_free(&buf);
91     } else if (r < 0) {
92         json_array_append_new(invalid, json_string(name));
93     }
94     return r;
95 }
96 
jmap_pointer_needsencode(const char * src)97 EXPORTED int jmap_pointer_needsencode(const char *src)
98 {
99     return strchr(src, '/') || strchr(src, '~');
100 }
101 
jmap_pointer_encode(const char * src)102 EXPORTED char* jmap_pointer_encode(const char *src)
103 {
104     struct buf buf = BUF_INITIALIZER;
105     const char *base, *top;
106     buf_ensure(&buf, strlen(src));
107 
108     base = src;
109     top = base;
110     while (*base) {
111         for (top = base; *top && *top != '~' && *top != '/'; top++)
112             ;
113         if (!*top) break;
114 
115         buf_appendmap(&buf, base, top-base);
116         if (*top == '~') {
117             buf_appendmap(&buf, "~0", 2);
118             top++;
119         } else if (*top == '/') {
120             buf_appendmap(&buf, "~1", 2);
121             top++;
122         }
123         base = top;
124     }
125     buf_appendmap(&buf, base, top-base);
126     return buf_release(&buf);
127 }
128 
jmap_pointer_decode(const char * src,size_t len)129 EXPORTED char *jmap_pointer_decode(const char *src, size_t len)
130 {
131     struct buf buf = BUF_INITIALIZER;
132     const char *base, *top, *end;
133 
134     buf_ensure(&buf, len);
135     end = src + len;
136 
137     base = src;
138     while (base < end && (top = strchr(base, '~')) && top < end) {
139         buf_appendmap(&buf, base, top-base);
140 
141         if (top < end-1 && *(top+1) == '0') {
142             buf_appendcstr(&buf, "~");
143             base = top + 2;
144         } else if (top < end-1 && *(top+1) == '1') {
145             buf_appendcstr(&buf, "/");
146             base = top + 2;
147         } else {
148             buf_appendcstr(&buf, "~");
149             base = top + 1;
150         }
151     }
152     if (base < end) {
153         buf_appendmap(&buf, base, end-base);
154     }
155 
156     return buf_release(&buf);
157 }
158 
jmap_patchobject_apply(json_t * val,json_t * patch,json_t * invalid)159 EXPORTED json_t* jmap_patchobject_apply(json_t *val, json_t *patch, json_t *invalid)
160 {
161     const char *path;
162     json_t *newval, *dst;
163 
164     dst = json_deep_copy(val);
165     json_object_foreach(patch, path, newval) {
166         /* Start traversal at root object */
167         json_t *it = dst;
168         const char *base = path, *top;
169         /* Find path in object tree */
170         while ((top = strchr(base, '/'))) {
171             char *name = jmap_pointer_decode(base, top-base);
172             it = json_object_get(it, name);
173             free(name);
174             base = top + 1;
175         }
176         if (!it) {
177             /* No such path in 'val' */
178             if (invalid) {
179                 json_array_append_new(invalid, json_string(path));
180             }
181             json_decref(dst);
182             return NULL;
183         }
184         /* Replace value at path */
185         char *name = jmap_pointer_decode(base, strlen(base));
186         if (newval == json_null()) {
187             json_object_del(it, name);
188         } else {
189             json_object_set(it, name, newval);
190         }
191         free(name);
192     }
193 
194     return dst;
195 }
196 
jmap_patchobject_set(json_t * diff,struct buf * path,const char * key,json_t * val)197 static void jmap_patchobject_set(json_t *diff, struct buf *path,
198                                  const char *key, json_t *val)
199 {
200     char *enckey = jmap_pointer_encode(key);
201     size_t len = buf_len(path);
202     if (len) buf_appendcstr(path, "/");
203     buf_appendcstr(path, enckey);
204     json_object_set(diff, buf_cstring(path), val);
205     buf_truncate(path, len);
206     free(enckey);
207 }
208 
jmap_patchobject_diff(json_t * diff,struct buf * path,json_t * src,json_t * dst)209 static void jmap_patchobject_diff(json_t *diff, struct buf *path,
210                                   json_t *src, json_t *dst)
211 {
212     if (!json_is_object(src) || !json_is_object(dst))
213         return;
214 
215     const char *key;
216     json_t *val;
217 
218     // Add any properties that are set in dst but not in src
219     json_object_foreach(dst, key, val) {
220         if (json_object_get(src, key) == NULL) {
221             jmap_patchobject_set(diff, path, key, val);
222         }
223     }
224 
225     // Remove any properties that are set in src but not in dst
226     json_object_foreach(src, key, val) {
227         if (json_object_get(dst, key) == NULL) {
228             jmap_patchobject_set(diff, path, key, json_null());
229         }
230     }
231 
232     // Handle properties that exist in both src and dst
233     json_object_foreach(dst, key, val) {
234         json_t *srcval = json_object_get(src, key);
235         if (!srcval) {
236             continue;
237         }
238         if (json_typeof(val) != JSON_OBJECT) {
239             if (!json_equal(val, srcval)) {
240                 jmap_patchobject_set(diff, path, key, val);
241             }
242         }
243         else if (json_typeof(srcval) != JSON_OBJECT) {
244             jmap_patchobject_set(diff, path, key, val);
245         }
246         else {
247             char *enckey = jmap_pointer_encode(key);
248             size_t len = buf_len(path);
249             if (len) buf_appendcstr(path, "/");
250             buf_appendcstr(path, enckey);
251             jmap_patchobject_diff(diff, path, srcval, val);
252             buf_truncate(path, len);
253             free(enckey);
254         }
255     }
256 }
257 
jmap_patchobject_create(json_t * src,json_t * dst)258 EXPORTED json_t *jmap_patchobject_create(json_t *src, json_t *dst)
259 {
260     json_t *diff = json_object();
261     struct buf buf = BUF_INITIALIZER;
262 
263     jmap_patchobject_diff(diff, &buf, src, dst);
264 
265     buf_free(&buf);
266     return diff;
267 }
268 
jmap_filterprops(json_t * jobj,hash_table * props)269 EXPORTED void jmap_filterprops(json_t *jobj, hash_table *props)
270 {
271     if (!props) return;
272 
273     const char *key;
274     json_t *jval;
275     void *tmp;
276     json_object_foreach_safe(jobj, tmp, key, jval) {
277         if (!hash_lookup(key, props)) {
278             json_object_del(jobj, key);
279         }
280     }
281 }
282 
address_to_smtp(smtp_addr_t * smtpaddr,json_t * addr)283 static void address_to_smtp(smtp_addr_t *smtpaddr, json_t *addr)
284 {
285     smtpaddr->addr = xstrdup(json_string_value(json_object_get(addr, "email")));
286 
287     const char *key;
288     json_t *val;
289     struct buf xtext = BUF_INITIALIZER;
290     json_object_foreach(json_object_get(addr, "parameters"), key, val) {
291         /* We never take AUTH at face value */
292         if (!strcasecmp(key, "AUTH")) {
293             continue;
294         }
295         /* We handle FUTURERELEASE ourselves */
296         else if (!strcasecmp(key, "HOLDFOR") || !strcasecmp(key, "HOLDUNTIL")) {
297             continue;
298         }
299         /* Encode xtext value */
300         if (json_is_string(val)) {
301             const char *p;
302             for (p = json_string_value(val); *p; p++) {
303                 if (('!' <= *p && *p <= '~') && *p != '=' && *p != '+') {
304                     buf_putc(&xtext, *p);
305                 }
306                 else buf_printf(&xtext, "+%02X", *p);
307             }
308         }
309         /* Build parameter */
310         smtp_param_t *param = xzmalloc(sizeof(smtp_param_t));
311         param->key = xstrdup(key);
312         param->val = buf_len(&xtext) ? xstrdup(buf_cstring(&xtext)) : NULL;
313         ptrarray_append(&smtpaddr->params, param);
314         buf_reset(&xtext);
315     }
316     buf_free(&xtext);
317 }
318 
jmap_emailsubmission_envelope_to_smtp(smtp_envelope_t * smtpenv,json_t * env)319 EXPORTED void jmap_emailsubmission_envelope_to_smtp(smtp_envelope_t *smtpenv,
320                                                     json_t *env)
321 {
322     address_to_smtp(&smtpenv->from, json_object_get(env, "mailFrom"));
323     size_t i;
324     json_t *val;
325     json_array_foreach(json_object_get(env, "rcptTo"), i, val) {
326         smtp_addr_t *smtpaddr = xzmalloc(sizeof(smtp_addr_t));
327         address_to_smtp(smtpaddr, val);
328         ptrarray_append(&smtpenv->rcpts, smtpaddr);
329     }
330 }
331 
jmap_fetch_snoozed(const char * mbox,uint32_t uid)332 EXPORTED json_t *jmap_fetch_snoozed(const char *mbox, uint32_t uid)
333 {
334     /* get the snoozed annotation */
335     const char *annot = IMAP_ANNOT_NS "snoozed";
336     struct buf value = BUF_INITIALIZER;
337     json_t *snooze = NULL;
338     int r;
339 
340     r = annotatemore_msg_lookup(mbox, uid, annot, "", &value);
341 
342     if (!r) {
343         if (!buf_len(&value)) {
344             /* get the legacy snoozed-until annotation */
345             annot = IMAP_ANNOT_NS "snoozed-until";
346 
347             r = annotatemore_msg_lookup(mbox, uid, annot, "", &value);
348             if (!r && buf_len(&value)) {
349                 /* build a SnoozeDetails object from the naked "until" value */
350                 snooze = json_pack("{s:s}",
351                                    "until", json_string(buf_cstring(&value)));
352             }
353         }
354         else {
355             json_error_t jerr;
356 
357             snooze = json_loadb(buf_base(&value), buf_len(&value), 0, &jerr);
358         }
359     }
360 
361     buf_free(&value);
362 
363     return snooze;
364 }
365 
jmap_email_keyword_is_valid(const char * keyword)366 EXPORTED int jmap_email_keyword_is_valid(const char *keyword)
367 {
368     const char *p;
369 
370     if (*keyword == '\0') {
371         return 0;
372     }
373     if (strlen(keyword) > 255) {
374         return 0;
375     }
376     for (p = keyword; *p; p++) {
377         if (*p < 0x21 || *p > 0x7e) {
378             return 0;
379         }
380         switch(*p) {
381             case '(':
382             case ')':
383             case '{':
384             case ']':
385             case '%':
386             case '*':
387             case '"':
388             case '\\':
389                 return 0;
390             default:
391                 ;
392         }
393     }
394     return 1;
395 }
396 
jmap_keyword_to_imap(const char * keyword)397 EXPORTED const char *jmap_keyword_to_imap(const char *keyword)
398 {
399     if (!strcasecmp(keyword, "$Seen")) {
400         return "\\Seen";
401     }
402     else if (!strcasecmp(keyword, "$Flagged")) {
403         return "\\Flagged";
404     }
405     else if (!strcasecmp(keyword, "$Answered")) {
406         return "\\Answered";
407     }
408     else if (!strcasecmp(keyword, "$Draft")) {
409         return "\\Draft";
410     }
411     else if (jmap_email_keyword_is_valid(keyword)) {
412         return keyword;
413     }
414     return NULL;
415 }
416 
jmap_parser_fini(struct jmap_parser * parser)417 HIDDEN void jmap_parser_fini(struct jmap_parser *parser)
418 {
419     strarray_fini(&parser->path);
420     json_decref(parser->invalid);
421     buf_free(&parser->buf);
422 }
423 
jmap_parser_push(struct jmap_parser * parser,const char * prop)424 HIDDEN void jmap_parser_push(struct jmap_parser *parser, const char *prop)
425 {
426     strarray_push(&parser->path, prop);
427 }
428 
jmap_parser_push_index(struct jmap_parser * parser,const char * prop,size_t index,const char * name)429 HIDDEN void jmap_parser_push_index(struct jmap_parser *parser, const char *prop,
430                                    size_t index, const char *name)
431 {
432     /* TODO make this more clever: won't need to printf most of the time */
433     buf_reset(&parser->buf);
434     if (name) buf_printf(&parser->buf, "%s[%zu:%s]", prop, index, name);
435     else buf_printf(&parser->buf, "%s[%zu]", prop, index);
436     strarray_push(&parser->path, buf_cstring(&parser->buf));
437     buf_reset(&parser->buf);
438 }
439 
jmap_parser_push_name(struct jmap_parser * parser,const char * prop,const char * name)440 HIDDEN void jmap_parser_push_name(struct jmap_parser *parser,
441                                   const char *prop, const char *name)
442 {
443     /* TODO make this more clever: won't need to printf most of the time */
444     buf_reset(&parser->buf);
445     buf_printf(&parser->buf, "%s{%s}", prop, name);
446     strarray_push(&parser->path, buf_cstring(&parser->buf));
447     buf_reset(&parser->buf);
448 }
449 
jmap_parser_pop(struct jmap_parser * parser)450 HIDDEN void jmap_parser_pop(struct jmap_parser *parser)
451 {
452     free(strarray_pop(&parser->path));
453 }
454 
jmap_parser_path(struct jmap_parser * parser,struct buf * buf)455 HIDDEN const char* jmap_parser_path(struct jmap_parser *parser, struct buf *buf)
456 {
457     int i;
458     buf_reset(buf);
459 
460     for (i = 0; i < parser->path.count; i++) {
461         const char *p = strarray_nth(&parser->path, i);
462         if (jmap_pointer_needsencode(p)) {
463             char *tmp = jmap_pointer_encode(p);
464             buf_appendcstr(buf, tmp);
465             free(tmp);
466         } else {
467             buf_appendcstr(buf, p);
468         }
469         if ((i + 1) < parser->path.count) {
470             buf_appendcstr(buf, "/");
471         }
472     }
473 
474     return buf_cstring(buf);
475 }
476 
jmap_parser_invalid(struct jmap_parser * parser,const char * prop)477 HIDDEN void jmap_parser_invalid(struct jmap_parser *parser, const char *prop)
478 {
479     if (prop)
480         jmap_parser_push(parser, prop);
481 
482     json_array_append_new(parser->invalid,
483             json_string(jmap_parser_path(parser, &parser->buf)));
484 
485     if (prop)
486         jmap_parser_pop(parser);
487 }
488 
jmap_server_error(int r)489 HIDDEN json_t *jmap_server_error(int r)
490 {
491     switch (r) {
492     case IMAP_CONVERSATION_GUIDLIMIT:
493         return json_pack("{s:s}", "type", "tooManyMailboxes");
494     case IMAP_QUOTA_EXCEEDED:
495         return json_pack("{s:s}", "type", "overQuota");
496     default:
497         return json_pack("{s:s, s:s}",
498                          "type", "serverFail",
499                          "description", error_message(r));
500     }
501 }
502 
jmap_encode_base64_nopad(const char * data,size_t len)503 HIDDEN char *jmap_encode_base64_nopad(const char *data, size_t len)
504 {
505     if (!len) return NULL;
506 
507     /* Encode data */
508     size_t b64len = ((len + 2) / 3) << 2;
509     char *b64 = xzmalloc(b64len + 1);
510     if (sasl_encode64(data, len, b64, b64len + 1, NULL) != SASL_OK) {
511         free(b64);
512         return NULL;
513     }
514     /* Remove padding */
515     char *end = b64 + strlen(b64) - 1;
516     while (*end == '=') {
517         *end = '\0';
518         end--;
519     }
520 
521     return b64;
522 }
523 
jmap_decode_base64_nopad(const char * b64,size_t b64len)524 HIDDEN char *jmap_decode_base64_nopad(const char *b64, size_t b64len)
525 {
526     /* Pad base64 data. */
527     size_t myb64len = b64len;
528     switch (b64len % 4) {
529         case 3:
530             myb64len += 1;
531             break;
532         case 2:
533             myb64len += 2;
534             break;
535         case 1:
536             return NULL;
537         default:
538             ; // do nothing
539     }
540     char *myb64 = xzmalloc(myb64len+1);
541     memcpy(myb64, b64, b64len);
542     switch (myb64len - b64len) {
543         case 2:
544             myb64[b64len+1] = '=';
545             // fall through
546         case 1:
547             myb64[b64len] = '=';
548             break;
549         default:
550             ; // do nothing
551     }
552     /* Decode data. */
553     size_t datalen = ((4 * myb64len / 3) + 3) & ~3;
554     char *data = xzmalloc(datalen + 1);
555     if (sasl_decode64(myb64, myb64len, data, datalen, NULL) != SASL_OK) {
556         free(data);
557         free(myb64);
558         return NULL;
559     }
560 
561     free(myb64);
562     return data;
563 }
564 
jmap_decode_to_utf8(const char * charset,int encoding,const char * data,size_t datalen,float confidence,char ** val,int * is_encoding_problem)565 EXPORTED const char *jmap_decode_to_utf8(const char *charset, int encoding,
566                                          const char *data, size_t datalen,
567                                          float confidence,
568                                          char **val,
569                                          int *is_encoding_problem)
570 {
571     charset_t cs = charset_lookupname(charset);
572     char *text = NULL;
573     *val = NULL;
574     const char *charset_id = charset_canon_name(cs);
575     assert(confidence >= 0.0 && confidence <= 1.0);
576 
577     /* Attempt fast path without allocation */
578     if (encoding == ENCODING_NONE && data[datalen] == '\0' &&
579             !strcasecmp(charset_id, "UTF-8")) {
580         struct char_counts counts = charset_count_validutf8(data, datalen);
581         if (!counts.invalid) {
582             charset_free(&cs);
583             return data;
584         }
585     }
586 
587     /* Can't use fast path. Allocate and try to detect charset. */
588     if (cs == CHARSET_UNKNOWN_CHARSET || encoding == ENCODING_UNKNOWN) {
589         syslog(LOG_INFO, "decode_to_utf8 error (%s, %s)",
590                 charset, encoding_name(encoding));
591         if (is_encoding_problem) *is_encoding_problem = 1;
592         goto done;
593     }
594     text = charset_to_utf8(data, datalen, cs, encoding);
595     if (!text) {
596         if (is_encoding_problem) *is_encoding_problem = 1;
597         goto done;
598     }
599 
600     size_t textlen = strlen(text);
601     struct char_counts counts = charset_count_validutf8(text, textlen);
602     if (is_encoding_problem)
603         *is_encoding_problem = counts.invalid || counts.replacement;
604 
605     if (!strncasecmp(charset_id, "UTF-32", 6)) {
606         /* Special-handle UTF-32. Some clients announce the wrong endianess. */
607         if (counts.invalid || counts.replacement) {
608             charset_t guess_cs = CHARSET_UNKNOWN_CHARSET;
609             if (!strcasecmp(charset_id, "UTF-32") || !strcasecmp(charset_id, "UTF-32BE"))
610                 guess_cs = charset_lookupname("UTF-32LE");
611             else
612                 guess_cs = charset_lookupname("UTF-32BE");
613             char *guess = charset_to_utf8(data, datalen, guess_cs, encoding);
614             if (guess) {
615                 struct char_counts guess_counts = charset_count_validutf8(guess, strlen(guess));
616                 if (guess_counts.valid > counts.valid) {
617                     free(text);
618                     text = guess;
619                     counts = guess_counts;
620                     textlen = strlen(text);
621                     charset_id = charset_canon_name(guess_cs);
622                 }
623             }
624             charset_free(&guess_cs);
625         }
626     }
627     else if (!charset_id || !strcasecmp("US-ASCII", charset_id)) {
628         int has_cntrl = 0;
629         size_t i;
630         for (i = 0; i < textlen; i++) {
631             if (iscntrl(text[i])) {
632                 has_cntrl = 1;
633                 break;
634             }
635         }
636         if (has_cntrl) {
637             /* Could be ISO-2022-JP */
638             charset_t guess_cs = charset_lookupname("ISO-2022-JP");
639             if (guess_cs != CHARSET_UNKNOWN_CHARSET) {
640                 char *guess = charset_to_utf8(data, datalen, guess_cs, encoding);
641                 if (guess) {
642                     struct char_counts guess_counts = charset_count_validutf8(guess, strlen(guess));
643                     if (!guess_counts.invalid && !guess_counts.replacement) {
644                         free(text);
645                         text = guess;
646                         counts = guess_counts;
647                         textlen = strlen(text);
648                         charset_id = charset_canon_name(guess_cs);
649                     }
650                     else free(guess);
651                 }
652                 charset_free(&guess_cs);
653             }
654         }
655     }
656 
657 #ifdef HAVE_LIBCHARDET
658     if (counts.invalid || counts.replacement) {
659         static Detect *d = NULL;
660         if (!d) d = detect_init();
661 
662         DetectObj *obj = detect_obj_init();
663         if (!obj) goto done;
664         detect_reset(&d);
665 
666         struct buf buf = BUF_INITIALIZER;
667         charset_decode(&buf, data, datalen, encoding);
668         buf_cstring(&buf);
669         if (detect_handledata_r(&d, buf_base(&buf), buf_len(&buf), &obj) == CHARDET_SUCCESS) {
670             charset_t guess_cs = charset_lookupname(obj->encoding);
671             if (guess_cs != CHARSET_UNKNOWN_CHARSET) {
672                 char *guess = charset_to_utf8(data, datalen, guess_cs, encoding);
673                 if (guess) {
674                     struct char_counts guess_counts =
675                         charset_count_validutf8(guess, strlen(guess));
676                     if ((guess_counts.valid > counts.valid) &&
677                         (obj->confidence >= confidence)) {
678                         free(text);
679                         text = guess;
680                         counts = guess_counts;
681                     }
682                     else {
683                         free(guess);
684                     }
685                 }
686                 charset_free(&guess_cs);
687             }
688         }
689         detect_obj_free(&obj);
690         buf_free(&buf);
691     }
692 #endif
693 
694 done:
695     charset_free(&cs);
696     *val = text;
697     return text;
698 }
699