index-fetch-section.c revision 1d5740f09a1f09f528acb7bc42048e2a0d2852ef
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce/* Copyright (C) 2002 Timo Sirainen */
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce#include "lib.h"
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce#include "temp-string.h"
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce#include "iobuffer.h"
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce#include "rfc822-tokenize.h"
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce#include "message-send.h"
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce#include "index-storage.h"
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce#include "index-fetch.h"
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce#include <ctype.h>
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce#include <unistd.h>
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo SorceImapCacheField index_fetch_body_get_cache(const char *section)
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce{
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce if (*section >= '0' && *section <= '9')
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce return IMAP_CACHE_MESSAGE_PART | IMAP_CACHE_MESSAGE_OPEN;
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce if (*section == '\0' || strcasecmp(section, "TEXT") == 0) {
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce /* no IMAP_CACHE_MESSAGE_BODY_SIZE, so that we don't
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce uselessly check it when we want to read partial data */
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce return IMAP_CACHE_MESSAGE_OPEN;
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce }
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce if (strncasecmp(section, "HEADER", 6) == 0 ||
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce strcasecmp(section, "MIME") == 0)
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce return IMAP_CACHE_MESSAGE_HDR_SIZE | IMAP_CACHE_MESSAGE_OPEN;
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce /* error */
edaadf8de0c86a2cfff2d29215775d42919476f3Pavel Březina return 0;
edaadf8de0c86a2cfff2d29215775d42919476f3Pavel Březina}
4ebab24f65b54720a6672898b76185462015ababPavel Březina
75d66aea7accc842e68c88f085f9053112b20eccPavel Březina/* fetch BODY[] or BODY[TEXT] */
c1058e96679c7ed1372825bf5226ce7d28a8e6ffPavel Březinastatic int fetch_body(MailIndexRecord *rec, MailFetchBodyData *sect,
dee7a89098b698e756f63e4041734d7322ad8b1ePavel Březina FetchContext *ctx, int fetch_header)
ab967283b710dfa05d11ee5b30c7ac916486ceecSimo Sorce{
c6872e79e8496fd075e20aec0343ade99cca725cSimo Sorce MessageSize size;
c6872e79e8496fd075e20aec0343ade99cca725cSimo Sorce IOBuffer *inbuf;
c6872e79e8496fd075e20aec0343ade99cca725cSimo Sorce const char *str;
2745b0156f12df7a7eb93d57716233243658e4d9Jakub Hrozek
22a21e910fd216ec1468fe769dcc29f1621a52a4Ondrej Kos if (!imap_msgcache_get_rfc822_partial(ctx->cache, rec->uid,
ab967283b710dfa05d11ee5b30c7ac916486ceecSimo Sorce sect->skip, sect->max_size,
b9c563c29243291f40489bb0dcbf3946fca72d58Jakub Hrozek fetch_header, &size, &inbuf)) {
ab967283b710dfa05d11ee5b30c7ac916486ceecSimo Sorce i_error("Couldn't get BODY[] for UID %u (index %s)",
ab967283b710dfa05d11ee5b30c7ac916486ceecSimo Sorce rec->uid, ctx->index->filepath);
c6872e79e8496fd075e20aec0343ade99cca725cSimo Sorce return FALSE;
233a3c6c48972b177e60d6ef4cecfacd3cf31659Simo Sorce }
c6872e79e8496fd075e20aec0343ade99cca725cSimo Sorce
c6872e79e8496fd075e20aec0343ade99cca725cSimo Sorce str = t_strdup_printf("{%lu}\r\n", (unsigned long) size.virtual_size);
233a3c6c48972b177e60d6ef4cecfacd3cf31659Simo Sorce (void)io_buffer_send(ctx->outbuf, str, strlen(str));
233a3c6c48972b177e60d6ef4cecfacd3cf31659Simo Sorce
7c69221077c780e62f6c536e78675f2dc1c131bcMichal Zidek (void)message_send(ctx->outbuf, inbuf, &size, 0, sect->max_size);
7c69221077c780e62f6c536e78675f2dc1c131bcMichal Zidek return TRUE;
7c69221077c780e62f6c536e78675f2dc1c131bcMichal Zidek}
aa7202c8ae677becd6c91d6a27a607fe0f3995eePavel Březina
f9961e5f82e0ef474d6492371bfdf9e74e208a99Pavel Březinastatic char *const *get_fields_array(const char *fields)
f9961e5f82e0ef474d6492371bfdf9e74e208a99Pavel Březina{
7a4e3e29196e3abc1746714fcf93624edae89f93Lukas Slebodnik char **field_list, **field;
9cb46bc62f22e0104f1b41a423b014c281ef5fc2Jakub Hrozek
9cb46bc62f22e0104f1b41a423b014c281ef5fc2Jakub Hrozek while (*fields == ' ')
9cb46bc62f22e0104f1b41a423b014c281ef5fc2Jakub Hrozek fields++;
7caf7ed4f2eae1ec1c0717b4ee6ce78bdacd5926Jakub Hrozek if (*fields == '(')
dcc6877aa2e2dd63a9dc9c411a9c58feaeb36b9aStephen Gallagher fields++;
bc30ce9b7d588a17e58012e699986f0d6898b791Pavel Březina
2a96981a0ac781d01e5bba473409ed2bdf4cd4e0Jakub Hrozek field_list = (char **) t_strsplit(fields, " )");
e81deec535d11912b87954c81a1edd768c1386c9Jakub Hrozek
4dd38025efda88f123eac672f87d3cda12f050c8Jakub Hrozek /* array ends at ")" element */
4dd38025efda88f123eac672f87d3cda12f050c8Jakub Hrozek for (field = field_list; *field != NULL; field++) {
0161a3c5637a0c0092bf54c436bb3d6508d7df26Jakub Hrozek if (strcasecmp(*field, ")") == 0)
0161a3c5637a0c0092bf54c436bb3d6508d7df26Jakub Hrozek *field = NULL;
7a4e3e29196e3abc1746714fcf93624edae89f93Lukas Slebodnik }
60cab26b12df9a2153823972cde0c38ca86e01b9Yassir Elley
1319e71fd1680ca4864afe0b1aca2b8c8e4a1ee4Stef Walter return field_list;
0c1d65998907930678da2d091789446f2c344d5dJakub Hrozek}
a2ea3f5d9ef9f17efbb61e942c2bc6cff7d1ebf2Jakub Hrozek
f3a25949de81f80c136bb073e4a8f504b080c20cJakub Hrozekstatic int header_match(char *const *fields, const char *name,
78a08d30b5fbf6e1e3b589e0cf67022e0c1faa33Michal Židek unsigned int size)
8394eddba54b5d3e3fda868145e3751247bdbdb2Michal Zidek{
5a5c5cdeb92f4012fc75fd717bfea06598f68f12Pavel Reichl const char *field, *name_start, *name_end;
7a4e3e29196e3abc1746714fcf93624edae89f93Lukas Slebodnik
1243e093fd31c5660adf1bb3dd477d6935a755beJakub Hrozek i_assert(size > 0);
1243e093fd31c5660adf1bb3dd477d6935a755beJakub Hrozek
7a4e3e29196e3abc1746714fcf93624edae89f93Lukas Slebodnik name_start = name;
979e8d8d6ed444007eeff6be5269e8dc5d2bdf68Pavel Reichl name_end = name + size;
05d935cc9d04f03522d0bb44598d22d99b085926Jakub Hrozek
64ea4127f463798410a2c20e0261c6b15f60257fJakub Hrozek for (; *fields != NULL; fields++) {
64ea4127f463798410a2c20e0261c6b15f60257fJakub Hrozek field = *fields;
a8d887323f83984679a7d9b827a70146656bb7b2Sumit Bose if (*field == '\0')
b42bf6c0c01db08208fb81d8295a2909d307284aPavel Reichl continue;
9118a539a5d59f669f551114f880fe91d6bb8741Jakub Hrozek
b5825c74b6bf7a99ae2172392dbecb51179013a6Jakub Hrozek for (name = name_start; name != name_end; name++) {
19e44537c28f6d5f011cd7ac885c74c1e892605fSimo Sorce /* field has been uppercased long time ago while
5f7cd30c865046a7ea69944f7e07c85b4c43465aSumit Bose parsing FETCH command */
c30b7a1931211fdcae0564551a7625cc4f6dee9fJakub Hrozek if (i_toupper(*name) != *field)
e732d23f3ec986a463d757781a334040e03d1f59Jakub Hrozek break;
e732d23f3ec986a463d757781a334040e03d1f59Jakub Hrozek
dd285415d7a8d8376207960cfa3e977524c3b98cJakub Hrozek field++;
dd285415d7a8d8376207960cfa3e977524c3b98cJakub Hrozek if (*field == '\0') {
beec1ee5799570f34a51ea57674c7291c15f7022Jakub Hrozek if (name+1 == name_end)
fcbcfa69f9291936f01f24b5fcb5a7672dca46f3Jakub Hrozek return TRUE;
4714118890e51b365fbce543d0a042b4b59b2b25Michal Zidek break;
efc65e78fa4e01e6cecc8690a9899af61213be62Fabiano Fidêncio }
41cd6072648bb7a9e14e56ed38004a2947f67657Jakub Hrozek }
65a38b8c9cabde6c46cc0e9868f54cb9bb10afbfFabiano Fidêncio }
7171a7584dda534dde5409f3e7f4657e845ece15Fabiano Fidêncio
d4757440418c7b73bbecec7e40baf6dfe8cc9460Sumit Bose return FALSE;
d4757440418c7b73bbecec7e40baf6dfe8cc9460Sumit Bose}
b9c563c29243291f40489bb0dcbf3946fca72d58Jakub Hrozek
b9c563c29243291f40489bb0dcbf3946fca72d58Jakub Hrozekstatic int header_match_not(char *const *fields, const char *name,
b9c563c29243291f40489bb0dcbf3946fca72d58Jakub Hrozek unsigned int size)
b9c563c29243291f40489bb0dcbf3946fca72d58Jakub Hrozek{
cac0db2f8004ae88b9263dc3888a11a2d3d3d114Jakub Hrozek return !header_match(fields, name, size);
cac0db2f8004ae88b9263dc3888a11a2d3d3d114Jakub Hrozek}
300b9e9217ee1ed8d845ed2370c5ccf5c87afb36Pavel Březina
300b9e9217ee1ed8d845ed2370c5ccf5c87afb36Pavel Březinastatic int header_match_mime(char *const *fields __attr_unused__,
300b9e9217ee1ed8d845ed2370c5ccf5c87afb36Pavel Březina const char *name, unsigned int size)
300b9e9217ee1ed8d845ed2370c5ccf5c87afb36Pavel Březina{
c0f9f5a0f6d71a1596ee3cef549b4b02295313c3Jakub Hrozek if (size > 8 && strncasecmp(name, "Content-", 8) == 0)
0a0b34f5fbe8f4a8c533a7d65f0f2961ee264054Jakub Hrozek return TRUE;
2af80640f18966d65cf82106059ce3c060df93bfamitkuma
ccd349f0274217e1f0cc118e3a6045e2235ce420Fabiano Fidêncio if (size == 12 && strncasecmp(name, "Mime-Version", 13) == 0)
7650ded4ffa87fcf7ce5adf00920fecf89cffcf5Michal Zidek return TRUE;
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce return FALSE;
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce}
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorcetypedef struct {
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce char *dest;
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce char *const *fields;
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce int (*match_func) (char *const *, const char *, unsigned int);
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce} FetchHeaderFieldContext;
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorcestatic void fetch_header_field(MessagePart *part __attr_unused__,
8bcabb97d988d1602882a1f036aac2eaf5e09234Simo Sorce const char *name, unsigned int name_len,
const char *value __attr_unused__,
unsigned int value_len __attr_unused__,
void *context)
{
FetchHeaderFieldContext *ctx = context;
const char *name_start, *name_end, *cr;
unsigned int len;
/* see if we want this field */
if (!ctx->match_func(ctx->fields, name, name_len))
return;
/* add the field, inserting CRs when needed. FIXME: is this too
kludgy? we assume name continues with ": value\n".. */
name_start = name;
name_end = name + name_len;
cr = NULL;
for (name_start = name; name != name_end; name++) {
if (*name == '\r')
cr = name;
else if (*name == '\n' && cr != name-1) {
/* missing CR */
len = (unsigned int) (name-name_start);
memcpy(ctx->dest, name_start, len);
ctx->dest[len++] = '\r';
ctx->dest[len++] = '\n';
ctx->dest += len;
name_start = name+1;
}
}
if (name_start != name_end) {
/* last linebreak was \r\n */
len = (unsigned int) (name_end-name_start);
memcpy(ctx->dest, name_start, len);
ctx->dest += len;
}
}
/* Store headers into dest, returns number of bytes written. */
static unsigned int
fetch_header_fields(IOBuffer *inbuf, char *dest, char *const *fields,
int (*match_func) (char *const *, const char *,
unsigned int))
{
FetchHeaderFieldContext ctx;
ctx.dest = dest;
ctx.fields = fields;
ctx.match_func = match_func;
message_parse_header(NULL, inbuf, NULL, fetch_header_field, &ctx);
return (unsigned int) (ctx.dest - dest);
}
/* fetch wanted headers from given data */
static void fetch_header_from(IOBuffer *inbuf, MessageSize *size,
const char *section, MailFetchBodyData *sect,
FetchContext *ctx)
{
const char *str;
char *dest;
unsigned int len;
/* HEADER, MIME, HEADER.FIELDS (list), HEADER.FIELDS.NOT (list) */
if (strcasecmp(section, "HEADER") == 0) {
/* all headers */
str = t_strdup_printf("{%lu}\r\n",
(unsigned long) size->virtual_size);
(void)io_buffer_send(ctx->outbuf, str, strlen(str));
(void)message_send(ctx->outbuf, inbuf, size,
sect->skip, sect->max_size);
return;
}
/* partial headers - copy the wanted fields into temporary memory.
Insert missing CRs on the way. */
t_push();
dest = t_malloc(size->virtual_size);
if (strncasecmp(section, "HEADER.FIELDS ", 14) == 0) {
len = fetch_header_fields(inbuf, dest,
get_fields_array(section + 14),
header_match);
} else if (strncasecmp(section, "HEADER.FIELDS.NOT ", 18) == 0) {
len = fetch_header_fields(inbuf, dest,
get_fields_array(section + 18),
header_match_not);
} else if (strcasecmp(section, "MIME") == 0) {
/* Mime-Version + Content-* fields */
len = fetch_header_fields(inbuf, dest, NULL, header_match_mime);
} else {
/* error */
len = 0;
}
i_assert(len <= size->virtual_size);
if ((off_t)len <= sect->skip)
len = 0;
else {
dest += sect->skip;
len -= sect->skip;
if (sect->max_size >= 0 && (off_t)len > sect->max_size)
len = sect->max_size;
}
str = t_strdup_printf("{%u}\r\n", len);
io_buffer_send(ctx->outbuf, str, strlen(str));
if (len > 0) io_buffer_send(ctx->outbuf, dest, len);
t_pop();
}
/* fetch BODY[HEADER...] */
static int fetch_header(MailIndexRecord *rec, MailFetchBodyData *sect,
FetchContext *ctx)
{
MessageSize hdr_size;
IOBuffer *inbuf;
if (!imap_msgcache_get_rfc822(ctx->cache, rec->uid,
&hdr_size, NULL, &inbuf))
return FALSE;
fetch_header_from(inbuf, &hdr_size, sect->section, sect, ctx);
return TRUE;
}
/* Find MessagePart for section (eg. 1.3.4) */
static MessagePart *part_find(MailIndexRecord *rec, MailFetchBodyData *sect,
FetchContext *ctx, const char **section)
{
MessagePart *part;
const char *path;
int num;
part = imap_msgcache_get_parts(ctx->cache, rec->uid);
path = sect->section;
while (*path >= '0' && *path <= '9' && part != NULL) {
/* get part number */
num = 0;
while (*path != '\0' && *path != '.') {
if (*path < '0' || *path > '9')
return NULL;
num = num*10 + *path - '0';
path++;
}
if (*path == '.')
path++;
if (part->multipart) {
/* find the part */
part = part->children;
for (; num > 1 && part != NULL; num--)
part = part->next;
} else {
/* only 1 allowed with non-multipart messages */
if (num != 1)
return NULL;
}
}
*section = path;
return part;
}
/* fetch BODY[1.2] or BODY[1.2.TEXT] */
static int fetch_part_body(MessagePart *part, unsigned int uid,
MailFetchBodyData *sect, FetchContext *ctx)
{
IOBuffer *inbuf;
const char *str;
off_t skip_pos;
if (!imap_msgcache_get_data(ctx->cache, uid, &inbuf))
return FALSE;
/* jump to beginning of wanted data */
skip_pos = (off_t) (part->pos.physical_pos +
part->header_size.physical_size);
io_buffer_skip(inbuf, skip_pos);
str = t_strdup_printf("{%lu}\r\n",
(unsigned long) part->body_size.virtual_size);
(void)io_buffer_send(ctx->outbuf, str, strlen(str));
/* FIXME: potential performance problem with big messages:
FETCH BODY[1]<100000..1024>, hopefully no clients do this */
(void)message_send(ctx->outbuf, inbuf, &part->body_size,
sect->skip, sect->max_size);
return TRUE;
}
/* fetch BODY[1.2.MIME|HEADER...] */
static int fetch_part_header(MessagePart *part, unsigned int uid,
const char *section, MailFetchBodyData *sect,
FetchContext *ctx)
{
IOBuffer *inbuf;
if (!imap_msgcache_get_data(ctx->cache, uid, &inbuf))
return FALSE;
io_buffer_skip(inbuf, part->pos.physical_pos);
fetch_header_from(inbuf, &part->header_size, section, sect, ctx);
return TRUE;
}
static int fetch_part(MailIndexRecord *rec, MailFetchBodyData *sect,
FetchContext *ctx)
{
MessagePart *part;
const char *section;
part = part_find(rec, sect, ctx, &section);
if (part == NULL)
return FALSE;
if (*section == '\0' || strcasecmp(section, "TEXT") == 0)
return fetch_part_body(part, rec->uid, sect, ctx);
if (strncasecmp(section, "HEADER", 6) == 0)
return fetch_part_header(part, rec->uid, section, sect, ctx);
if (strcasecmp(section, "MIME") == 0)
return fetch_part_header(part, rec->uid, section, sect, ctx);
return FALSE;
}
void index_fetch_body_section(MailIndexRecord *rec,
unsigned int seq __attr_unused__,
MailFetchBodyData *sect, FetchContext *ctx)
{
const char *str;
int fetch_ok;
str = !sect->skip_set ?
t_strdup_printf(" BODY[%s] ", sect->section) :
t_strdup_printf(" BODY[%s]<%lu> ", sect->section,
(unsigned long) sect->skip);
(void)io_buffer_send(ctx->outbuf, str, strlen(str));
if (*sect->section == '\0') {
fetch_ok = fetch_body(rec, sect, ctx, TRUE);
} else if (strcasecmp(sect->section, "TEXT") == 0) {
fetch_ok = fetch_body(rec, sect, ctx, FALSE);
} else if (strncasecmp(sect->section, "HEADER", 6) == 0) {
fetch_ok = fetch_header(rec, sect, ctx);
} else if (*sect->section >= '0' && *sect->section <= '9') {
fetch_ok = fetch_part(rec, sect, ctx);
} else {
fetch_ok = FALSE;
}
if (!fetch_ok) {
/* error */
(void)io_buffer_send(ctx->outbuf, "{0}\r\n", 5);
}
}