From 7fdf73edecb06a5e3b9cefcd2efe97cc4e888301 Mon Sep 17 00:00:00 2001 From: Slava Monich Date: Tue, 25 Feb 2014 17:38:06 +0200 Subject: [PATCH] Preliminary support for sending MMS messages --- .gitignore | 1 + mms-engine/Makefile | 2 +- mms-engine/mms-engine.pro | 2 + mms-engine/mms_engine.c | 88 ++++ mms-handler-dbus/src/mms_handler_dbus.c | 52 ++- mms-lib/Makefile | 11 +- mms-lib/include/mms_dispatcher.h | 17 + mms-lib/include/mms_handler.h | 36 ++ mms-lib/include/mms_lib_log.h | 2 + mms-lib/include/mms_lib_types.h | 9 + mms-lib/mms-lib.pro | 4 + mms-lib/src/mms_attachment.c | 301 ++++++++++++++ mms-lib/src/mms_attachment.h | 75 ++++ mms-lib/src/mms_codec.c | 16 + mms-lib/src/mms_codec.h | 1 + mms-lib/src/mms_dispatcher.c | 35 ++ mms-lib/src/mms_file_util.c | 60 +++ mms-lib/src/mms_file_util.h | 7 + mms-lib/src/mms_handler.c | 32 ++ mms-lib/src/mms_task.h | 22 + mms-lib/src/mms_task_encode.c | 507 ++++++++++++++++++++++++ mms-lib/src/mms_task_http.c | 4 +- mms-lib/src/mms_task_send.c | 145 +++++++ mms-lib/test/common/Makefile | 2 +- mms-send/Makefile | 129 ++++++ mms-send/mms-send.c | 155 ++++++++ 26 files changed, 1705 insertions(+), 10 deletions(-) create mode 100644 mms-lib/src/mms_attachment.c create mode 100644 mms-lib/src/mms_attachment.h create mode 100644 mms-lib/src/mms_task_encode.c create mode 100644 mms-lib/src/mms_task_send.c create mode 100644 mms-send/Makefile create mode 100644 mms-send/mms-send.c diff --git a/.gitignore b/.gitignore index 783f6ce..f2c2724 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ mms-handler-dbus/test/mms_handler_dbus_client/build mms-dump/build mms-dump/mms_dump.ncb mms-dump/mms_dump.opt +mms-send/build diff --git a/mms-engine/Makefile b/mms-engine/Makefile index bbaa48a..84d9a96 100644 --- a/mms-engine/Makefile +++ b/mms-engine/Makefile @@ -90,7 +90,7 @@ endif DEBUG_CFLAGS = $(DEBUG_FLAGS) $(DEBUG_DEFS) $(CFLAGS) RELEASE_CFLAGS = $(RELEASE_FLAGS) $(RELEASE_DEFS) $(CFLAGS) -LIBS = $(shell pkg-config --libs $(LIB_PKGS)) +LIBS = $(shell pkg-config --libs $(LIB_PKGS)) -lmagic DEBUG_LIBS = \ $(MMS_OFONO_DEBUG_LIB) \ $(MMS_HANDLER_DEBUG_LIB) \ diff --git a/mms-engine/mms-engine.pro b/mms-engine/mms-engine.pro index 2553824..a3c8121 100644 --- a/mms-engine/mms-engine.pro +++ b/mms-engine/mms-engine.pro @@ -35,6 +35,8 @@ CONFIG(debug, debug|release) { LIBS += $$MMS_LIB_DIR/build/release/libmms-lib.a } +LIBS += -lmagic + MMS_ENGINE_DBUS_XML = $$DBUS_INTERFACE_DIR/org.nemomobile.MmsEngine.xml MMS_ENGINE_DBUS_H = org.nemomobile.MmsEngine.h org_nemomobile_mmsengine_h.input = MMS_ENGINE_DBUS_XML diff --git a/mms-engine/mms_engine.c b/mms-engine/mms_engine.c index a212a34..11b3d77 100644 --- a/mms-engine/mms_engine.c +++ b/mms-engine/mms_engine.c @@ -39,6 +39,7 @@ struct mms_engine { gboolean stopped; gboolean keep_running; guint start_timeout_id; + gulong send_message_id; gulong push_signal_id; gulong push_notify_signal_id; gulong receive_signal_id; @@ -129,6 +130,89 @@ mms_engine_handle_test( } #endif /* ENABLE_TEST */ +/* org.nemomobile.MmsEngine.sendMessage */ +static +gboolean +mms_engine_handle_send_message( + OrgNemomobileMmsEngine* proxy, + GDBusMethodInvocation* call, + int database_id, + const char* imsi_to, + const char* const* to, + const char* const* cc, + const char* const* bcc, + const char* subject, + guint flags, + GVariant* attachments, + MMSEngine* engine) +{ + if (to && *to) { + unsigned int i; + char* to_list = g_strjoinv(",", (char**)to); + char* cc_list = NULL; + char* bcc_list = NULL; + char* id = NULL; + char* imsi; + MMSAttachmentInfo* parts; + GArray* info = g_array_sized_new(FALSE, FALSE, sizeof(*parts), 0); + GError* error = NULL; + + /* Extract attachment info */ + char* fn = NULL; + char* ct = NULL; + char* cid = NULL; + GVariantIter* iter = NULL; + g_variant_get(attachments, "a(sss)", &iter); + while (g_variant_iter_loop(iter, "(sss)", &fn, &ct, &cid)) { + MMSAttachmentInfo part; + part.file_name = g_strdup(fn); + part.content_type = g_strdup(ct); + part.content_id = g_strdup(cid); + g_array_append_vals(info, &part, 1); + } + + /* Convert address lists into comma-separated strings + * expected by mms_dispatcher_send_message and mms_codec */ + if (cc && *cc) cc_list = g_strjoinv(",", (char**)cc); + if (bcc && *bcc) bcc_list = g_strjoinv(",", (char**)bcc); + if (database_id > 0) id = g_strdup_printf("%u", database_id); + + /* Queue the message */ + parts = (void*)info->data; + imsi = mms_dispatcher_send_message(engine->dispatcher, id, + imsi_to, to_list, cc_list, bcc_list, subject, flags, parts, + info->len, &error); + if (imsi) { + if (mms_dispatcher_start(engine->dispatcher)) { + mms_engine_start_timeout_cancel(engine); + } + org_nemomobile_mms_engine_complete_send_message(proxy, call, imsi); + g_free(imsi); + } else { + g_dbus_method_invocation_return_error(call, G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, "%s", MMS_ERRMSG(error)); + g_error_free(error); + } + + for (i=0; ilen; i++) { + g_free((void*)parts[i].file_name); + g_free((void*)parts[i].content_type); + g_free((void*)parts[i].content_id); + } + + g_free(to_list); + g_free(cc_list); + g_free(bcc_list); + g_free(id); + g_array_unref(info); + g_variant_iter_free(iter); + } else { + g_dbus_method_invocation_return_error(call, G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, "Missing recipient"); + } + return TRUE; +} + /* org.nemomobile.MmsEngine.receiveMessage */ static gboolean @@ -343,6 +427,9 @@ mms_engine_new( mms->log_modules = log_modules; mms->log_count = log_count; mms->proxy = org_nemomobile_mms_engine_skeleton_new(); + mms->send_message_id = + g_signal_connect(mms->proxy, "handle-send-message", + G_CALLBACK(mms_engine_handle_send_message), mms); mms->push_signal_id = g_signal_connect(mms->proxy, "handle-push", G_CALLBACK(mms_engine_handle_push), mms); @@ -486,6 +573,7 @@ mms_engine_dispose( #ifdef ENABLE_TEST g_signal_handler_disconnect(e->proxy, e->test_signal_id); #endif + g_signal_handler_disconnect(e->proxy, e->send_message_id); g_signal_handler_disconnect(e->proxy, e->push_signal_id); g_signal_handler_disconnect(e->proxy, e->push_notify_signal_id); g_signal_handler_disconnect(e->proxy, e->receive_signal_id); diff --git a/mms-handler-dbus/src/mms_handler_dbus.c b/mms-handler-dbus/src/mms_handler_dbus.c index 0d574ee..c663f82 100644 --- a/mms-handler-dbus/src/mms_handler_dbus.c +++ b/mms-handler-dbus/src/mms_handler_dbus.c @@ -131,7 +131,7 @@ mms_handler_dbus_message_received( return ok; } -/* Updates message state in the database */ +/* Updates message receive state in the database */ static gboolean mms_handler_dbus_message_receive_state_changed( @@ -155,6 +155,53 @@ mms_handler_dbus_message_receive_state_changed( return ok; } +/* Updates message send state in the database */ +static +gboolean +mms_handler_dbus_message_send_state_changed( + MMSHandler* handler, + const char* id, + MMS_SEND_STATE state) +{ + gboolean ok = FALSE; + OrgNemomobileMmsHandler* proxy = mms_handler_dbus_connect(handler); + MMS_ASSERT(id && id[0]); + if (id && id[0] && proxy) { + GError* error = NULL; + if (org_nemomobile_mms_handler_call_message_send_state_changed_sync( + proxy, id, state, NULL, &error)) { + ok = TRUE; + } else { + MMS_ERR("%s", MMS_ERRMSG(error)); + g_error_free(error); + } + } + return ok; +} + +/* Message has been sent */ +gboolean +mms_handler_dbus_message_sent( + MMSHandler* handler, + const char* id, + const char* msgid) +{ + gboolean ok = FALSE; + OrgNemomobileMmsHandler* proxy = mms_handler_dbus_connect(handler); + MMS_ASSERT(id && id[0]); + if (id && id[0] && msgid && msgid[0] && proxy) { + GError* error = NULL; + if (org_nemomobile_mms_handler_call_message_sent_sync(proxy, + id, msgid, NULL, &error)) { + ok = TRUE; + } else { + MMS_ERR("%s", MMS_ERRMSG(error)); + g_error_free(error); + } + } + return ok; +} + static void mms_handler_dbus_dispose( @@ -177,6 +224,9 @@ mms_handler_dbus_class_init( klass->fn_message_received = mms_handler_dbus_message_received; klass->fn_message_receive_state_changed = mms_handler_dbus_message_receive_state_changed; + klass->fn_message_send_state_changed = + mms_handler_dbus_message_send_state_changed; + klass->fn_message_sent = mms_handler_dbus_message_sent; G_OBJECT_CLASS(klass)->dispose = mms_handler_dbus_dispose; } diff --git a/mms-lib/Makefile b/mms-lib/Makefile index b186509..9abad76 100644 --- a/mms-lib/Makefile +++ b/mms-lib/Makefile @@ -15,11 +15,12 @@ all: debug release # Sources # -SRC = mms_codec.c mms_connection.c mms_connman.c mms_dispatcher.c \ - mms_error.c mms_handler.c mms_lib_util.c mms_file_util.c mms_log.c \ - mms_message.c mms_task.c mms_task_ack.c mms_task_decode.c \ - mms_task_http.c mms_task_notification.c mms_task_notifyresp.c \ - mms_task_publish.c mms_task_read.c mms_task_retrieve.c mms_util.c +SRC = mms_attachment.c mms_codec.c mms_connection.c mms_connman.c \ + mms_dispatcher.c mms_error.c mms_handler.c mms_lib_util.c mms_file_util.c \ + mms_log.c mms_message.c mms_task.c mms_task_ack.c mms_task_decode.c \ + mms_task_encode.c mms_task_http.c mms_task_notification.c \ + mms_task_notifyresp.c mms_task_publish.c mms_task_read.c \ + mms_task_retrieve.c mms_task_send.c mms_util.c # # Directories diff --git a/mms-lib/include/mms_dispatcher.h b/mms-lib/include/mms_dispatcher.h index 0f1d38d..e36e837 100644 --- a/mms-lib/include/mms_dispatcher.h +++ b/mms-lib/include/mms_dispatcher.h @@ -79,6 +79,23 @@ mms_dispatcher_send_read_report( MMSReadStatus status, GError** error); +char* +mms_dispatcher_send_message( + MMSDispatcher* dispatcher, + const char* id, + const char* imsi, + const char* to, + const char* cc, + const char* bcc, + const char* subject, + unsigned int flags, + const MMSAttachmentInfo* parts, + unsigned int nparts, + GError** error); + +#define MMS_SEND_FLAG_REQUEST_DELIVERY_REPORT (0x01) +#define MMS_SEND_FLAG_REQUEST_READ_REPORT (0x02) + void mms_dispatcher_cancel( MMSDispatcher* dispatcher, diff --git a/mms-lib/include/mms_handler.h b/mms-lib/include/mms_handler.h index 14452a1..11cebe2 100644 --- a/mms-lib/include/mms_handler.h +++ b/mms-lib/include/mms_handler.h @@ -28,6 +28,18 @@ typedef enum _mmm_receive_state { MMS_RECEIVE_STATE_DECODING_ERROR } MMS_RECEIVE_STATE; +/* Send state */ +typedef enum _mmm_send_state { + MMS_SEND_STATE_INVALID = -1, + MMS_SEND_STATE_ENCODING, + MMS_SEND_STATE_TOO_BIG, + MMS_SEND_STATE_SENDING, + MMS_SEND_STATE_DEFERRED, + MMS_SEND_STATE_NO_SPACE, + MMS_SEND_STATE_SEND_ERROR, + MMS_SEND_STATE_REFUSED +} MMS_SEND_STATE; + /* Class */ typedef struct mms_handler_class { GObjectClass parent; @@ -53,6 +65,18 @@ typedef struct mms_handler_class { MMSHandler* handler, /* Handler instance */ MMSMessage* msg); /* Decoded message */ + /* Sets the send state */ + gboolean (*fn_message_send_state_changed)( + MMSHandler* handler, /* Handler instance */ + const char* id, /* Handler record id */ + MMS_SEND_STATE state); /* Receive state */ + + /* Message has been sent */ + gboolean (*fn_message_sent)( + MMSHandler* handler, /* Handler instance */ + const char* id, /* Handler record id */ + const char* msgid); /* Message id assigned by operator */ + } MMSHandlerClass; GType mms_handler_get_type(void); @@ -86,6 +110,18 @@ mms_handler_message_received( MMSHandler* handler, /* Handler instance */ MMSMessage* msg); /* Decoded message */ +gboolean +mms_handler_message_send_state_changed( + MMSHandler* handler, /* Handler instance */ + const char* id, /* Handler record id */ + MMS_SEND_STATE state); /* Receive state */ + +gboolean +mms_handler_message_sent( + MMSHandler* handler, /* Handler instance */ + const char* id, /* Handler record id */ + const char* msgid); /* Message id assigned by operator */ + #endif /* JOLLA_MMS_HANDLER_H */ /* diff --git a/mms-lib/include/mms_lib_log.h b/mms-lib/include/mms_lib_log.h index 292fa57..00c45bc 100644 --- a/mms-lib/include/mms_lib_log.h +++ b/mms-lib/include/mms_lib_log.h @@ -21,9 +21,11 @@ log(mms_dispatcher_log)\ log(mms_handler_log)\ log(mms_message_log)\ + log(mms_attachment_log)\ log(mms_task_log)\ log(mms_task_http_log)\ log(mms_task_decode_log)\ + log(mms_task_encode_log)\ log(mms_task_notification_log)\ log(mms_task_retrieve_log)\ log(mms_task_publish_log)\ diff --git a/mms-lib/include/mms_lib_types.h b/mms-lib/include/mms_lib_types.h index 2b6598d..d84f0e0 100644 --- a/mms-lib/include/mms_lib_types.h +++ b/mms-lib/include/mms_lib_types.h @@ -49,6 +49,13 @@ typedef struct mms_config { gboolean send_dr; /* Allow sending delivery reports */ } MMSConfig; +/* Attachment information */ +typedef struct mms_attachment_info { + const char* file_name; /* Full path name */ + const char* content_type; /* Content type */ + const char* content_id; /* Content id */ +} MMSAttachmentInfo; + /* Types */ typedef GObject MMSHandler; typedef GObject MMSConnMan; @@ -57,9 +64,11 @@ typedef struct mms_dispatcher MMSDispatcher; typedef struct mms_connection MMSConnection; typedef struct mms_message MMSPdu; typedef struct _mms_message MMSMessage; +typedef struct _mms_attachment MMSAttachment; /* MMS content type */ #define MMS_CONTENT_TYPE "application/vnd.wap.mms-message" +#define SMIL_CONTENT_TYPE "application/smil" /* MMS read status */ typedef enum mms_read_status { diff --git a/mms-lib/mms-lib.pro b/mms-lib/mms-lib.pro index 6a2e362..94113fb 100644 --- a/mms-lib/mms-lib.pro +++ b/mms-lib/mms-lib.pro @@ -14,6 +14,7 @@ CONFIG(debug, debug|release) { } SOURCES += \ + src/mms_attachment.c \ src/mms_codec.c \ src/mms_connection.c \ src/mms_connman.c \ @@ -27,15 +28,18 @@ SOURCES += \ src/mms_task.c \ src/mms_task_ack.c \ src/mms_task_decode.c \ + src/mms_task_encode.c \ src/mms_task_http.c \ src/mms_task_notification.c \ src/mms_task_notifyresp.c \ src/mms_task_publish.c \ src/mms_task_read.c \ src/mms_task_retrieve.c \ + src/mms_task_send.c \ src/mms_util.c HEADERS += \ + src/mms_attachment.h \ src/mms_codec.h \ src/mms_error.h \ src/mms_file_util.h \ diff --git a/mms-lib/src/mms_attachment.c b/mms-lib/src/mms_attachment.c new file mode 100644 index 0000000..745d692 --- /dev/null +++ b/mms-lib/src/mms_attachment.c @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#include "mms_attachment.h" +#include "mms_file_util.h" +#include "mms_codec.h" + +#include + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_attachment_log +#include "mms_lib_log.h" +#include "mms_error.h" +MMS_LOG_MODULE_DEFINE("mms-attachment"); + +#define MMS_ATTACHMENT_DEFAULT_TYPE "application/octet-stream" + +G_DEFINE_TYPE(MMSAttachment, mms_attachment, G_TYPE_OBJECT); + +#define MMS_ATTACHMENT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), MMS_TYPE_ATTACHMENT, MMSAttachment)) +#define MMS_ATTACHMENT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), MMS_TYPE_ATTACHMENT, MMSAttachmentClass)) + +#define REGION_TEXT "Text" +#define REGION_MEDIA "Media" + +#define MEDIA_TEXT "text" +#define MEDIA_IMAGE "img" +#define MEDIA_VIDEO "video" +#define MEDIA_AUDIO "audio" +#define MEDIA_OTHER "ref" + +static +void +mms_attachment_finalize( + GObject* object) +{ + MMSAttachment* at = MMS_ATTACHMENT(object); + g_mapped_file_unref(at->map); + if (!at->config->keep_temp_files && + !(at->flags & MMS_ATTACHMENT_DONT_DELETE_FILES)) { + char* dir = g_path_get_dirname(at->file_name); + remove(at->file_name); + rmdir(dir); + g_free(dir); + } + g_free(at->file_name); + g_free(at->content_type); + g_free(at->content_location); + g_free(at->content_id); + G_OBJECT_CLASS(mms_attachment_parent_class)->finalize(object); +} + +static +void +mms_attachment_class_init( + MMSAttachmentClass* klass) +{ + G_OBJECT_CLASS(klass)->finalize = mms_attachment_finalize; +} + +static +void +mms_attachment_init( + MMSAttachment* attachment) +{ + MMS_VERBOSE_("%p", attachment); +} + +static +char* +mms_attachment_get_path( + const char* file, + GError** error) +{ + char* path = g_malloc(PATH_MAX); + if (realpath(file, path)) { + if (g_file_test(path, G_FILE_TEST_IS_REGULAR)) { + char* fname = g_strdup(path); + g_free(path); + return fname; + } else { + MMS_ERROR(error, MMS_LIB_ERROR_IO, "%s not found", file); + } + } else { + MMS_ERROR(error, MMS_LIB_ERROR_IO, "%s: %s\n", file, strerror(errno)); + } + return NULL; +} + +gboolean +mms_attachment_write_smil( + FILE* f, + MMSAttachment** ats, + int n, + GError** error) +{ + if (fputs( + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n", f) >= 0) { + int i; + for (i=0; iflags & MMS_ATTACHMENT_SMIL)); + if (g_str_has_prefix(at->content_type, "text/")) { + elem = "text"; + region = REGION_TEXT; + } else { + region = REGION_MEDIA; + if (g_str_has_prefix(at->content_type, "image/")) { + elem = "img"; + } else if (g_str_has_prefix(at->content_type, "video/")) { + elem = "video"; + } else if (g_str_has_prefix(at->content_type, "audio/")) { + elem = "audio"; + } else { + elem = "ref"; + } + } + if (fprintf(f, " <%s src=\"%s\" region=\"%s\"/>\n", elem, + at->content_location, region) < 0) { + break; + } + } + if (i == n && fputs(" \n \n\n", f) >= 0) { + return TRUE; + } + } + MMS_ERROR(error, MMS_LIB_ERROR_IO, "Error writing SMIL: %s", + strerror(errno)); + return FALSE; +} + +MMSAttachment* +mms_attachment_new_smil( + const MMSConfig* config, + const char* path, + MMSAttachment** ats, + int n, + GError** error) +{ + MMSAttachment* smil = NULL; + int fd = open(path, O_CREAT|O_RDWR|O_TRUNC, MMS_FILE_PERM); + if (fd >= 0) { + FILE* f = fdopen(fd, "w"); + if (f) { + gboolean ok = mms_attachment_write_smil(f, ats, n, error); + fclose(f); + if (ok) { + MMSAttachmentInfo ai; + ai.file_name = path; + ai.content_type = SMIL_CONTENT_TYPE "; charset=utf-8"; + ai.content_id = NULL; + smil = mms_attachment_new(config, &ai, error); + MMS_ASSERT(smil && (smil->flags & MMS_ATTACHMENT_SMIL)); + } + } else { + MMS_ERROR(error, MMS_LIB_ERROR_IO, + "Failed to open file %s: %s", path, strerror(errno)); + close(fd); + } + } else { + MMS_ERROR(error, MMS_LIB_ERROR_IO, + "Failed to create file %s: %s", path, strerror(errno)); + } + return smil; +} + +MMSAttachment* +mms_attachment_new( + const MMSConfig* config, + const MMSAttachmentInfo* info, + GError** error) +{ + char* path = mms_attachment_get_path(info->file_name, error); + if (path) { + GMappedFile* map = g_mapped_file_new(path, FALSE, error); + if (map) { + MMSAttachment* at = g_object_new(MMS_TYPE_ATTACHMENT, NULL); + at->config = config; + at->file_name = path; + at->map = map; + + if (info->content_type) { + char** ct = mms_parse_http_content_type(info->content_type); + if (ct) { + at->content_type = mms_unparse_http_content_type(ct); + if (!strcmp(ct[0], SMIL_CONTENT_TYPE)) { + at->flags |= MMS_ATTACHMENT_SMIL; + } + g_strfreev(ct); + } + } + + if (!at->content_type) { + /* Use magic to determine mime type */ + const char* default_charset = "utf-8"; + const char* content_type = NULL; + const char* charset = NULL; + const char* ct[4]; + int n; + + magic_t magic = magic_open(MAGIC_MIME_TYPE); + if (magic) { + if (magic_load(magic, NULL) == 0) { + content_type = magic_file(magic, path); + } + } + + /* Magic detects SMIL as text/html */ + if ((!content_type || + g_str_has_prefix(content_type, "text/")) && + mms_file_is_smil(path)) { + content_type = SMIL_CONTENT_TYPE; + } + + if (!content_type) { + MMS_WARN("No mime type for %s", path); + content_type = MMS_ATTACHMENT_DEFAULT_TYPE; + } + + if (!strcmp(content_type, SMIL_CONTENT_TYPE)) { + at->flags |= MMS_ATTACHMENT_SMIL; + charset = default_charset; + } else if (g_str_has_prefix(content_type, "text/")) { + charset = default_charset; + } + + n = 0; + ct[n++] = content_type; + if (charset) { + ct[n++] = "charset"; + ct[n++] = charset; + } + ct[n++] = NULL; + at->content_type = mms_unparse_http_content_type((char**)ct); + + if (magic) magic_close(magic); + } + + at->content_location = g_path_get_basename(path); + at->content_id = (info->content_id && info->content_id[0]) ? + g_strdup(info->content_id) : + g_strdup(at->content_location); + + MMS_DEBUG("%s: %s", path, at->content_type); + return at; + } + g_free(path); + } + return NULL; +} + +MMSAttachment* +mms_attachment_ref( + MMSAttachment* at) +{ + if (at) g_object_ref(MMS_ATTACHMENT(at)); + return at; +} + +void +mms_attachment_unref( + MMSAttachment* at) +{ + if (at) g_object_unref(MMS_ATTACHMENT(at)); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_attachment.h b/mms-lib/src/mms_attachment.h new file mode 100644 index 0000000..ddbb0ef --- /dev/null +++ b/mms-lib/src/mms_attachment.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#ifndef JOLLA_MMS_ATTACHMENT_H +#define JOLLA_MMS_ATTACHMENT_H + +#include "mms_lib_types.h" + +/* Attachment object */ +struct _mms_attachment { + GObject parent; /* Parent object */ + const MMSConfig* config; /* Immutable configuration */ + char* file_name; /* Full path name */ + char* content_type; /* Content type */ + char* content_id; /* Content id */ + char* content_location; /* Content location */ + GMappedFile* map; /* Mapped attachment file */ + unsigned int flags; /* Flags: */ + +#define MMS_ATTACHMENT_SMIL (0x01) +#define MMS_ATTACHMENT_DONT_DELETE_FILES (0x02) + +}; + +typedef struct mms_attachment_class { + GObjectClass parent; +} MMSAttachmentClass; + +GType mms_attachment_get_type(void); +#define MMS_TYPE_ATTACHMENT (mms_attachment_get_type()) +#define MMS_ATTACHMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + MMS_TYPE_ATTACHMENT, MMSAttachmentClass)) + +MMSAttachment* +mms_attachment_new( + const MMSConfig* config, + const MMSAttachmentInfo* info, + GError** error); + +MMSAttachment* +mms_attachment_new_smil( + const MMSConfig* config, + const char* path, + MMSAttachment** attachments, + int count, + GError** error); + +MMSAttachment* +mms_attachment_ref( + MMSAttachment* attachment); + +void +mms_attachment_unref( + MMSAttachment* attachment); + +#endif /* JOLLA_MMS_ATTACHMENT_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_codec.c b/mms-lib/src/mms_codec.c index 9d28765..ba434c0 100644 --- a/mms-lib/src/mms_codec.c +++ b/mms-lib/src/mms_codec.c @@ -1375,6 +1375,12 @@ static gboolean decode_send_req(struct wsp_header_iter *iter, &out->version, MMS_HEADER_TO, HEADER_FLAG_ALLOW_MULTI, &out->sr.to, + MMS_HEADER_CC, + HEADER_FLAG_ALLOW_MULTI, &out->sr.cc, + MMS_HEADER_BCC, + HEADER_FLAG_ALLOW_MULTI, &out->sr.bcc, + MMS_HEADER_DELIVERY_REPORT, 0, &out->sr.dr, + MMS_HEADER_READ_REPORT, 0, &out->sr.rr, MMS_HEADER_INVALID) == FALSE) return FALSE; @@ -1724,6 +1730,9 @@ static gboolean encode_text(struct file_buffer *fb, char **text = user; unsigned int len; + if (!*text) + return TRUE; + len = strlen(*text) + 1; if ((*text)[0] & 0x80) len++; @@ -1776,6 +1785,9 @@ static gboolean encode_quoted_string(struct file_buffer *fb, char **text = user; unsigned int len; + if (!*text) + return TRUE; + len = strlen(*text) + 1; ptr = fb_request_field(fb, header, len + 3); @@ -1798,6 +1810,9 @@ static gboolean encode_text_array_element(struct file_buffer *fb, char **tos; int i; + if (!*text) + return TRUE; + tos = g_strsplit(*text, ",", 0); for (i = 0; tos[i] != NULL; i++) { @@ -2229,6 +2244,7 @@ static gboolean mms_encode_send_req(struct mms_message *msg, MMS_HEADER_BCC, &msg->sr.bcc, MMS_HEADER_SUBJECT, &msg->sr.subject, MMS_HEADER_DELIVERY_REPORT, &msg->sr.dr, + MMS_HEADER_READ_REPORT, &msg->sr.rr, MMS_HEADER_CONTENT_TYPE, &msg->sr.content_type, MMS_HEADER_INVALID) == FALSE) return FALSE; diff --git a/mms-lib/src/mms_codec.h b/mms-lib/src/mms_codec.h index 4614a17..0417a21 100644 --- a/mms-lib/src/mms_codec.h +++ b/mms-lib/src/mms_codec.h @@ -129,6 +129,7 @@ struct mms_send_req { time_t date; char *content_type; gboolean dr; + gboolean rr; }; struct mms_send_conf { diff --git a/mms-lib/src/mms_dispatcher.c b/mms-lib/src/mms_dispatcher.c index ad2b286..78fd982 100644 --- a/mms-lib/src/mms_dispatcher.c +++ b/mms-lib/src/mms_dispatcher.c @@ -486,6 +486,41 @@ mms_dispatcher_send_read_report( id, imsi, message_id, to, status, error)); } +/** + * Sends MMS message + */ +char* +mms_dispatcher_send_message( + MMSDispatcher* disp, + const char* id, + const char* imsi, + const char* to, + const char* cc, + const char* bcc, + const char* subject, + unsigned int flags, + const MMSAttachmentInfo* parts, + unsigned int nparts, + GError** error) +{ + char* default_imsi = NULL; + if (!imsi || !imsi[0]) { + /* No IMSI specified - try the default one */ + imsi = default_imsi = mms_connman_default_imsi(disp->cm); + } + if (imsi) { + if (mms_dispatcher_queue_and_unref_task(disp, + mms_task_encode_new(disp->config, disp->handler, id, imsi, + to, cc, bcc, subject, flags, parts, nparts, error))) { + return default_imsi ? default_imsi : g_strdup(imsi); + } + } else { + MMS_ERROR(error, MMS_LIB_ERROR_NOSIM, + "No IMSI is provided and none is available"); + } + return NULL; +} + /** * Cancels al the activity associated with the specified message */ diff --git a/mms-lib/src/mms_file_util.c b/mms-lib/src/mms_file_util.c index 4f21762..a906672 100644 --- a/mms-lib/src/mms_file_util.c +++ b/mms-lib/src/mms_file_util.c @@ -16,6 +16,66 @@ #include "mms_log.h" #include "mms_error.h" +/** + * Callbacks for mms_file_is_smil + */ +static +void +mms_smil_parse_start( + GMarkupParseContext* parser, + const char* element_name, + const char **attribute_names, + const char **attribute_values, + gpointer userdata, + GError** error) +{ + static const GMarkupParser skip = { NULL, NULL, NULL, NULL, NULL }; + if (!strcmp(element_name, "smil")) { + gboolean* smil_found = userdata; + *smil_found = TRUE; + } + g_markup_parse_context_push(parser, &skip, NULL); +} + +static +void +mms_smil_parse_end( + GMarkupParseContext* parser, + const gchar* element_name, + gpointer userdata, + GError** error) +{ + g_markup_parse_context_pop(parser); +} + +/** + * Checks if this ia a SMIL file, i.e. a parsable XML with as a + * root tag. + */ +gboolean +mms_file_is_smil( + const char* file) +{ + static const GMarkupParser toplevel = { mms_smil_parse_start, + mms_smil_parse_end, NULL, NULL, NULL }; + gboolean smil = FALSE; + GMappedFile* map = g_mapped_file_new(file, FALSE, NULL); + if (map) { + gboolean smil_found = FALSE; + GMarkupParseContext* parser = g_markup_parse_context_new(&toplevel, + G_MARKUP_TREAT_CDATA_AS_TEXT, &smil_found, NULL); + if (g_markup_parse_context_parse(parser, + g_mapped_file_get_contents(map), + g_mapped_file_get_length(map), NULL)) { + g_markup_parse_context_end_parse(parser, NULL); + smil = smil_found; + } + g_mapped_file_unref(map); + g_markup_parse_context_free(parser); + } + return smil; +} + /** * Removes both the file and the directory containing it, if it's empty. */ diff --git a/mms-lib/src/mms_file_util.h b/mms-lib/src/mms_file_util.h index c8eb9a2..4a8c5f8 100644 --- a/mms-lib/src/mms_file_util.h +++ b/mms-lib/src/mms_file_util.h @@ -25,6 +25,7 @@ #define MMS_ATTIC_DIR "attic" #define MMS_MESSAGE_DIR "msg" #define MMS_PARTS_DIR "parts" +#define MMS_ENCODE_DIR "encode" #define MMS_NOTIFICATION_IND_FILE "m-notification.ind" #define MMS_NOTIFYRESP_IND_FILE "m-notifyresp.ind" @@ -33,8 +34,14 @@ #define MMS_DELIVERY_IND_FILE "m-delivery.ind" #define MMS_READ_REC_IND_FILE "m-read-rec.ind" #define MMS_READ_ORIG_IND_FILE "m-read-orig.ind" +#define MMS_SEND_REQ_FILE "m-send.req" +#define MMS_SEND_CONF_FILE "m-send.conf" #define MMS_UNRECOGNIZED_PUSH_FILE "push.pdu" +gboolean +mms_file_is_smil( + const char* file); + void mms_remove_file_and_dir( const char* file); diff --git a/mms-lib/src/mms_handler.c b/mms-lib/src/mms_handler.c index 526acdb..0fd0af3 100644 --- a/mms-lib/src/mms_handler.c +++ b/mms-lib/src/mms_handler.c @@ -125,6 +125,38 @@ mms_handler_message_received( return FALSE; } +gboolean +mms_handler_message_send_state_changed( + MMSHandler* h, + const char* id, + MMS_SEND_STATE state) +{ + if (h) { + MMSHandlerClass* klass = MMS_HANDLER_GET_CLASS(h); + if (klass->fn_message_send_state_changed) { + return klass->fn_message_send_state_changed(h, id, state); + } + MMS_ERR("mms_handler_message_send_state_changed not implemented"); + } + return FALSE; +} + +gboolean +mms_handler_message_sent( + MMSHandler* h, + const char* id, + const char* msgid) +{ + if (h) { + MMSHandlerClass* klass = MMS_HANDLER_GET_CLASS(h); + if (klass->fn_message_sent) { + return klass->fn_message_sent(h, id, msgid); + } + MMS_ERR("mms_handler_message_sent not implemented"); + } + return FALSE; +} + /* * Local Variables: * mode: C diff --git a/mms-lib/src/mms_task.h b/mms-lib/src/mms_task.h index a897af8..ca66baf 100644 --- a/mms-lib/src/mms_task.h +++ b/mms-lib/src/mms_task.h @@ -209,6 +209,28 @@ mms_task_publish_new( MMSHandler* handler, MMSMessage* msg); +MMSTask* +mms_task_encode_new( + const MMSConfig* config, + MMSHandler* handler, + const char* id, + const char* imsi, + const char* to, + const char* cc, + const char* bcc, + const char* subject, + int flags, + const MMSAttachmentInfo* parts, + int nparts, + GError** error); + +MMSTask* +mms_task_send_new( + const MMSConfig* config, + MMSHandler* handler, + const char* id, + const char* imsi); + #endif /* JOLLA_MMS_TASK_H */ /* diff --git a/mms-lib/src/mms_task_encode.c b/mms-lib/src/mms_task_encode.c new file mode 100644 index 0000000..1fa4da3 --- /dev/null +++ b/mms-lib/src/mms_task_encode.c @@ -0,0 +1,507 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#include "mms_task.h" +#include "mms_attachment.h" +#include "mms_dispatcher.h" +#include "mms_handler.h" +#include "mms_file_util.h" +#include "mms_util.h" +#include "mms_codec.h" + +#include + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_task_encode_log +#include "mms_lib_log.h" +#include "mms_error.h" +MMS_LOG_MODULE_DEFINE("mms-task-encode"); + +/* Class definition */ +typedef MMSTaskClass MMSTaskEncodeClass; +typedef struct mms_encode_job MMSEncodeJob; +typedef struct mms_task_encode { + MMSTask task; + char* to; + char* cc; + char* bcc; + char* subject; + int flags; + MMSAttachment** parts; + int nparts; + MMSEncodeJob* active_job; +} MMSTaskEncode; + +G_DEFINE_TYPE(MMSTaskEncode, mms_task_encode, MMS_TYPE_TASK); +#define MMS_TYPE_TASK_ENCODE (mms_task_encode_get_type()) +#define MMS_TASK_ENCODE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + MMS_TYPE_TASK_ENCODE, MMSTaskEncode)) + +/* Encoding job (runs on its own thread) */ + +typedef enum mms_encode_state { + MMS_ENCODE_STATE_NONE, + MMS_ENCODE_STATE_RUNNING, + MMS_ENCODE_STATE_ERROR, + MMS_ENCODE_STATE_DONE +} MMS_ENCODE_STATE; + +typedef struct mms_encode_job { + gint ref_count; /* Reference count */ + MMSTaskEncode* enc; /* Associated task */ + GCancellable* cancellable; /* Can be used to cancel the job */ + GMainContext* context; /* Pointer to the main contex */ + char* path; /* Path to the encoded file */ + MMS_ENCODE_STATE state; /* Job state */ +} MMSEncodeJob; + +static +void +mms_task_encode_job_done( + MMSTaskEncode* enc, + MMSEncodeJob* job); + +static +void +mms_encode_job_run( + MMSEncodeJob* job) +{ + MMSTaskEncode* enc = job->enc; + MMS_ENCODE_STATE final_state = MMS_ENCODE_STATE_ERROR; + char* dir; + int fd; + + job->state = MMS_ENCODE_STATE_RUNNING; + + MMS_ASSERT(!job->path); + dir = mms_task_dir(&enc->task); + fd = mms_create_file(dir, MMS_SEND_REQ_FILE, &job->path, NULL); + if (fd >= 0) { + int i; + gboolean ok; + char* start; + const int flags = enc->flags; + MMSPdu* mms = g_new0(MMSPdu, 1); + MMSAttachment* smil = enc->parts[0]; + + const char* ct[6]; + ct[0] = "application/vnd.wap.multipart.related"; + ct[1] = "start"; + ct[2] = (start = g_strconcat("<", smil->content_id, ">", NULL)); + ct[3] = "type"; + ct[4] = SMIL_CONTENT_TYPE; + ct[5] = NULL; + MMS_ASSERT(smil->flags & MMS_ATTACHMENT_SMIL); + + mms->type = MMS_MESSAGE_TYPE_SEND_REQ; + mms->version = MMS_VERSION; + mms->transaction_id = g_strdup(enc->task.id); + mms->sr.to = g_strdup(enc->to); + mms->sr.cc = g_strdup(enc->cc); + mms->sr.bcc = g_strdup(enc->bcc); + mms->sr.subject = g_strdup(enc->subject); + mms->sr.dr = ((flags & MMS_SEND_FLAG_REQUEST_DELIVERY_REPORT) != 0); + mms->sr.rr = ((flags & MMS_SEND_FLAG_REQUEST_READ_REPORT) != 0); + mms->sr.content_type = mms_unparse_http_content_type((char**)ct); + for (i=0; inparts; i++) { + MMSAttachment* part = enc->parts[i]; + struct mms_attachment* at = g_new0(struct mms_attachment, 1); + at->content_type = g_strdup(part->content_type); + at->data = (void*)g_mapped_file_get_contents(part->map); + at->length = g_mapped_file_get_length(part->map); + at->content_id = g_strdup(part->content_id); + at->content_location = g_strdup(part->content_location); + mms->attachments = g_slist_append(mms->attachments, at); + } + + ok = mms_message_encode(mms, fd); + mms_message_free(mms); + g_free(start); + close(fd); + + if (ok) { + MMS_DEBUG("Created %s", job->path); + final_state = MMS_ENCODE_STATE_DONE; + } else { + MMS_ERR("Failed to encode message"); + unlink(job->path); + g_free(job->path); + job->path = NULL; + } + } + g_free(dir); + + job->state = final_state; +} + +static +MMSEncodeJob* +mms_encode_job_ref( + MMSEncodeJob* job) +{ + if (job) { + MMS_ASSERT(job->ref_count > 0); + g_atomic_int_inc(&job->ref_count); + } + return job; +} + +static +void +mms_encode_job_unref( + MMSEncodeJob* job) +{ + if (job) { + MMS_ASSERT(job->ref_count > 0); + if (g_atomic_int_dec_and_test(&job->ref_count)) { + mms_task_unref(&job->enc->task); + g_object_unref(job->cancellable); + g_main_context_unref(job->context); + g_free(job->path); + g_free(job); + } + } +} + +static +MMSEncodeJob* +mms_encode_job_new( + MMSTaskEncode* enc) +{ + MMSEncodeJob* job = g_new0(MMSEncodeJob, 1); + mms_task_ref(&enc->task); + job->ref_count = 1; + job->enc = enc; + job->cancellable = g_cancellable_new(); + job->context = g_main_context_ref(g_main_context_default()); + return job; +} + +static +gboolean +mms_encode_job_done( + gpointer data) +{ + MMSEncodeJob* job = data; + mms_task_encode_job_done(job->enc, job); + mms_encode_job_unref(job); + return FALSE; +} + +static +gpointer +mms_encode_job_thread( + gpointer data) +{ + MMSEncodeJob* job = data; + mms_encode_job_run(job); + g_main_context_invoke(job->context, mms_encode_job_done, job); + /* Reference will be released by mms_encode_job_done */ + return NULL; +} + +/* Encoding task */ + +static +void +mms_task_encode_job_done( + MMSTaskEncode* enc, + MMSEncodeJob* job) +{ + if (enc->active_job == job) { + MMSTask* task = &enc->task; + MMS_VERBOSE_("Encoding completion state %d", job->state); + enc->active_job = NULL; + if (job->state == MMS_ENCODE_STATE_DONE) { + mms_task_queue_and_unref(task->delegate, mms_task_send_new( + task->config, task->handler, task->id, task->imsi)); + } else { + mms_handler_message_send_state_changed(task->handler, task->id, + MMS_SEND_STATE_SEND_ERROR); + } + mms_task_set_state(&enc->task, MMS_TASK_STATE_DONE); + mms_encode_job_unref(job); + } else { + MMS_VERBOSE_("Ignoring stale job completion"); + } +} + +static +void +mms_task_encode_run( + MMSTask* task) +{ + MMSTaskEncode* enc = MMS_TASK_ENCODE(task); + MMSEncodeJob* job = mms_encode_job_new(enc); + GError* error = NULL; + GThread* thread; + /* Add one extra reference. mms_encode_job_done() will release it */ + mms_encode_job_ref(job); + thread = g_thread_try_new(task->name, mms_encode_job_thread, job, &error); + if (thread) { + g_thread_unref(thread); + mms_handler_message_send_state_changed(task->handler, task->id, + MMS_SEND_STATE_ENCODING); + mms_task_set_state(task, MMS_TASK_STATE_WORKING); + MMS_ASSERT(!enc->active_job); + enc->active_job = job; + } else { + MMS_ERR("%s", MMS_ERRMSG(error)); + g_error_free(error); + mms_encode_job_unref(job); + mms_encode_job_unref(job); + mms_handler_message_send_state_changed(task->handler, task->id, + mms_task_retry(task) ? MMS_SEND_STATE_DEFERRED : + MMS_SEND_STATE_SEND_ERROR); + } +} + +static +void +mms_task_encode_finalize( + GObject* object) +{ + MMSTaskEncode* enc = MMS_TASK_ENCODE(object); + if (enc->parts) { + int i; + for (i=0; inparts; i++) mms_attachment_unref(enc->parts[i]); + g_free(enc->parts); + } + g_free(enc->to); + g_free(enc->cc); + g_free(enc->bcc); + g_free(enc->subject); + G_OBJECT_CLASS(mms_task_encode_parent_class)->finalize(object); +} + +static +char* +mms_task_encode_generate_unique_path( + const char* dir, + const char* file) +{ + /* Most likely the very first check would succeed */ + char* path = g_strconcat(dir, "/", file, NULL); + if (g_file_test(path, G_FILE_TEST_IS_REGULAR)) { + int i; + char* tmpfile = NULL; + for (i=0; i<100; i++) { + char* newfile = g_strconcat("_", file, NULL); + g_free(tmpfile); + g_free(path); + path = g_strconcat(dir, "/", newfile, NULL); + file = tmpfile = newfile; + if (!g_file_test(path, G_FILE_TEST_IS_REGULAR)) break; + } + } + return path; +} + +static +GPtrArray* +mms_task_encode_prepare_attachments( + const MMSConfig* config, + const char* dir, + const MMSAttachmentInfo* parts, + int nparts, + GError** error) +{ + int i; + int smil_index = -1; + GPtrArray* array = g_ptr_array_sized_new(nparts); + for (i=0; iflags & MMS_ATTACHMENT_SMIL)) { + smil_index = i; + } + g_ptr_array_add(array, attachment); + } + } else if (error && *error) { + MMS_ERR("%s", MMS_ERRMSG(*error)); + } + g_object_unref(src); + g_object_unref(dest); + g_free(path); + if (!attachment) break; + } + + if (i == nparts) { + /* Generate SMIL if necessary */ + if (smil_index < 0) { + char* path = mms_task_encode_generate_unique_path(dir, "smil"); + MMSAttachment* smil = mms_attachment_new_smil(config, path, + (MMSAttachment**)array->pdata, array->len, error); + g_free(path); + if (smil) { + smil_index = array->len; + g_ptr_array_add(array, smil); + } + } + /* Make sure that we SMIL is the first attachment */ + if (smil_index > 0) { + MMSAttachment* smil = array->pdata[smil_index]; + memmove(array->pdata + 1, array->pdata, sizeof(void*)*smil_index); + smil_index = 0; + array->pdata[smil_index] = smil; + } + } + + if (smil_index >= 0) { + return array; + } else { + for (i=0; ipdata[i]); + } + g_ptr_array_free(array, TRUE); + return NULL; + } +} + +static +char* +mms_task_encode_prepare_address( + const char* s) +{ + if (s && s[0]) { + char* str = g_strstrip(g_strdup(s)); + const char* type = g_strrstr(str, MMS_ADDRESS_TYPE_SUFFIX); + if (type) { + return str; + } else { + char* adr = g_strconcat(str, MMS_ADDRESS_TYPE_SUFFIX_PHONE, NULL); + g_free(str); + return adr; + } + } + return NULL; +} + +static +char* +mms_task_encode_prepare_address_list( + const char* s) +{ + if (s && s[0]) { + if (strchr(s, ',')) { + int i; + char** part = g_strsplit(s, ",", 0); + GString* buf = g_string_sized_new(strlen(s)); + for (i=0; part[i]; i++) { + char* addr = mms_task_encode_prepare_address(part[i]); + if (addr) { + if (buf->len > 0) g_string_append_c(buf, ','); + g_string_append(buf, addr); + g_free(addr); + } + } + g_strfreev(part); + return g_string_free(buf, FALSE); + } else { + return mms_task_encode_prepare_address(s); + } + } + return NULL; +} + +static +void +mms_task_encode_class_init( + MMSTaskEncodeClass* klass) +{ + klass->fn_run = mms_task_encode_run; + G_OBJECT_CLASS(klass)->finalize = mms_task_encode_finalize; +} + +static +void +mms_task_encode_init( + MMSTaskEncode* enc) +{ +} + +/* Create MMS encode task */ +MMSTask* +mms_task_encode_new( + const MMSConfig* config, + MMSHandler* handler, + const char* id, + const char* imsi, + const char* to, + const char* cc, + const char* bcc, + const char* subject, + int flags, + const MMSAttachmentInfo* parts, + int nparts, + GError** error) +{ + MMS_ASSERT(to && to[0]); + if (to && to[0]) { + int err; + char* dir; + MMSTaskEncode* enc = mms_task_alloc(MMS_TYPE_TASK_ENCODE, + config, handler, "Encode", id, imsi); + MMSTask* task = &enc->task; + + mms_task_make_id(task); + dir = mms_task_file(task, MMS_ENCODE_DIR); + err = g_mkdir_with_parents(dir, MMS_DIR_PERM); + if (!err || errno == EEXIST) { + GPtrArray* array = mms_task_encode_prepare_attachments(config, + dir, parts, nparts, error); + if (array) { + enc->nparts = array->len; + enc->parts = (MMSAttachment**)g_ptr_array_free(array, FALSE); + enc->to = mms_task_encode_prepare_address_list(to); + enc->cc = mms_task_encode_prepare_address_list(cc); + enc->bcc = mms_task_encode_prepare_address_list(bcc); + enc->subject = g_strdup(subject); + enc->flags = flags; + + MMS_ASSERT(!error || !*error); + g_free(dir); + return &enc->task; + } + } else { + MMS_ERROR(error, MMS_LIB_ERROR_IO, + "Failed to create directory %s: %s", dir, strerror(errno)); + } + g_free(dir); + mms_task_unref(task); + } else { + MMS_ERROR(error, MMS_LIB_ERROR_ARGS, "Missing To: address"); + } + MMS_ASSERT(!error || *error); + return NULL; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_task_http.c b/mms-lib/src/mms_task_http.c index e229ab6..697ad1d 100644 --- a/mms-lib/src/mms_task_http.c +++ b/mms-lib/src/mms_task_http.c @@ -42,7 +42,7 @@ typedef enum _mms_http_state { MMS_HTTP_DONE /* HTTP transaction has been finished */ } MMS_HTTP_STATE; -#define MMS_HTTP_MAX_CHUNK (2048) +#define MMS_HTTP_MAX_CHUNK (4046) /* Transfer context */ typedef struct mms_http_transfer { @@ -340,10 +340,10 @@ mms_task_http_got_chunk( { MMSTaskHttpPrivate* priv = http->priv; MMSHttpTransfer* tx = priv->tx; - MMS_VERBOSE("%u bytes received", priv->bytes_received); MMS_ASSERT(tx && tx->message == msg); if (tx && tx->message == msg) { priv->bytes_received += buf->length; + MMS_VERBOSE("%u bytes received", priv->bytes_received); if (write(tx->receive_fd, buf->data, buf->length) != (int)buf->length) { MMS_ERR("Write error: %s", strerror(errno)); mms_task_http_finish_transfer(http); diff --git a/mms-lib/src/mms_task_send.c b/mms-lib/src/mms_task_send.c new file mode 100644 index 0000000..dd2e8bf --- /dev/null +++ b/mms-lib/src/mms_task_send.c @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#include "mms_task.h" +#include "mms_task_http.h" +#include "mms_file_util.h" +#include "mms_handler.h" +#include "mms_codec.h" + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_task_send_log +#include "mms_lib_log.h" +#include "mms_error.h" +MMS_LOG_MODULE_DEFINE("mms-task-send"); + +/* Class definition */ +typedef MMSTaskHttpClass MMSTaskSendClass; +typedef MMSTaskHttp MMSTaskSend; + +G_DEFINE_TYPE(MMSTaskSend, mms_task_send, MMS_TYPE_TASK_HTTP); +#define MMS_TYPE_TASK_SEND (mms_task_send_get_type()) +#define MMS_TASK_SEND(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + MMS_TYPE_TASK_SEND, MMSTaskSend)) + +static +void +mms_task_send_started( + MMSTaskHttp* http) +{ + mms_handler_message_send_state_changed(http->task.handler, + http->task.id, MMS_SEND_STATE_SENDING); +} + +static +void +mms_task_send_paused( + MMSTaskHttp* http) +{ + mms_handler_message_send_state_changed(http->task.handler, + http->task.id, MMS_SEND_STATE_DEFERRED); +} + +static +void +mms_task_send_done( + MMSTaskHttp* http, + const char* path, + SoupStatus status) +{ + MMSPdu* pdu = NULL; + const char* msgid = NULL; + if (SOUP_STATUS_IS_SUCCESSFUL(status)) { + /* Decode the result */ + GError* error = NULL; + GMappedFile* map = g_mapped_file_new(path, FALSE, &error); + if (map) { + const void* data = g_mapped_file_get_contents(map); + const gsize len = g_mapped_file_get_length(map); + pdu = g_new0(MMSPdu, 1); + if (mms_message_decode(data, len, pdu)) { + if (pdu && + pdu->type == MMS_MESSAGE_TYPE_SEND_CONF) { + if (pdu->sc.rsp_status == MMS_MESSAGE_RSP_STATUS_OK) { + if (pdu->sc.msgid && pdu->sc.msgid[0]) { + msgid = pdu->sc.msgid; + MMS_INFO("Message ID %s", pdu->sc.msgid); + } else { + MMS_ERR("Missing Message-ID"); + } + } else { + MMS_ERR("MMSC responded with %u", pdu->sc.rsp_status); + } + } else { + MMS_ERR("Unexpected response from MMSC"); + } + } + g_mapped_file_unref(map); + } else { + MMS_ERR("%s", MMS_ERRMSG(error)); + g_error_free(error); + } + } + if (msgid) { + mms_handler_message_sent(http->task.handler, http->task.id, msgid); + } else { + mms_handler_message_send_state_changed(http->task.handler, + http->task.id, MMS_SEND_STATE_SEND_ERROR); + } + if (pdu) mms_message_free(pdu); +} + +/** + * Per class initializer + */ +static +void +mms_task_send_class_init( + MMSTaskSendClass* klass) +{ + klass->fn_started = mms_task_send_started; + klass->fn_paused = mms_task_send_paused; + klass->fn_done = mms_task_send_done; +} + +/** + * Per instance initializer + */ +static +void +mms_task_send_init( + MMSTaskSend* send) +{ +} + +/* Create MMS send task */ +MMSTask* +mms_task_send_new( + const MMSConfig* config, + MMSHandler* handler, + const char* id, + const char* imsi) +{ + return mms_task_http_alloc( + MMS_TYPE_TASK_SEND, config, handler, "Send", id, imsi, + NULL, MMS_SEND_CONF_FILE, MMS_SEND_REQ_FILE); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/test/common/Makefile b/mms-lib/test/common/Makefile index 99d10f9..2bcf340 100644 --- a/mms-lib/test/common/Makefile +++ b/mms-lib/test/common/Makefile @@ -71,7 +71,7 @@ RELEASE_FLAGS = $(BASE_FLAGS) -O2 DEBUG_DEFS = -DDEBUG RELEASE_DEFS = WARNINGS = -Wall -LIBS = $(shell pkg-config --libs $(LIB_PKGS)) +LIBS = $(shell pkg-config --libs $(LIB_PKGS)) -lmagic CFLAGS = $(shell pkg-config --cflags $(PKGS)) -I$(MMS_LIB_DIR)/include \ -I$(MMS_LIB_DIR)/src -I$(COMMON_DIR) -MMD diff --git a/mms-send/Makefile b/mms-send/Makefile new file mode 100644 index 0000000..4310eb2 --- /dev/null +++ b/mms-send/Makefile @@ -0,0 +1,129 @@ +# -*- Mode: makefile-gmake -*- + +.PHONY: all debug release clean + +# Required packages +PKGS = gio-unix-2.0 gio-2.0 glib-2.0 +LIB_PKGS = $(PKGS) + +# +# Default target +# + +all: debug release + +# +# Sources +# + +SRC = mms-send.c +GEN_SRC = org.nemomobile.MmsEngine.c + +# +# Directories +# + +SRC_DIR = . +SPEC_DIR = ../mms-engine +BUILD_DIR = build +GEN_DIR = $(BUILD_DIR) +DEBUG_BUILD_DIR = $(BUILD_DIR)/debug +RELEASE_BUILD_DIR = $(BUILD_DIR)/release + +# +# Tools and flags +# + +CC = $(CROSS_COMPILE)gcc +LD = $(CC) +DEBUG_FLAGS = -g +RELEASE_FLAGS = -O2 +DEBUG_DEFS = -DDEBUG +RELEASE_DEFS = +WARN = -Wall +CFLAGS = $(shell pkg-config --cflags $(PKGS)) -I. -I$(GEN_DIR) -MMD +LIBS = $(shell pkg-config --libs $(LIB_PKGS)) + +ifndef KEEP_SYMBOLS +KEEP_SYMBOLS = 0 +endif + +ifneq ($(KEEP_SYMBOLS),0) +RELEASE_FLAGS += -g +endif + +DEBUG_CFLAGS = $(DEBUG_FLAGS) $(DEBUG_DEFS) $(CFLAGS) +RELEASE_CFLAGS = $(RELEASE_FLAGS) $(RELEASE_DEFS) $(CFLAGS) +DEBUG_LIBS = $(LIBS) +RELEASE_LIBS = $(LIBS) + +# +# Files +# + +.PRECIOUS: $(GEN_SRC:%=$(GEN_DIR)/%) + +DEBUG_OBJS = \ + $(GEN_SRC:%.c=$(DEBUG_BUILD_DIR)/%.o) \ + $(SRC:%.c=$(DEBUG_BUILD_DIR)/%.o) +RELEASE_OBJS = \ + $(GEN_SRC:%.c=$(RELEASE_BUILD_DIR)/%.o) \ + $(SRC:%.c=$(RELEASE_BUILD_DIR)/%.o) + +# +# Dependencies +# + +DEBUG_EXE_DEPS = $(DEBUG_BUILD_DIR) +RELEASE_EXE_DEPS = $(RELEASE_BUILD_DIR) +DEPS = $(DEBUG_OBJS:%.o=%.d) $(RELEASE_OBJS:%.o=%.d) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(strip $(DEPS)),) +-include $(DEPS) +endif +endif + +# +# Rules +# + +EXE = mms-send +DEBUG_EXE = $(DEBUG_BUILD_DIR)/$(EXE) +RELEASE_EXE = $(RELEASE_BUILD_DIR)/$(EXE) + +debug: $(DEBUG_EXE) + +release: $(RELEASE_EXE) + +clean: + rm -fr $(BUILD_DIR) $(SRC_DIR)/*~ + +$(DEBUG_BUILD_DIR): + mkdir -p $@ + +$(RELEASE_BUILD_DIR): + mkdir -p $@ + +$(GEN_DIR)/%.c: $(SPEC_DIR)/%.xml + gdbus-codegen --generate-c-code $(@:%.c=%) $< + +$(DEBUG_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(WARN) $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(WARN) $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(DEBUG_BUILD_DIR)/%.o : $(GEN_DIR)/%.c + $(CC) -c $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o : $(GEN_DIR)/%.c + $(CC) -c $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(DEBUG_EXE): $(DEBUG_EXE_DEPS) $(DEBUG_OBJS) + $(LD) $(DEBUG_FLAGS) $(DEBUG_OBJS) $(DEBUG_LIBS) -o $@ + +$(RELEASE_EXE): $(RELEASE_EXE_DEPS) $(RELEASE_OBJS) + $(LD) $(RELEASE_FLAGS) $(RELEASE_OBJS) $(RELEASE_LIBS) -o $@ +ifeq ($(KEEP_SYMBOLS),0) + strip $@ +endif diff --git a/mms-send/mms-send.c b/mms-send/mms-send.c new file mode 100644 index 0000000..6a38a83 --- /dev/null +++ b/mms-send/mms-send.c @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + */ + +#include +#include +#include +#include +#include + +/* Generated headers */ +#include "org.nemomobile.MmsEngine.h" + +static const char pname[] = "mms-send"; + +enum app_ret_value { + RET_OK, + RET_ERR_CMDLINE, + RET_ERR_SEND +}; + +#define MMS_SEND_REQUEST_DELIVERY_REPORT (0x01) +#define MMS_SEND_REQUEST_READ_REPORT (0x02) +#define MMS_SEND_FLAG_VERBOSE (0x04) + +/* Protocol flags */ +#define MMS_SEND_REQUEST_PROTOCOL_FLAGS (\ + MMS_SEND_REQUEST_DELIVERY_REPORT |\ + MMS_SEND_REQUEST_READ_REPORT ) + +static +char* +mms_send( + char* files[], + int count, + const char* to, + const char* subject, + int flags) +{ + char* imsi = NULL; + GError* error = NULL; + OrgNemomobileMmsEngine* proxy = + org_nemomobile_mms_engine_proxy_new_for_bus_sync( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, + "org.nemomobile.MmsEngine", "/", NULL, &error); + if (proxy) { + int i; + gboolean ok = TRUE; + char* fname = g_malloc(PATH_MAX); + GVariant* parts; + GVariantBuilder b; + + g_variant_builder_init(&b, G_VARIANT_TYPE("a(sss)")); + for (i=0; ok && imessage); + g_error_free(error); + } + g_free(to_list); + } + + g_free(fname); + g_variant_unref(parts); + g_object_unref(proxy); + } else if (error) { + fprintf(stderr, "%s\n", error->message); + g_error_free(error); + } + return imsi; +} + +int main(int argc, char* argv[]) +{ + int ret = RET_ERR_CMDLINE; + gboolean ok, verbose = FALSE, dr = FALSE, rr = FALSE; + GError* error = NULL; + char* subject = NULL; + GOptionEntry entries[] = { + { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, + "Enable verbose output", NULL }, + { "delivery-report", 'd', 0, G_OPTION_ARG_NONE, &dr, + "Request delivery report", NULL }, + { "read-report", 'r', 0, G_OPTION_ARG_NONE, &rr, + "Request read report", NULL }, + { "subject", 's', 0, G_OPTION_ARG_STRING, &subject, + "Request read report", NULL }, + { NULL } + }; + GOptionContext* options = g_option_context_new("TO FILES..."); + g_option_context_add_main_entries(options, entries, NULL); + ok = g_option_context_parse(options, &argc, &argv, &error); + if (ok) { + if (argc > 2) { + int flags = 0; + char* imsi; + if (verbose) flags |= MMS_SEND_FLAG_VERBOSE; + if (dr) flags |= MMS_SEND_REQUEST_DELIVERY_REPORT; + if (rr) flags |= MMS_SEND_REQUEST_READ_REPORT; + imsi = mms_send(argv+2, argc-2, argv[1], subject, flags); + if (imsi) { + if (verbose) printf("%s\n", imsi); + g_free(imsi); + ret = RET_OK; + } + } else { + char* help = g_option_context_get_help(options, TRUE, NULL); + fprintf(stderr, "%s", help); + g_free(help); + } + } else { + printf("%s: %s\n", pname, error->message); + g_error_free(error); + } + g_option_context_free(options); + return ret; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */