551 lines
12 KiB
C
551 lines
12 KiB
C
/*
|
|
*
|
|
* OBEX Server
|
|
*
|
|
* Copyright (C) 2010-2011 Nokia Corporation
|
|
*
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <sys/types.h>
|
|
#include <dirent.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "obexd/src/log.h"
|
|
|
|
#include "messages.h"
|
|
|
|
#define MSG_LIST_XML "mlisting.xml"
|
|
|
|
static char *root_folder = NULL;
|
|
|
|
struct session {
|
|
char *cwd;
|
|
char *cwd_absolute;
|
|
void *request;
|
|
};
|
|
|
|
struct folder_listing_data {
|
|
struct session *session;
|
|
const char *name;
|
|
uint16_t max;
|
|
uint16_t offset;
|
|
messages_folder_listing_cb callback;
|
|
void *user_data;
|
|
};
|
|
|
|
struct message_listing_data {
|
|
struct session *session;
|
|
const char *name;
|
|
uint16_t max;
|
|
uint16_t offset;
|
|
uint8_t subject_len;
|
|
uint16_t size;
|
|
char *path;
|
|
FILE *fp;
|
|
const struct messages_filter *filter;
|
|
messages_get_messages_listing_cb callback;
|
|
void *user_data;
|
|
};
|
|
|
|
/* NOTE: Neither IrOBEX nor MAP specs says that folder listing needs to
|
|
* be sorted (in IrOBEX examples it is not). However existing implementations
|
|
* seem to follow the fig. 3-2 from MAP specification v1.0, and I've seen a
|
|
* test suite requiring folder listing to be in that order.
|
|
*/
|
|
static int folder_names_cmp(gconstpointer a, gconstpointer b,
|
|
gpointer user_data)
|
|
{
|
|
static const char *order[] = {
|
|
"inbox", "outbox", "sent", "deleted", "draft", NULL
|
|
};
|
|
struct session *session = user_data;
|
|
int ia, ib;
|
|
|
|
if (g_strcmp0(session->cwd, "telecom/msg") == 0) {
|
|
for (ia = 0; order[ia]; ia++) {
|
|
if (g_strcmp0(a, order[ia]) == 0)
|
|
break;
|
|
}
|
|
for (ib = 0; order[ib]; ib++) {
|
|
if (g_strcmp0(b, order[ib]) == 0)
|
|
break;
|
|
}
|
|
if (ia != ib)
|
|
return ia - ib;
|
|
}
|
|
|
|
return g_strcmp0(a, b);
|
|
}
|
|
|
|
static char *get_next_subdir(DIR *dp, char *path)
|
|
{
|
|
struct dirent *ep;
|
|
char *abs, *name;
|
|
|
|
for (;;) {
|
|
if ((ep = readdir(dp)) == NULL)
|
|
return NULL;
|
|
|
|
if (strcmp(ep->d_name, ".") == 0 || strcmp(ep->d_name, "..") == 0)
|
|
continue;
|
|
|
|
abs = g_build_filename(path, ep->d_name, NULL);
|
|
|
|
if (g_file_test(abs, G_FILE_TEST_IS_DIR)) {
|
|
g_free(abs);
|
|
break;
|
|
}
|
|
|
|
g_free(abs);
|
|
}
|
|
|
|
name = g_filename_to_utf8(ep->d_name, -1, NULL, NULL, NULL);
|
|
|
|
if (name == NULL) {
|
|
DBG("g_filename_to_utf8(): invalid filename");
|
|
return NULL;
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
static ssize_t get_subdirs(struct folder_listing_data *fld, GSList **list)
|
|
{
|
|
DIR *dp;
|
|
char *path, *name;
|
|
size_t n;
|
|
|
|
path = g_build_filename(fld->session->cwd_absolute, fld->name, NULL);
|
|
dp = opendir(path);
|
|
|
|
if (dp == NULL) {
|
|
int err = -errno;
|
|
|
|
DBG("opendir(): %d, %s", -err, strerror(-err));
|
|
g_free(path);
|
|
|
|
return err;
|
|
}
|
|
|
|
n = 0;
|
|
|
|
while ((name = get_next_subdir(dp, path)) != NULL) {
|
|
n++;
|
|
if (fld->max > 0)
|
|
*list = g_slist_prepend(*list, name);
|
|
}
|
|
|
|
closedir(dp);
|
|
g_free(path);
|
|
|
|
*list = g_slist_sort_with_data(*list, folder_names_cmp, fld->session);
|
|
|
|
return n;
|
|
}
|
|
|
|
static void return_folder_listing(struct folder_listing_data *fld, GSList *list)
|
|
{
|
|
struct session *session = fld->session;
|
|
GSList *cur;
|
|
uint16_t num = 0;
|
|
uint16_t offs = 0;
|
|
|
|
for (cur = list; offs < fld->offset; offs++) {
|
|
cur = cur->next;
|
|
if (cur == NULL)
|
|
break;
|
|
}
|
|
|
|
for (; cur != NULL && num < fld->max; cur = cur->next, num++)
|
|
fld->callback(session, -EAGAIN, 0, cur->data, fld->user_data);
|
|
|
|
fld->callback(session, 0, 0, NULL, fld->user_data);
|
|
}
|
|
|
|
static gboolean get_folder_listing(void *d)
|
|
{
|
|
struct folder_listing_data *fld = d;
|
|
ssize_t n;
|
|
GSList *list = NULL;
|
|
|
|
n = get_subdirs(fld, &list);
|
|
|
|
if (n < 0) {
|
|
fld->callback(fld->session, n, 0, NULL, fld->user_data);
|
|
return FALSE;
|
|
}
|
|
|
|
if (fld->max == 0) {
|
|
fld->callback(fld->session, 0, n, NULL, fld->user_data);
|
|
return FALSE;
|
|
}
|
|
|
|
return_folder_listing(fld, list);
|
|
g_slist_free_full(list, g_free);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
int messages_init(void)
|
|
{
|
|
char *tmp;
|
|
|
|
if (root_folder)
|
|
return 0;
|
|
|
|
tmp = getenv("MAP_ROOT");
|
|
if (tmp) {
|
|
root_folder = g_strdup(tmp);
|
|
return 0;
|
|
}
|
|
|
|
tmp = getenv("HOME");
|
|
if (!tmp)
|
|
return -ENOENT;
|
|
|
|
root_folder = g_build_filename(tmp, "map-messages", NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void messages_exit(void)
|
|
{
|
|
g_free(root_folder);
|
|
root_folder = NULL;
|
|
}
|
|
|
|
int messages_connect(void **s)
|
|
{
|
|
struct session *session;
|
|
|
|
session = g_new0(struct session, 1);
|
|
session->cwd = g_strdup("");
|
|
session->cwd_absolute = g_strdup(root_folder);
|
|
|
|
*s = session;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void messages_disconnect(void *s)
|
|
{
|
|
struct session *session = s;
|
|
|
|
g_free(session->cwd);
|
|
g_free(session->cwd_absolute);
|
|
g_free(session);
|
|
}
|
|
|
|
int messages_set_notification_registration(void *session,
|
|
void (*send_event)(void *session,
|
|
const struct messages_event *event, void *user_data),
|
|
void *user_data)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
int messages_set_folder(void *s, const char *name, gboolean cdup)
|
|
{
|
|
struct session *session = s;
|
|
char *newrel = NULL;
|
|
char *newabs;
|
|
char *tmp;
|
|
|
|
if (name && (strchr(name, '/') || strcmp(name, "..") == 0))
|
|
return -EBADR;
|
|
|
|
if (cdup) {
|
|
if (session->cwd[0] == 0)
|
|
return -ENOENT;
|
|
|
|
newrel = g_path_get_dirname(session->cwd);
|
|
|
|
/* We use empty string for indication of the root directory */
|
|
if (newrel[0] == '.' && newrel[1] == 0)
|
|
newrel[0] = 0;
|
|
}
|
|
|
|
tmp = newrel;
|
|
if (!cdup && (!name || name[0] == 0))
|
|
newrel = g_strdup("");
|
|
else
|
|
newrel = g_build_filename(newrel ? newrel : session->cwd, name,
|
|
NULL);
|
|
g_free(tmp);
|
|
|
|
newabs = g_build_filename(root_folder, newrel, NULL);
|
|
|
|
if (!g_file_test(newabs, G_FILE_TEST_IS_DIR)) {
|
|
g_free(newrel);
|
|
g_free(newabs);
|
|
return -ENOENT;
|
|
}
|
|
|
|
g_free(session->cwd);
|
|
session->cwd = newrel;
|
|
|
|
g_free(session->cwd_absolute);
|
|
session->cwd_absolute = newabs;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int messages_get_folder_listing(void *s, const char *name, uint16_t max,
|
|
uint16_t offset,
|
|
messages_folder_listing_cb callback,
|
|
void *user_data)
|
|
{
|
|
struct session *session = s;
|
|
struct folder_listing_data *fld;
|
|
|
|
fld = g_new0(struct folder_listing_data, 1);
|
|
fld->session = session;
|
|
fld->name = name;
|
|
fld->max = max;
|
|
fld->offset = offset;
|
|
fld->callback = callback;
|
|
fld->user_data = user_data;
|
|
|
|
session->request = fld;
|
|
|
|
g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, get_folder_listing,
|
|
fld, g_free);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void max_msg_element(GMarkupParseContext *ctxt, const char *element,
|
|
const char **names, const char **values,
|
|
gpointer user_data, GError **gerr)
|
|
{
|
|
struct message_listing_data *mld = user_data;
|
|
const char *key;
|
|
int i;
|
|
|
|
for (i = 0, key = names[i]; key; key = names[++i]) {
|
|
if (g_strcmp0(names[i], "handle") == 0) {
|
|
mld->size++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void msg_element(GMarkupParseContext *ctxt, const char *element,
|
|
const char **names, const char **values,
|
|
gpointer user_data, GError **gerr)
|
|
{
|
|
struct message_listing_data *mld = user_data;
|
|
struct messages_message *entry = NULL;
|
|
int i;
|
|
|
|
entry = g_new0(struct messages_message, 1);
|
|
if (mld->filter->parameter_mask == 0) {
|
|
entry->mask = (entry->mask | PMASK_SUBJECT \
|
|
| PMASK_DATETIME | PMASK_RECIPIENT_ADDRESSING \
|
|
| PMASK_SENDER_ADDRESSING \
|
|
| PMASK_ATTACHMENT_SIZE | PMASK_TYPE \
|
|
| PMASK_RECEPTION_STATUS);
|
|
} else
|
|
entry->mask = mld->filter->parameter_mask;
|
|
|
|
for (i = 0 ; names[i]; ++i) {
|
|
if (g_strcmp0(names[i], "handle") == 0) {
|
|
entry->handle = g_strdup(values[i]);
|
|
mld->size++;
|
|
continue;
|
|
}
|
|
if (g_strcmp0(names[i], "attachment_size") == 0) {
|
|
entry->attachment_size = g_strdup(values[i]);
|
|
continue;
|
|
}
|
|
if (g_strcmp0(names[i], "datetime") == 0) {
|
|
entry->datetime = g_strdup(values[i]);
|
|
continue;
|
|
}
|
|
if (g_strcmp0(names[i], "subject") == 0) {
|
|
entry->subject = g_strdup(values[i]);
|
|
continue;
|
|
}
|
|
if (g_strcmp0(names[i], "recipient_addressing") == 0) {
|
|
entry->recipient_addressing = g_strdup(values[i]);
|
|
continue;
|
|
}
|
|
if (g_strcmp0(names[i], "sender_addressing") == 0) {
|
|
entry->sender_addressing = g_strdup(values[i]);
|
|
continue;
|
|
}
|
|
if (g_strcmp0(names[i], "type") == 0) {
|
|
entry->type = g_strdup(values[i]);
|
|
continue;
|
|
}
|
|
if (g_strcmp0(names[i], "reception_status") == 0)
|
|
entry->reception_status = g_strdup(values[i]);
|
|
}
|
|
|
|
if (mld->size > mld->offset)
|
|
mld->callback(mld->session, -EAGAIN, mld->size, 0, entry, mld->user_data);
|
|
|
|
g_free(entry->reception_status);
|
|
g_free(entry->type);
|
|
g_free(entry->sender_addressing);
|
|
g_free(entry->subject);
|
|
g_free(entry->datetime);
|
|
g_free(entry->attachment_size);
|
|
g_free(entry->handle);
|
|
g_free(entry);
|
|
}
|
|
|
|
static const GMarkupParser msg_parser = {
|
|
msg_element,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
static const GMarkupParser max_msg_parser = {
|
|
max_msg_element,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
static gboolean get_messages_listing(void *d)
|
|
{
|
|
|
|
struct message_listing_data *mld = d;
|
|
/* 1024 is the maximum size of the line which is calculated to be more
|
|
* sufficient*/
|
|
char buffer[1024];
|
|
GMarkupParseContext *ctxt;
|
|
size_t len;
|
|
|
|
while (fgets(buffer, 1024, mld->fp)) {
|
|
len = strlen(buffer);
|
|
|
|
if (mld->max == 0) {
|
|
ctxt = g_markup_parse_context_new(&max_msg_parser, 0, mld, NULL);
|
|
g_markup_parse_context_parse(ctxt, buffer, len, NULL);
|
|
g_markup_parse_context_free(ctxt);
|
|
} else {
|
|
ctxt = g_markup_parse_context_new(&msg_parser, 0, mld, NULL);
|
|
g_markup_parse_context_parse(ctxt, buffer, len, NULL);
|
|
g_markup_parse_context_free(ctxt);
|
|
}
|
|
}
|
|
|
|
if (mld->max == 0) {
|
|
mld->callback(mld->session, 0, mld->size, 0, NULL, mld->user_data);
|
|
goto done;
|
|
}
|
|
|
|
mld->callback(mld->session, 0, mld->size, 0, NULL, mld->user_data);
|
|
|
|
done:
|
|
fclose(mld->fp);
|
|
return FALSE;
|
|
}
|
|
|
|
int messages_get_messages_listing(void *session, const char *name,
|
|
uint16_t max, uint16_t offset,
|
|
uint8_t subject_len,
|
|
const struct messages_filter *filter,
|
|
messages_get_messages_listing_cb callback,
|
|
void *user_data)
|
|
{
|
|
struct message_listing_data *mld;
|
|
struct session *s = session;
|
|
char *path;
|
|
|
|
mld = g_new0(struct message_listing_data, 1);
|
|
mld->session = s;
|
|
mld->name = name;
|
|
mld->max = max;
|
|
mld->offset = offset;
|
|
mld->subject_len = subject_len;
|
|
mld->callback = callback;
|
|
mld->filter = filter;
|
|
mld->user_data = user_data;
|
|
|
|
path = g_build_filename(s->cwd_absolute, MSG_LIST_XML, NULL);
|
|
mld->fp = fopen(path, "r");
|
|
if (mld->fp == NULL) {
|
|
g_free(path);
|
|
messages_set_folder(s, mld->name, 0);
|
|
path = g_build_filename(s->cwd_absolute, MSG_LIST_XML, NULL);
|
|
mld->fp = fopen(path, "r");
|
|
if (mld->fp == NULL) {
|
|
int err = -errno;
|
|
DBG("fopen(): %d, %s", -err, strerror(-err));
|
|
g_free(path);
|
|
return -EBADR;
|
|
}
|
|
}
|
|
|
|
|
|
g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, get_messages_listing,
|
|
mld, g_free);
|
|
g_free(path);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int messages_get_message(void *session, const char *handle,
|
|
unsigned long flags,
|
|
messages_get_message_cb callback,
|
|
void *user_data)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
int messages_update_inbox(void *session, messages_status_cb callback,
|
|
void *user_data)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
int messages_set_read(void *session, const char *handle, uint8_t value,
|
|
messages_status_cb callback, void *user_data)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
int messages_set_delete(void *session, const char *handle, uint8_t value,
|
|
messages_status_cb callback, void *user_data)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
void messages_abort(void *s)
|
|
{
|
|
struct session *session = s;
|
|
|
|
if (session->request) {
|
|
g_idle_remove_by_data(session->request);
|
|
session->request = NULL;
|
|
}
|
|
}
|