From b3cedede793ac7dd7f4ae76f303c8e72020b5363 Mon Sep 17 00:00:00 2001 From: Ken Murchison Date: Fri, 23 Jun 2023 15:01:40 -0400 Subject: [PATCH] index.c: use libicalvcard if available --- cassandane/Cassandane/Cyrus/SearchFuzzy.pm | 86 +++++++++++++++ imap/index.c | 120 +++++++++++++++++++++ 2 files changed, 206 insertions(+) diff --git a/cassandane/Cassandane/Cyrus/SearchFuzzy.pm b/cassandane/Cassandane/Cyrus/SearchFuzzy.pm index e4056415388..4ea9c052903 100644 --- a/cassandane/Cassandane/Cyrus/SearchFuzzy.pm +++ b/cassandane/Cassandane/Cyrus/SearchFuzzy.pm @@ -1234,6 +1234,92 @@ sub test_search_omit_ical $self->assert_num_equals(2, scalar @$uids); } +sub test_search_omit_vcard + :min_version_3_9 :needs_search_xapian +{ + my ($self) = @_; + + xlog $self, "Generate and index test messages."; + + $self->make_message("test", + mime_type => "multipart/related", + mime_boundary => "boundary_1", + body => "" + . "\r\n--boundary_1\r\n" + . "Content-Type: text/plain\r\n" + . "\r\n" + . "txt body" + . "\r\n--boundary_1\r\n" + . "Content-Type: text/vcard;charset=utf-8\r\n" + . "Content-Transfer-Encoding: quoted-printable\r\n" + . "\r\n" + . "BEGIN:VCARD\r\n" + . "VERSION:3.0\r\n" + . "UID:1234567890\r\n" + . "BDAY:1944-06-07\r\n" + . "N:Gump;Forrest;;Mr.\r\n" + . "FN:Forrest Gump\r\n" + . "ORG;PROP-ID=O1:Bubba Gump Shrimp Co.\r\n" + . "TITLE;PROP-ID=T1:Shrimp Man\r\n" + . "PHOTO;PROP-ID=P1;ENCODING=b;TYPE=JPEG:c29tZSBwaG90bw==\r\n" + . "foo.ADR;PROP-ID=A1:;;1501 Broadway;New York;NY;10036;USA\r\n" + . "foo.GEO:40.7571383482188;-73.98695548990568\r\n" + . "foo.TZ:-05:00\r\n" + . "EMAIL;TYPE=PREF:bgump\@example.com\r\n" + . "X-SOCIAL-PROFILE:https://example.com/\@bubba" + . "REV:2008-04-24T19:52:43Z\r\n" + . "END:VCARD\r\n" + . "\r\n--boundary_1--\r\n" + ) || die; + + $self->make_message("top", + mime_type => "text/vcard", + body => "" + . "BEGIN:VCARD\r\n" + . "VERSION:3.0\r\n" + . "UID:1234567890\r\n" + . "BDAY:1944-06-07\r\n" + . "N:Gump;Forrest;;Mr.\r\n" + . "FN:Forrest Gump\r\n" + . "ORG;PROP-ID=O1:Bubba Gump Shrimp Co.\r\n" + . "TITLE;PROP-ID=T1:Shrimp Man\r\n" + . "PHOTO;PROP-ID=P1;ENCODING=b;TYPE=JPEG:c29tZSBwaG90bw==\r\n" + . "foo.ADR;PROP-ID=A1:;;1501 Broadway;New York;NY;10036;USA\r\n" + . "foo.GEO:40.7571383482188;-73.98695548990568\r\n" + . "foo.TZ:-05:00\r\n" + . "EMAIL;TYPE=PREF:bgump\@example.com\r\n" + . "X-SOCIAL-PROFILE:https://example.com/\@bubba" + . "REV:2008-04-24T19:52:43Z\r\n" + . "END:VCARD\r\n" + ) || die; + + $self->{instance}->run_command({cyrus => 1}, 'squatter'); + + my $talk = $self->{store}->get_client(); + + my $r = $talk->select("INBOX") || die; + my $uidvalidity = $talk->get_response_code('uidvalidity'); + my $uids = $talk->search('1:*', 'NOT', 'DELETED'); + + $uids = $talk->search('fuzzy', 'text', '1944') || die; + $self->assert_num_equals(0, scalar @$uids); + + $uids = $talk->search('fuzzy', 'text', 'Forrest') || die; + $self->assert_num_equals(2, scalar @$uids); + + $uids = $talk->search('fuzzy', 'text', 'Mr.') || die; + $self->assert_num_equals(2, scalar @$uids); + + $uids = $talk->search('fuzzy', 'text', 'Shrimp') || die; + $self->assert_num_equals(2, scalar @$uids); + + $uids = $talk->search('fuzzy', 'text', 'example') || die; + $self->assert_num_equals(2, scalar @$uids); + + $uids = $talk->search('fuzzy', 'text', 'https') || die; + $self->assert_num_equals(2, scalar @$uids); +} + sub test_xapian_index_partid :min_version_3_0 :needs_search_xapian :needs_component_jmap { diff --git a/imap/index.c b/imap/index.c index ff60c6d66f9..3941b8da67d 100644 --- a/imap/index.c +++ b/imap/index.c @@ -5444,6 +5444,125 @@ static int extract_icalbuf(struct buf *raw, charset_t charset, int encoding, return r; } +#ifdef HAVE_LIBICALVCARD + +static int extract_vcardbuf(struct buf *raw, charset_t charset, int encoding, + struct getsearchtext_rock *str) +{ + vcardcomponent *vcard = NULL; + vcardproperty *prop; + int r = 0; + struct buf buf = BUF_INITIALIZER; + +syslog(LOG_INFO, "XXXXXXXX extract_vcardbuf()"); + /* Parse the message into a vcard object */ + const struct buf *vcardbuf = NULL; + if (encoding || strcasecmp(charset_canon_name(charset), "utf-8")) { + char *tmp = charset_to_utf8(buf_cstring(raw), + buf_len(raw), charset, encoding); + if (!tmp) return 0; /* could be a bogus header - ignore */ + buf_initm(&buf, tmp, strlen(tmp)); + vcardbuf = &buf; + } + else { + vcardbuf = raw; + } + +syslog(LOG_INFO, "XXXXXXXX new_from_string()"); + vcard = vcardcomponent_new_from_string(buf_cstring(vcardbuf)); + if (!vcard) { + r = IMAP_INTERNAL; + goto done; + } + + buf_reset(&buf); + +syslog(LOG_INFO, "XXXXXXXX iterate"); + // these are all the things that we think might be interesting + for (prop = vcardcomponent_get_first_property(vcard, VCARD_ANY_PROPERTY); + prop; + prop = vcardcomponent_get_next_property(vcard, VCARD_ANY_PROPERTY)) { + vcardstructuredtype *stp = NULL; + vcardstructuredtype st = { 1, { 0 } }; + const char *val; + + switch (vcardproperty_isa(prop)) { + case VCARD_X_PROPERTY: { + const char *propname = vcardproperty_get_property_name(prop); + + if (strcasecmp(propname, "x-social-profile") && + strcasecmp(propname, "x-fm-online-other")) { + break; + } + + GCC_FALLTHROUGH + } + + case VCARD_FN_PROPERTY: + case VCARD_EMAIL_PROPERTY: + case VCARD_TEL_PROPERTY: + case VCARD_URL_PROPERTY: + case VCARD_IMPP_PROPERTY: + case VCARD_SOCIALPROFILE_PROPERTY: + case VCARD_NICKNAME_PROPERTY: + case VCARD_NOTE_PROPERTY: + val = vcardproperty_get_value_as_string(prop); + if (val && val[0]) { + if (buf_len(&buf)) buf_putc(&buf, ' '); + buf_appendcstr(&buf, val); + } + break; + + case VCARD_ORG_PROPERTY: { + unsigned f; + size_t v; + + st.field[0] = vcardproperty_get_org(prop); + stp = &st; + + GCC_FALLTHROUGH + + case VCARD_N_PROPERTY: + case VCARD_ADR_PROPERTY: + if (!stp) stp = vcardproperty_get_adr(prop); + + for (f = 0; f < stp->num_fields; f++) { + vcardstrarray *vals = stp->field[f]; + + for (v = 0; vals && v < vcardstrarray_size(vals); v++) { + val = vcardstrarray_element_at(vals, v); + if (val && val[0]) { + if (buf_len(&buf)) buf_putc(&buf, ' '); + buf_appendcstr(&buf, val); + } + } + } + break; + } + + default: + break; + } + } + + if (buf_len(&buf)) { + charset_t utf8 = charset_lookupname("utf-8"); + str->receiver->begin_part(str->receiver, SEARCH_PART_BODY); + charset_extract(extract_cb, str, &buf, utf8, 0, "vcard", + str->charset_flags); + str->receiver->end_part(str->receiver, SEARCH_PART_BODY); + charset_free(&utf8); + buf_reset(&buf); + } + +done: + if (vcard) vcardcomponent_free(vcard); + buf_free(&buf); + return r; +} + +#else /* !HAVE_LIBICALVCARD */ + static void _add_vcard_singlval(struct vparse_card *card, const char *key, struct buf *buf) { struct vparse_entry *entry; @@ -5532,6 +5651,7 @@ static int extract_vcardbuf(struct buf *raw, charset_t charset, int encoding, return r; } +#endif /* HAVE_LIBICALVCARD */ #endif /* USE_HTTPD */