diff options
285 files changed, 18750 insertions, 251 deletions
@@ -40,3 +40,6 @@ checker-report.xml VERSION.* cppcheck.log /build +/.settings/ +/nbproject/private/ +3rdParty/LibMiniUPnPc/src/miniupnpc/miniupnpcstrings.h diff --git a/3rdParty/LibMiniUPnPc/SConscript b/3rdParty/LibMiniUPnPc/SConscript new file mode 100644 index 0000000..be9910c --- /dev/null +++ b/3rdParty/LibMiniUPnPc/SConscript @@ -0,0 +1,66 @@ +Import(["env", "conf_env"]) + +if env.get("LIBMINIUPNPC_BUNDLED", False) : + +################################################################################ +# Module flags +################################################################################ + + if env["SCONS_STAGE"] == "flags" : + env["LIBMINIUPNPC_FLAGS"] = { + "CPPPATH": [Dir("src/miniupnpc")], + "LIBPATH": [Dir(".")], + "LIBS": ["Swiften_MiniUPnPc"], + } + #if env["PLATFORM"] == "win32" : + # env["LIBIDN_FLAGS"]["CPPPATH"] += [Dir("stubs/win32")] + # if env["MSVC_VERSION"][:3] == "9.0" : + # env["LIBIDN_FLAGS"]["CPPPATH"] += [Dir("stubs/win32/VC2008")] + +################################################################################ +# Build +################################################################################ + + if env["SCONS_STAGE"] == "build" : + myenv = env.Clone() + myenv.Append(CPPPATH = ["src"]) + # Remove warn flags + myenv.Replace(CCFLAGS = [flag for flag in env["CCFLAGS"] if flag not in ["-W", "-Wall"]]) + + myenv.Append(CCFLAGS = ["-DNDEBUG", "-DSTATICLIB"]) + + if myenv["PLATFORM"] != "win32": + myenv.Append(CCFLAGS = ["-DMINIUPNPC_SET_SOCKET_TIMEOUT"]) + + if myenv["PLATFORM"] == "darwin": + myenv.Append(CCFLAGS = ["-DMACOSX", "-D_DARWIN_C_SOURCE"]) + + if myenv["PLATFORM"] == "win32": + myenv.Append(CCFLAGS = ["-DWIN32", "-D_WIN32_WINNT=0x0501"]) + + myenv.WriteVal("src/miniupnpc/miniupnpcstrings.h", myenv.Value( +""" +#ifndef __MINIUPNPCSTRINGS_H__ +#define __MINIUPNPCSTRINGS_H__ + +#define OS_STRING "$OS_STRING" +#define MINIUPNPC_VERSION_STRING "1.5" + +#endif +""".replace("$OS_STRING", myenv["PLATFORM"]))) + + myenv.StaticLibrary("Swiften_MiniUPnPc", [ + "src/miniupnpc/igd_desc_parse.c", + "src/miniupnpc/miniupnpc.c", + "src/miniupnpc/minixml.c", + "src/miniupnpc/minisoap.c", + "src/miniupnpc/minissdpc.c", + "src/miniupnpc/miniwget.c", + #"src/miniupnpc/upnpc.c", + "src/miniupnpc/upnpcommands.c", + "src/miniupnpc/upnpreplyparse.c", + "src/miniupnpc/upnperrors.c", + "src/miniupnpc/connecthostport.c", + "src/miniupnpc/portlistingparse.c", + "src/miniupnpc/receivedata.c" + ]) diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/LICENSE b/3rdParty/LibMiniUPnPc/src/miniupnpc/LICENSE new file mode 100644 index 0000000..2434c86 --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/LICENSE @@ -0,0 +1,27 @@ +MiniUPnPc +Copyright (c) 2005-2011, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/bsdqueue.h b/3rdParty/LibMiniUPnPc/src/miniupnpc/bsdqueue.h new file mode 100644 index 0000000..1fe0599 --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/bsdqueue.h @@ -0,0 +1,531 @@ +/* $OpenBSD: queue.h,v 1.31 2005/11/25 08:06:25 otto Exp $ */ +/* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */ + +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + */ + +#ifndef _SYS_QUEUE_H_ +#define _SYS_QUEUE_H_ + +/* + * This file defines five types of data structures: singly-linked lists, + * lists, simple queues, tail queues, and circular queues. + * + * + * A singly-linked list is headed by a single forward pointer. The elements + * are singly linked for minimum space and pointer manipulation overhead at + * the expense of O(n) removal for arbitrary elements. New elements can be + * added to the list after an existing element or at the head of the list. + * Elements being removed from the head of the list should use the explicit + * macro for this purpose for optimum efficiency. A singly-linked list may + * only be traversed in the forward direction. Singly-linked lists are ideal + * for applications with large datasets and few or no removals or for + * implementing a LIFO queue. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may only be traversed in the forward direction. + * + * A simple queue is headed by a pair of pointers, one the head of the + * list and the other to the tail of the list. The elements are singly + * linked to save space, so elements can only be removed from the + * head of the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the + * list. A simple queue may only be traversed in the forward direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * A circle queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the list. + * A circle queue may be traversed in either direction, but has a more + * complex end of list detection. + * + * For details on the use of these macros, see the queue(3) manual page. + */ + +#ifdef QUEUE_MACRO_DEBUG +#define _Q_INVALIDATE(a) (a) = ((void *)-1) +#else +#define _Q_INVALIDATE(a) +#endif + +/* + * Singly-linked List definitions. + */ +#define SLIST_HEAD(name, type) \ +struct name { \ + struct type *slh_first; /* first element */ \ +} + +#define SLIST_HEAD_INITIALIZER(head) \ + { NULL } + +#ifdef SLIST_ENTRY +#undef SLIST_ENTRY +#endif + +#define SLIST_ENTRY(type) \ +struct { \ + struct type *sle_next; /* next element */ \ +} + +/* + * Singly-linked List access methods. + */ +#define SLIST_FIRST(head) ((head)->slh_first) +#define SLIST_END(head) NULL +#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head)) +#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) + +#define SLIST_FOREACH(var, head, field) \ + for((var) = SLIST_FIRST(head); \ + (var) != SLIST_END(head); \ + (var) = SLIST_NEXT(var, field)) + +#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \ + for ((varp) = &SLIST_FIRST((head)); \ + ((var) = *(varp)) != SLIST_END(head); \ + (varp) = &SLIST_NEXT((var), field)) + +/* + * Singly-linked List functions. + */ +#define SLIST_INIT(head) { \ + SLIST_FIRST(head) = SLIST_END(head); \ +} + +#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ + (elm)->field.sle_next = (slistelm)->field.sle_next; \ + (slistelm)->field.sle_next = (elm); \ +} while (0) + +#define SLIST_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.sle_next = (head)->slh_first; \ + (head)->slh_first = (elm); \ +} while (0) + +#define SLIST_REMOVE_NEXT(head, elm, field) do { \ + (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \ +} while (0) + +#define SLIST_REMOVE_HEAD(head, field) do { \ + (head)->slh_first = (head)->slh_first->field.sle_next; \ +} while (0) + +#define SLIST_REMOVE(head, elm, type, field) do { \ + if ((head)->slh_first == (elm)) { \ + SLIST_REMOVE_HEAD((head), field); \ + } else { \ + struct type *curelm = (head)->slh_first; \ + \ + while (curelm->field.sle_next != (elm)) \ + curelm = curelm->field.sle_next; \ + curelm->field.sle_next = \ + curelm->field.sle_next->field.sle_next; \ + _Q_INVALIDATE((elm)->field.sle_next); \ + } \ +} while (0) + +/* + * List definitions. + */ +#define LIST_HEAD(name, type) \ +struct name { \ + struct type *lh_first; /* first element */ \ +} + +#define LIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define LIST_ENTRY(type) \ +struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ +} + +/* + * List access methods + */ +#define LIST_FIRST(head) ((head)->lh_first) +#define LIST_END(head) NULL +#define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head)) +#define LIST_NEXT(elm, field) ((elm)->field.le_next) + +#define LIST_FOREACH(var, head, field) \ + for((var) = LIST_FIRST(head); \ + (var)!= LIST_END(head); \ + (var) = LIST_NEXT(var, field)) + +/* + * List functions. + */ +#define LIST_INIT(head) do { \ + LIST_FIRST(head) = LIST_END(head); \ +} while (0) + +#define LIST_INSERT_AFTER(listelm, elm, field) do { \ + if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ + (listelm)->field.le_next->field.le_prev = \ + &(elm)->field.le_next; \ + (listelm)->field.le_next = (elm); \ + (elm)->field.le_prev = &(listelm)->field.le_next; \ +} while (0) + +#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.le_prev = (listelm)->field.le_prev; \ + (elm)->field.le_next = (listelm); \ + *(listelm)->field.le_prev = (elm); \ + (listelm)->field.le_prev = &(elm)->field.le_next; \ +} while (0) + +#define LIST_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.le_next = (head)->lh_first) != NULL) \ + (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ + (head)->lh_first = (elm); \ + (elm)->field.le_prev = &(head)->lh_first; \ +} while (0) + +#define LIST_REMOVE(elm, field) do { \ + if ((elm)->field.le_next != NULL) \ + (elm)->field.le_next->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = (elm)->field.le_next; \ + _Q_INVALIDATE((elm)->field.le_prev); \ + _Q_INVALIDATE((elm)->field.le_next); \ +} while (0) + +#define LIST_REPLACE(elm, elm2, field) do { \ + if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ + (elm2)->field.le_next->field.le_prev = \ + &(elm2)->field.le_next; \ + (elm2)->field.le_prev = (elm)->field.le_prev; \ + *(elm2)->field.le_prev = (elm2); \ + _Q_INVALIDATE((elm)->field.le_prev); \ + _Q_INVALIDATE((elm)->field.le_next); \ +} while (0) + +/* + * Simple queue definitions. + */ +#define SIMPLEQ_HEAD(name, type) \ +struct name { \ + struct type *sqh_first; /* first element */ \ + struct type **sqh_last; /* addr of last next element */ \ +} + +#define SIMPLEQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).sqh_first } + +#define SIMPLEQ_ENTRY(type) \ +struct { \ + struct type *sqe_next; /* next element */ \ +} + +/* + * Simple queue access methods. + */ +#define SIMPLEQ_FIRST(head) ((head)->sqh_first) +#define SIMPLEQ_END(head) NULL +#define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head)) +#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) + +#define SIMPLEQ_FOREACH(var, head, field) \ + for((var) = SIMPLEQ_FIRST(head); \ + (var) != SIMPLEQ_END(head); \ + (var) = SIMPLEQ_NEXT(var, field)) + +/* + * Simple queue functions. + */ +#define SIMPLEQ_INIT(head) do { \ + (head)->sqh_first = NULL; \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (0) + +#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (head)->sqh_first = (elm); \ +} while (0) + +#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.sqe_next = NULL; \ + *(head)->sqh_last = (elm); \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (0) + +#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (listelm)->field.sqe_next = (elm); \ +} while (0) + +#define SIMPLEQ_REMOVE_HEAD(head, field) do { \ + if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (0) + +/* + * Tail queue definitions. + */ +#define TAILQ_HEAD(name, type) \ +struct name { \ + struct type *tqh_first; /* first element */ \ + struct type **tqh_last; /* addr of last next element */ \ +} + +#define TAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).tqh_first } + +#define TAILQ_ENTRY(type) \ +struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ +} + +/* + * tail queue access methods + */ +#define TAILQ_FIRST(head) ((head)->tqh_first) +#define TAILQ_END(head) NULL +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) +/* XXX */ +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) +#define TAILQ_EMPTY(head) \ + (TAILQ_FIRST(head) == TAILQ_END(head)) + +#define TAILQ_FOREACH(var, head, field) \ + for((var) = TAILQ_FIRST(head); \ + (var) != TAILQ_END(head); \ + (var) = TAILQ_NEXT(var, field)) + +#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for((var) = TAILQ_LAST(head, headname); \ + (var) != TAILQ_END(head); \ + (var) = TAILQ_PREV(var, headname, field)) + +/* + * Tail queue functions. + */ +#define TAILQ_INIT(head) do { \ + (head)->tqh_first = NULL; \ + (head)->tqh_last = &(head)->tqh_first; \ +} while (0) + +#define TAILQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ + (head)->tqh_first->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (head)->tqh_first = (elm); \ + (elm)->field.tqe_prev = &(head)->tqh_first; \ +} while (0) + +#define TAILQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.tqe_next = NULL; \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &(elm)->field.tqe_next; \ +} while (0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ + (elm)->field.tqe_next->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (listelm)->field.tqe_next = (elm); \ + (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ +} while (0) + +#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + (elm)->field.tqe_next = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ +} while (0) + +#define TAILQ_REMOVE(head, elm, field) do { \ + if (((elm)->field.tqe_next) != NULL) \ + (elm)->field.tqe_next->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ + _Q_INVALIDATE((elm)->field.tqe_prev); \ + _Q_INVALIDATE((elm)->field.tqe_next); \ +} while (0) + +#define TAILQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \ + (elm2)->field.tqe_next->field.tqe_prev = \ + &(elm2)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm2)->field.tqe_next; \ + (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ + *(elm2)->field.tqe_prev = (elm2); \ + _Q_INVALIDATE((elm)->field.tqe_prev); \ + _Q_INVALIDATE((elm)->field.tqe_next); \ +} while (0) + +/* + * Circular queue definitions. + */ +#define CIRCLEQ_HEAD(name, type) \ +struct name { \ + struct type *cqh_first; /* first element */ \ + struct type *cqh_last; /* last element */ \ +} + +#define CIRCLEQ_HEAD_INITIALIZER(head) \ + { CIRCLEQ_END(&head), CIRCLEQ_END(&head) } + +#define CIRCLEQ_ENTRY(type) \ +struct { \ + struct type *cqe_next; /* next element */ \ + struct type *cqe_prev; /* previous element */ \ +} + +/* + * Circular queue access methods + */ +#define CIRCLEQ_FIRST(head) ((head)->cqh_first) +#define CIRCLEQ_LAST(head) ((head)->cqh_last) +#define CIRCLEQ_END(head) ((void *)(head)) +#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) +#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) +#define CIRCLEQ_EMPTY(head) \ + (CIRCLEQ_FIRST(head) == CIRCLEQ_END(head)) + +#define CIRCLEQ_FOREACH(var, head, field) \ + for((var) = CIRCLEQ_FIRST(head); \ + (var) != CIRCLEQ_END(head); \ + (var) = CIRCLEQ_NEXT(var, field)) + +#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ + for((var) = CIRCLEQ_LAST(head); \ + (var) != CIRCLEQ_END(head); \ + (var) = CIRCLEQ_PREV(var, field)) + +/* + * Circular queue functions. + */ +#define CIRCLEQ_INIT(head) do { \ + (head)->cqh_first = CIRCLEQ_END(head); \ + (head)->cqh_last = CIRCLEQ_END(head); \ +} while (0) + +#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm)->field.cqe_next; \ + (elm)->field.cqe_prev = (listelm); \ + if ((listelm)->field.cqe_next == CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm); \ + else \ + (listelm)->field.cqe_next->field.cqe_prev = (elm); \ + (listelm)->field.cqe_next = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm); \ + (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ + if ((listelm)->field.cqe_prev == CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm); \ + else \ + (listelm)->field.cqe_prev->field.cqe_next = (elm); \ + (listelm)->field.cqe_prev = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.cqe_next = (head)->cqh_first; \ + (elm)->field.cqe_prev = CIRCLEQ_END(head); \ + if ((head)->cqh_last == CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm); \ + else \ + (head)->cqh_first->field.cqe_prev = (elm); \ + (head)->cqh_first = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.cqe_next = CIRCLEQ_END(head); \ + (elm)->field.cqe_prev = (head)->cqh_last; \ + if ((head)->cqh_first == CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm); \ + else \ + (head)->cqh_last->field.cqe_next = (elm); \ + (head)->cqh_last = (elm); \ +} while (0) + +#define CIRCLEQ_REMOVE(head, elm, field) do { \ + if ((elm)->field.cqe_next == CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm)->field.cqe_prev; \ + else \ + (elm)->field.cqe_next->field.cqe_prev = \ + (elm)->field.cqe_prev; \ + if ((elm)->field.cqe_prev == CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm)->field.cqe_next; \ + else \ + (elm)->field.cqe_prev->field.cqe_next = \ + (elm)->field.cqe_next; \ + _Q_INVALIDATE((elm)->field.cqe_prev); \ + _Q_INVALIDATE((elm)->field.cqe_next); \ +} while (0) + +#define CIRCLEQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.cqe_next = (elm)->field.cqe_next) == \ + CIRCLEQ_END(head)) \ + (head).cqh_last = (elm2); \ + else \ + (elm2)->field.cqe_next->field.cqe_prev = (elm2); \ + if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) == \ + CIRCLEQ_END(head)) \ + (head).cqh_first = (elm2); \ + else \ + (elm2)->field.cqe_prev->field.cqe_next = (elm2); \ + _Q_INVALIDATE((elm)->field.cqe_prev); \ + _Q_INVALIDATE((elm)->field.cqe_next); \ +} while (0) + +#endif /* !_SYS_QUEUE_H_ */ diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/codelength.h b/3rdParty/LibMiniUPnPc/src/miniupnpc/codelength.h new file mode 100644 index 0000000..f11e5e9 --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/codelength.h @@ -0,0 +1,24 @@ +/* $Id: codelength.h,v 1.1 2008/10/06 22:04:06 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas BERNARD + * copyright (c) 2005-2008 Thomas Bernard + * This software is subjet to the conditions detailed in the + * provided LICENCE file. */ +#ifndef __CODELENGTH_H__ +#define __CODELENGTH_H__ + +/* Encode length by using 7bit per Byte : + * Most significant bit of each byte specifies that the + * following byte is part of the code */ +#define DECODELENGTH(n, p) n = 0; \ + do { n = (n << 7) | (*p & 0x7f); } \ + while(*(p++)&0x80); + +#define CODELENGTH(n, p) if(n>=268435456) *(p++) = (n >> 28) | 0x80; \ + if(n>=2097152) *(p++) = (n >> 21) | 0x80; \ + if(n>=16384) *(p++) = (n >> 14) | 0x80; \ + if(n>=128) *(p++) = (n >> 7) | 0x80; \ + *(p++) = n & 0x7f; + +#endif + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/connecthostport.c b/3rdParty/LibMiniUPnPc/src/miniupnpc/connecthostport.c new file mode 100644 index 0000000..76e8e37 --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/connecthostport.c @@ -0,0 +1,241 @@ +/* $Id: connecthostport.c,v 1.5 2011/04/09 08:49:50 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2010-2011 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. */ + +/* use getaddrinfo() or gethostbyname() + * uncomment the following line in order to use gethostbyname() */ +#ifdef NO_GETADDRINFO +#define USE_GETHOSTBYNAME +#endif + +#include <string.h> +#include <stdio.h> +#ifdef WIN32 +#include <winsock2.h> +#include <ws2tcpip.h> +#include <io.h> +#define MAXHOSTNAMELEN 64 +#define snprintf _snprintf +#define herror +#define socklen_t int +#else /* #ifdef WIN32 */ +#include <unistd.h> +#include <sys/param.h> +#include <errno.h> +#define closesocket close +#include <netdb.h> +/* defining MINIUPNPC_IGNORE_EINTR enable the ignore of interruptions + * during the connect() call */ +#define MINIUPNPC_IGNORE_EINTR +#ifndef USE_GETHOSTBYNAME +#include <sys/types.h> +#include <sys/socket.h> +#endif /* #ifndef USE_GETHOSTBYNAME */ +#endif /* #else WIN32 */ + +/* definition of PRINT_SOCKET_ERROR */ +#ifdef WIN32 +#define PRINT_SOCKET_ERROR(x) printf("Socket error: %s, %d\n", x, WSAGetLastError()); +#else +#define PRINT_SOCKET_ERROR(x) perror(x) +#endif + +#if defined(__amigaos__) || defined(__amigaos4__) +#define herror(A) printf("%s\n", A) +#endif + +#include "connecthostport.h" + +/* connecthostport() + * return a socket connected (TCP) to the host and port + * or -1 in case of error */ +int connecthostport(const char * host, unsigned short port) +{ + int s, n; +#ifdef USE_GETHOSTBYNAME + struct sockaddr_in dest; + struct hostent *hp; +#else /* #ifdef USE_GETHOSTBYNAME */ + char tmp_host[MAXHOSTNAMELEN+1]; + char port_str[8]; + struct addrinfo *ai, *p; + struct addrinfo hints; +#endif /* #ifdef USE_GETHOSTBYNAME */ +#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT + struct timeval timeout; +#endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ + +#ifdef USE_GETHOSTBYNAME + hp = gethostbyname(host); + if(hp == NULL) + { + herror(host); + return -1; + } + memcpy(&dest.sin_addr, hp->h_addr, sizeof(dest.sin_addr)); + memset(dest.sin_zero, 0, sizeof(dest.sin_zero)); + s = socket(PF_INET, SOCK_STREAM, 0); + if(s < 0) + { + PRINT_SOCKET_ERROR("socket"); + return -1; + } +#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT + /* setting a 3 seconds timeout for the connect() call */ + timeout.tv_sec = 3; + timeout.tv_usec = 0; + if(setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(struct timeval)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt"); + } + timeout.tv_sec = 3; + timeout.tv_usec = 0; + if(setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(struct timeval)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt"); + } +#endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ + dest.sin_family = AF_INET; + dest.sin_port = htons(port); + n = connect(s, (struct sockaddr *)&dest, sizeof(struct sockaddr_in)); +#ifdef MINIUPNPC_IGNORE_EINTR + while(n < 0 && errno == EINTR) + { + socklen_t len; + fd_set wset; + int err; + FD_ZERO(&wset); + FD_SET(s, &wset); + if((n = select(s + 1, NULL, &wset, NULL, NULL)) == -1 && errno == EINTR) + continue; + /*len = 0;*/ + /*n = getpeername(s, NULL, &len);*/ + len = sizeof(err); + if(getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len) < 0) { + PRINT_SOCKET_ERROR("getsockopt"); + closesocket(s); + return -1; + } + if(err != 0) { + errno = err; + n = -1; + } + } +#endif /* #ifdef MINIUPNPC_IGNORE_EINTR */ + if(n<0) + { + PRINT_SOCKET_ERROR("connect"); + closesocket(s); + return -1; + } +#else /* #ifdef USE_GETHOSTBYNAME */ + /* use getaddrinfo() instead of gethostbyname() */ + memset(&hints, 0, sizeof(hints)); + /* hints.ai_flags = AI_ADDRCONFIG; */ +#ifdef AI_NUMERICSERV + hints.ai_flags = AI_NUMERICSERV; +#endif + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = AF_UNSPEC; /* AF_INET, AF_INET6 or AF_UNSPEC */ + /* hints.ai_protocol = IPPROTO_TCP; */ + snprintf(port_str, sizeof(port_str), "%hu", port); + if(host[0] == '[') + { + /* literal ip v6 address */ + int i; + for(i = 0; host[i+1] && (host[i+1] != ']') && i < MAXHOSTNAMELEN; i++) + { + tmp_host[i] = host[i+1]; + } + tmp_host[i] = '\0'; + } + else + { + strncpy(tmp_host, host, MAXHOSTNAMELEN); + } + tmp_host[MAXHOSTNAMELEN] = '\0'; + n = getaddrinfo(tmp_host, port_str, &hints, &ai); + if(n != 0) + { +#ifdef WIN32 + fprintf(stderr, "getaddrinfo() error : %d\n", n); +#else + fprintf(stderr, "getaddrinfo() error : %s\n", gai_strerror(n)); +#endif + return -1; + } + s = -1; + for(p = ai; p; p = p->ai_next) + { + s = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if(s < 0) + continue; +#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT + /* setting a 3 seconds timeout for the connect() call */ + timeout.tv_sec = 3; + timeout.tv_usec = 0; + if(setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(struct timeval)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt"); + } + timeout.tv_sec = 3; + timeout.tv_usec = 0; + if(setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(struct timeval)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt"); + } +#endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ + n = connect(s, p->ai_addr, p->ai_addrlen); +#ifdef MINIUPNPC_IGNORE_EINTR + while(n < 0 && errno == EINTR) + { + socklen_t len; + fd_set wset; + int err; + FD_ZERO(&wset); + FD_SET(s, &wset); + if((n = select(s + 1, NULL, &wset, NULL, NULL)) == -1 && errno == EINTR) + continue; + /*len = 0;*/ + /*n = getpeername(s, NULL, &len);*/ + len = sizeof(err); + if(getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len) < 0) { + PRINT_SOCKET_ERROR("getsockopt"); + closesocket(s); + freeaddrinfo(ai); + return -1; + } + if(err != 0) { + errno = err; + n = -1; + } + } +#endif /* #ifdef MINIUPNPC_IGNORE_EINTR */ + if(n < 0) + { + closesocket(s); + continue; + } + else + { + break; + } + } + freeaddrinfo(ai); + if(s < 0) + { + PRINT_SOCKET_ERROR("socket"); + return -1; + } + if(n < 0) + { + PRINT_SOCKET_ERROR("connect"); + return -1; + } +#endif /* #ifdef USE_GETHOSTBYNAME */ + return s; +} + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/connecthostport.h b/3rdParty/LibMiniUPnPc/src/miniupnpc/connecthostport.h new file mode 100644 index 0000000..57e24eb --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/connecthostport.h @@ -0,0 +1,17 @@ +/* $Id: connecthostport.h,v 1.1 2010/04/04 23:21:03 nanard Exp $ */ +/* Project: miniupnp + * http://miniupnp.free.fr/ + * Author: Thomas Bernard + * Copyright (c) 2010 Thomas Bernard + * This software is subjects to the conditions detailed + * in the LICENCE file provided within this distribution */ +#ifndef __CONNECTHOSTPORT_H__ +#define __CONNECTHOSTPORT_H__ + +/* connecthostport() + * return a socket connected (TCP) to the host and port + * or -1 in case of error */ +int connecthostport(const char * host, unsigned short port); + +#endif + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/declspec.h b/3rdParty/LibMiniUPnPc/src/miniupnpc/declspec.h new file mode 100644 index 0000000..b804247 --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/declspec.h @@ -0,0 +1,15 @@ +#ifndef __DECLSPEC_H__ +#define __DECLSPEC_H__ + +#if defined(WIN32) && !defined(STATICLIB) + #ifdef MINIUPNP_EXPORTS + #define LIBSPEC __declspec(dllexport) + #else + #define LIBSPEC __declspec(dllimport) + #endif +#else + #define LIBSPEC +#endif + +#endif + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/igd_desc_parse.c b/3rdParty/LibMiniUPnPc/src/miniupnpc/igd_desc_parse.c new file mode 100644 index 0000000..6c3e656 --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/igd_desc_parse.c @@ -0,0 +1,125 @@ +/* $Id: igd_desc_parse.c,v 1.14 2011/04/11 09:19:24 nanard Exp $ */ +/* Project : miniupnp + * http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2005-2010 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. */ + +#include "igd_desc_parse.h" +#include <stdio.h> +#include <string.h> + +/* Start element handler : + * update nesting level counter and copy element name */ +void IGDstartelt(void * d, const char * name, int l) +{ + struct IGDdatas * datas = (struct IGDdatas *)d; + memcpy( datas->cureltname, name, l); + datas->cureltname[l] = '\0'; + datas->level++; + if( (l==7) && !memcmp(name, "service", l) ) { + datas->tmp.controlurl[0] = '\0'; + datas->tmp.eventsuburl[0] = '\0'; + datas->tmp.scpdurl[0] = '\0'; + datas->tmp.servicetype[0] = '\0'; + } +} + +/* End element handler : + * update nesting level counter and update parser state if + * service element is parsed */ +void IGDendelt(void * d, const char * name, int l) +{ + struct IGDdatas * datas = (struct IGDdatas *)d; + datas->level--; + /*printf("endelt %2d %.*s\n", datas->level, l, name);*/ + if( (l==7) && !memcmp(name, "service", l) ) + { + /* + if( datas->state < 1 + && !strcmp(datas->servicetype, + // "urn:schemas-upnp-org:service:WANIPConnection:1") ) + "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1")) + datas->state ++; + */ + if(0==strcmp(datas->tmp.servicetype, + "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1")) { + memcpy(&datas->CIF, &datas->tmp, sizeof(struct IGDdatas_service)); + } else if(0==strcmp(datas->tmp.servicetype, + "urn:schemas-upnp-org:service:WANIPv6FirewallControl:1")) { + memcpy(&datas->IPv6FC, &datas->tmp, sizeof(struct IGDdatas_service)); + } else if(0==strcmp(datas->tmp.servicetype, + "urn:schemas-upnp-org:service:WANIPConnection:1") + || 0==strcmp(datas->tmp.servicetype, + "urn:schemas-upnp-org:service:WANPPPConnection:1") ) { + if(datas->first.servicetype[0] == '\0') { + memcpy(&datas->first, &datas->tmp, sizeof(struct IGDdatas_service)); + } else { + memcpy(&datas->second, &datas->tmp, sizeof(struct IGDdatas_service)); + } + } + } +} + +/* Data handler : + * copy data depending on the current element name and state */ +void IGDdata(void * d, const char * data, int l) +{ + struct IGDdatas * datas = (struct IGDdatas *)d; + char * dstmember = 0; + /*printf("%2d %s : %.*s\n", + datas->level, datas->cureltname, l, data); */ + if( !strcmp(datas->cureltname, "URLBase") ) + dstmember = datas->urlbase; + else if( !strcmp(datas->cureltname, "presentationURL") ) + dstmember = datas->presentationurl; + else if( !strcmp(datas->cureltname, "serviceType") ) + dstmember = datas->tmp.servicetype; + else if( !strcmp(datas->cureltname, "controlURL") ) + dstmember = datas->tmp.controlurl; + else if( !strcmp(datas->cureltname, "eventSubURL") ) + dstmember = datas->tmp.eventsuburl; + else if( !strcmp(datas->cureltname, "SCPDURL") ) + dstmember = datas->tmp.scpdurl; +/* else if( !strcmp(datas->cureltname, "deviceType") ) + dstmember = datas->devicetype_tmp;*/ + if(dstmember) + { + if(l>=MINIUPNPC_URL_MAXSIZE) + l = MINIUPNPC_URL_MAXSIZE-1; + memcpy(dstmember, data, l); + dstmember[l] = '\0'; + } +} + +void printIGD(struct IGDdatas * d) +{ + printf("urlbase = '%s'\n", d->urlbase); + printf("WAN Device (Common interface config) :\n"); + /*printf(" deviceType = '%s'\n", d->CIF.devicetype);*/ + printf(" serviceType = '%s'\n", d->CIF.servicetype); + printf(" controlURL = '%s'\n", d->CIF.controlurl); + printf(" eventSubURL = '%s'\n", d->CIF.eventsuburl); + printf(" SCPDURL = '%s'\n", d->CIF.scpdurl); + printf("primary WAN Connection Device (IP or PPP Connection):\n"); + /*printf(" deviceType = '%s'\n", d->first.devicetype);*/ + printf(" servicetype = '%s'\n", d->first.servicetype); + printf(" controlURL = '%s'\n", d->first.controlurl); + printf(" eventSubURL = '%s'\n", d->first.eventsuburl); + printf(" SCPDURL = '%s'\n", d->first.scpdurl); + printf("secondary WAN Connection Device (IP or PPP Connection):\n"); + /*printf(" deviceType = '%s'\n", d->second.devicetype);*/ + printf(" servicetype = '%s'\n", d->second.servicetype); + printf(" controlURL = '%s'\n", d->second.controlurl); + printf(" eventSubURL = '%s'\n", d->second.eventsuburl); + printf(" SCPDURL = '%s'\n", d->second.scpdurl); + printf("WAN IPv6 Firewall Control :\n"); + /*printf(" deviceType = '%s'\n", d->IPv6FC.devicetype);*/ + printf(" servicetype = '%s'\n", d->IPv6FC.servicetype); + printf(" controlURL = '%s'\n", d->IPv6FC.controlurl); + printf(" eventSubURL = '%s'\n", d->IPv6FC.eventsuburl); + printf(" SCPDURL = '%s'\n", d->IPv6FC.scpdurl); +} + + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/igd_desc_parse.h b/3rdParty/LibMiniUPnPc/src/miniupnpc/igd_desc_parse.h new file mode 100644 index 0000000..bab1fd5 --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/igd_desc_parse.h @@ -0,0 +1,48 @@ +/* $Id: igd_desc_parse.h,v 1.10 2011/04/11 09:19:24 nanard Exp $ */ +/* Project : miniupnp + * http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2005-2010 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#ifndef __IGD_DESC_PARSE_H__ +#define __IGD_DESC_PARSE_H__ + +/* Structure to store the result of the parsing of UPnP + * descriptions of Internet Gateway Devices */ +#define MINIUPNPC_URL_MAXSIZE (128) +struct IGDdatas_service { + char controlurl[MINIUPNPC_URL_MAXSIZE]; + char eventsuburl[MINIUPNPC_URL_MAXSIZE]; + char scpdurl[MINIUPNPC_URL_MAXSIZE]; + char servicetype[MINIUPNPC_URL_MAXSIZE]; + /*char devicetype[MINIUPNPC_URL_MAXSIZE];*/ +}; + +struct IGDdatas { + char cureltname[MINIUPNPC_URL_MAXSIZE]; + char urlbase[MINIUPNPC_URL_MAXSIZE]; + char presentationurl[MINIUPNPC_URL_MAXSIZE]; + int level; + /*int state;*/ + /* "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" */ + struct IGDdatas_service CIF; + /* "urn:schemas-upnp-org:service:WANIPConnection:1" + * "urn:schemas-upnp-org:service:WANPPPConnection:1" */ + struct IGDdatas_service first; + /* if both WANIPConnection and WANPPPConnection are present */ + struct IGDdatas_service second; + /* "urn:schemas-upnp-org:service:WANIPv6FirewallControl:1" */ + struct IGDdatas_service IPv6FC; + /* tmp */ + struct IGDdatas_service tmp; +}; + +void IGDstartelt(void *, const char *, int); +void IGDendelt(void *, const char *, int); +void IGDdata(void *, const char *, int); +void printIGD(struct IGDdatas *); + +#endif + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/minisoap.c b/3rdParty/LibMiniUPnPc/src/miniupnpc/minisoap.c new file mode 100644 index 0000000..8889bf0 --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/minisoap.c @@ -0,0 +1,121 @@ +/* $Id: minisoap.c,v 1.21 2011/03/22 19:15:35 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2005-2009 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * + * Minimal SOAP implementation for UPnP protocol. + */ +#include <stdio.h> +#include <string.h> +#ifdef WIN32 +#include <io.h> +#include <winsock2.h> +#define snprintf _snprintf +#else +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#endif +#include "minisoap.h" +#include "miniupnpcstrings.h" + +/* only for malloc */ +#include <stdlib.h> + +#ifdef WIN32 +#define PRINT_SOCKET_ERROR(x) printf("Socket error: %s, %d\n", x, WSAGetLastError()); +#else +#define PRINT_SOCKET_ERROR(x) perror(x) +#endif + +/* httpWrite sends the headers and the body to the socket + * and returns the number of bytes sent */ +static int +httpWrite(int fd, const char * body, int bodysize, + const char * headers, int headerssize) +{ + int n = 0; + /*n = write(fd, headers, headerssize);*/ + /*if(bodysize>0) + n += write(fd, body, bodysize);*/ + /* Note : my old linksys router only took into account + * soap request that are sent into only one packet */ + char * p; + /* TODO: AVOID MALLOC */ + p = malloc(headerssize+bodysize); + if(!p) + return 0; + memcpy(p, headers, headerssize); + memcpy(p+headerssize, body, bodysize); + /*n = write(fd, p, headerssize+bodysize);*/ + n = send(fd, p, headerssize+bodysize, 0); + if(n<0) { + PRINT_SOCKET_ERROR("send"); + } + /* disable send on the socket */ + /* draytek routers dont seems to like that... */ +#if 0 +#ifdef WIN32 + if(shutdown(fd, SD_SEND)<0) { +#else + if(shutdown(fd, SHUT_WR)<0) { /*SD_SEND*/ +#endif + PRINT_SOCKET_ERROR("shutdown"); + } +#endif + free(p); + return n; +} + +/* self explanatory */ +int soapPostSubmit(int fd, + const char * url, + const char * host, + unsigned short port, + const char * action, + const char * body, + const char * httpversion) +{ + int bodysize; + char headerbuf[512]; + int headerssize; + char portstr[8]; + bodysize = (int)strlen(body); + /* We are not using keep-alive HTTP connections. + * HTTP/1.1 needs the header Connection: close to do that. + * This is the default with HTTP/1.0 + * Using HTTP/1.1 means we need to support chunked transfer-encoding : + * When using HTTP/1.1, the router "BiPAC 7404VNOX" always use chunked + * transfer encoding. */ + /* Connection: Close is normally there only in HTTP/1.1 but who knows */ + portstr[0] = '\0'; + if(port != 80) + snprintf(portstr, sizeof(portstr), ":%hu", port); + headerssize = snprintf(headerbuf, sizeof(headerbuf), + "POST %s HTTP/%s\r\n" + "Host: %s%s\r\n" + "User-Agent: " OS_STRING ", UPnP/1.0, MiniUPnPc/" MINIUPNPC_VERSION_STRING "\r\n" + "Content-Length: %d\r\n" + "Content-Type: text/xml\r\n" + "SOAPAction: \"%s\"\r\n" + "Connection: Close\r\n" + "Cache-Control: no-cache\r\n" /* ??? */ + "Pragma: no-cache\r\n" + "\r\n", + url, httpversion, host, portstr, bodysize, action); +#ifdef DEBUG + /*printf("SOAP request : headersize=%d bodysize=%d\n", + headerssize, bodysize); + */ + printf("SOAP request : POST %s HTTP/%s - Host: %s%s\n", + url, httpversion, host, portstr); + printf("SOAPAction: \"%s\" - Content-Length: %d\n", action, bodysize); + printf("Headers :\n%s", headerbuf); + printf("Body :\n%s\n", body); +#endif + return httpWrite(fd, body, bodysize, headerbuf, headerssize); +} + + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/minisoap.h b/3rdParty/LibMiniUPnPc/src/miniupnpc/minisoap.h new file mode 100644 index 0000000..696725f --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/minisoap.h @@ -0,0 +1,15 @@ +/* $Id: minisoap.h,v 1.4 2010/04/12 20:39:41 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2005 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. */ +#ifndef __MINISOAP_H__ +#define __MINISOAP_H__ + +/*int httpWrite(int, const char *, int, const char *);*/ +int soapPostSubmit(int, const char *, const char *, unsigned short, + const char *, const char *, const char *); + +#endif + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/minissdpc.c b/3rdParty/LibMiniUPnPc/src/miniupnpc/minissdpc.c new file mode 100644 index 0000000..e61c1cd --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/minissdpc.c @@ -0,0 +1,138 @@ +/* $Id: minissdpc.c,v 1.14 2010/11/25 09:57:25 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas BERNARD + * copyright (c) 2005-2009 Thomas Bernard + * This software is subjet to the conditions detailed in the + * provided LICENCE file. */ +/*#include <syslog.h>*/ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#ifndef WIN32 +#include <unistd.h> +#include <sys/types.h> +#else +#define ssize_t int +#endif + +#if defined(WIN32) || defined(__amigaos__) || defined(__amigaos4__) +#ifdef WIN32 +#include <winsock2.h> +#include <ws2tcpip.h> +#include <io.h> +#include <winsock.h> +#include <stdint.h> +#endif +#if defined(__amigaos__) || defined(__amigaos4__) +#include <sys/socket.h> +#endif +#if defined(__amigaos__) +#define uint16_t unsigned short +#endif +/* Hack */ +#define UNIX_PATH_LEN 108 +struct sockaddr_un { + uint16_t sun_family; + char sun_path[UNIX_PATH_LEN]; +}; +#else +#include <sys/socket.h> +#include <sys/un.h> +#endif + +#include "minissdpc.h" +#include "miniupnpc.h" + +#include "codelength.h" + +struct UPNPDev * +getDevicesFromMiniSSDPD(const char * devtype, const char * socketpath) +{ + struct UPNPDev * tmp; + struct UPNPDev * devlist = NULL; + unsigned char buffer[2048]; + ssize_t n; + unsigned char * p; + unsigned char * url; + unsigned int i; + unsigned int urlsize, stsize, usnsize, l; + int s; + struct sockaddr_un addr; + + s = socket(AF_UNIX, SOCK_STREAM, 0); + if(s < 0) + { + /*syslog(LOG_ERR, "socket(unix): %m");*/ + perror("socket(unix)"); + return NULL; + } + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, socketpath, sizeof(addr.sun_path)); + /* TODO : check if we need to handle the EINTR */ + if(connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) + { + /*syslog(LOG_WARNING, "connect(\"%s\"): %m", socketpath);*/ + close(s); + return NULL; + } + stsize = strlen(devtype); + buffer[0] = 1; /* request type 1 : request devices/services by type */ + p = buffer + 1; + l = stsize; CODELENGTH(l, p); + if(p + stsize > buffer + sizeof(buffer)) + { + /* devtype is too long ! */ + close(s); + return NULL; + } + memcpy(p, devtype, stsize); + p += stsize; + if(write(s, buffer, p - buffer) < 0) + { + /*syslog(LOG_ERR, "write(): %m");*/ + perror("minissdpc.c: write()"); + close(s); + return NULL; + } + n = read(s, buffer, sizeof(buffer)); + if(n<=0) + { + perror("minissdpc.c: read()"); + close(s); + return NULL; + } + p = buffer + 1; + for(i = 0; i < buffer[0]; i++) + { + if(p+2>=buffer+sizeof(buffer)) + break; + DECODELENGTH(urlsize, p); + if(p+urlsize+2>=buffer+sizeof(buffer)) + break; + url = p; + p += urlsize; + DECODELENGTH(stsize, p); + if(p+stsize+2>=buffer+sizeof(buffer)) + break; + tmp = (struct UPNPDev *)malloc(sizeof(struct UPNPDev)+urlsize+stsize); + tmp->pNext = devlist; + tmp->descURL = tmp->buffer; + tmp->st = tmp->buffer + 1 + urlsize; + memcpy(tmp->buffer, url, urlsize); + tmp->buffer[urlsize] = '\0'; + memcpy(tmp->buffer + urlsize + 1, p, stsize); + p += stsize; + tmp->buffer[urlsize+1+stsize] = '\0'; + devlist = tmp; + /* added for compatibility with recent versions of MiniSSDPd + * >= 2007/12/19 */ + DECODELENGTH(usnsize, p); + p += usnsize; + if(p>buffer + sizeof(buffer)) + break; + } + close(s); + return devlist; +} + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/minissdpc.h b/3rdParty/LibMiniUPnPc/src/miniupnpc/minissdpc.h new file mode 100644 index 0000000..25e91ce --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/minissdpc.h @@ -0,0 +1,15 @@ +/* $Id: minissdpc.h,v 1.1 2007/08/31 15:15:33 nanard Exp $ */ +/* Project: miniupnp + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * Author: Thomas Bernard + * Copyright (c) 2005-2007 Thomas Bernard + * This software is subjects to the conditions detailed + * in the LICENCE file provided within this distribution */ +#ifndef __MINISSDPC_H__ +#define __MINISSDPC_H__ + +struct UPNPDev * +getDevicesFromMiniSSDPD(const char * devtype, const char * socketpath); + +#endif + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/miniupnpc.c b/3rdParty/LibMiniUPnPc/src/miniupnpc/miniupnpc.c new file mode 100644 index 0000000..bd46a24 --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/miniupnpc.c @@ -0,0 +1,906 @@ +/* $Id: miniupnpc.c,v 1.95 2011/05/15 21:42:26 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas BERNARD + * copyright (c) 2005-2011 Thomas Bernard + * This software is subjet to the conditions detailed in the + * provided LICENSE file. */ +#define __EXTENSIONS__ 1 +#if !defined(MACOSX) && !defined(__sun) +#if !defined(_XOPEN_SOURCE) && !defined(__OpenBSD__) && !defined(__NetBSD__) +#ifndef __cplusplus +#define _XOPEN_SOURCE 600 +#endif +#endif +#ifndef __BSD_VISIBLE +#define __BSD_VISIBLE 1 +#endif +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#ifdef WIN32 +/* Win32 Specific includes and defines */ +#include <winsock2.h> +#include <ws2tcpip.h> +#include <io.h> +#include <iphlpapi.h> +#define snprintf _snprintf +#ifndef strncasecmp +#if defined(_MSC_VER) && (_MSC_VER >= 1400) +#define strncasecmp _memicmp +#else /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ +#define strncasecmp memicmp +#endif /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ +#endif /* #ifndef strncasecmp */ +#define MAXHOSTNAMELEN 64 +#else /* #ifdef WIN32 */ +/* Standard POSIX includes */ +#include <unistd.h> +#if defined(__amigaos__) && !defined(__amigaos4__) +/* Amiga OS 3 specific stuff */ +#define socklen_t int +#else +#include <sys/select.h> +#endif +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/param.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <net/if.h> +#if !defined(__amigaos__) && !defined(__amigaos4__) +#include <poll.h> +#endif +#include <strings.h> +#include <errno.h> +#define closesocket close +#endif /* #else WIN32 */ +#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT +#include <sys/time.h> +#endif +#if defined(__amigaos__) || defined(__amigaos4__) +/* Amiga OS specific stuff */ +#define TIMEVAL struct timeval +#endif + +#include "miniupnpc.h" +#include "minissdpc.h" +#include "miniwget.h" +#include "minisoap.h" +#include "minixml.h" +#include "upnpcommands.h" +#include "connecthostport.h" +#include "receivedata.h" + +#ifdef WIN32 +#define PRINT_SOCKET_ERROR(x) printf("Socket error: %s, %d\n", x, WSAGetLastError()); +#else +#define PRINT_SOCKET_ERROR(x) perror(x) +#endif + +#define SOAPPREFIX "s" +#define SERVICEPREFIX "u" +#define SERVICEPREFIX2 'u' + +/* root description parsing */ +LIBSPEC void parserootdesc(const char * buffer, int bufsize, struct IGDdatas * data) +{ + struct xmlparser parser; + /* xmlparser object */ + parser.xmlstart = buffer; + parser.xmlsize = bufsize; + parser.data = data; + parser.starteltfunc = IGDstartelt; + parser.endeltfunc = IGDendelt; + parser.datafunc = IGDdata; + parser.attfunc = 0; + parsexml(&parser); +#ifdef DEBUG + printIGD(data); +#endif +} + +/* simpleUPnPcommand2 : + * not so simple ! + * return values : + * pointer - OK + * NULL - error */ +char * simpleUPnPcommand2(int s, const char * url, const char * service, + const char * action, struct UPNParg * args, + int * bufsize, const char * httpversion) +{ + char hostname[MAXHOSTNAMELEN+1]; + unsigned short port = 0; + char * path; + char soapact[128]; + char soapbody[2048]; + char * buf; + int n; + + *bufsize = 0; + snprintf(soapact, sizeof(soapact), "%s#%s", service, action); + if(args==NULL) + { + /*soapbodylen = */snprintf(soapbody, sizeof(soapbody), + "<?xml version=\"1.0\"?>\r\n" + "<" SOAPPREFIX ":Envelope " + "xmlns:" SOAPPREFIX "=\"http://schemas.xmlsoap.org/soap/envelope/\" " + SOAPPREFIX ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<" SOAPPREFIX ":Body>" + "<" SERVICEPREFIX ":%s xmlns:" SERVICEPREFIX "=\"%s\">" + "</" SERVICEPREFIX ":%s>" + "</" SOAPPREFIX ":Body></" SOAPPREFIX ":Envelope>" + "\r\n", action, service, action); + } + else + { + char * p; + const char * pe, * pv; + int soapbodylen; + soapbodylen = snprintf(soapbody, sizeof(soapbody), + "<?xml version=\"1.0\"?>\r\n" + "<" SOAPPREFIX ":Envelope " + "xmlns:" SOAPPREFIX "=\"http://schemas.xmlsoap.org/soap/envelope/\" " + SOAPPREFIX ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<" SOAPPREFIX ":Body>" + "<" SERVICEPREFIX ":%s xmlns:" SERVICEPREFIX "=\"%s\">", + action, service); + p = soapbody + soapbodylen; + while(args->elt) + { + /* check that we are never overflowing the string... */ + if(soapbody + sizeof(soapbody) <= p + 100) + { + /* we keep a margin of at least 100 bytes */ + return NULL; + } + *(p++) = '<'; + pe = args->elt; + while(*pe) + *(p++) = *(pe++); + *(p++) = '>'; + if((pv = args->val)) + { + while(*pv) + *(p++) = *(pv++); + } + *(p++) = '<'; + *(p++) = '/'; + pe = args->elt; + while(*pe) + *(p++) = *(pe++); + *(p++) = '>'; + args++; + } + *(p++) = '<'; + *(p++) = '/'; + *(p++) = SERVICEPREFIX2; + *(p++) = ':'; + pe = action; + while(*pe) + *(p++) = *(pe++); + strncpy(p, "></" SOAPPREFIX ":Body></" SOAPPREFIX ":Envelope>\r\n", + soapbody + sizeof(soapbody) - p); + } + if(!parseURL(url, hostname, &port, &path)) return NULL; + if(s<0) + { + s = connecthostport(hostname, port); + if(s < 0) + { + return NULL; + } + } + + n = soapPostSubmit(s, path, hostname, port, soapact, soapbody, httpversion); + if(n<=0) { +#ifdef DEBUG + printf("Error sending SOAP request\n"); +#endif + closesocket(s); + return NULL; + } + + buf = getHTTPResponse(s, bufsize); +#ifdef DEBUG + if(*bufsize > 0 && buf) + { + printf("SOAP Response :\n%.*s\n", *bufsize, buf); + } +#endif + closesocket(s); + return buf; +} + +/* simpleUPnPcommand : + * not so simple ! + * return values : + * pointer - OK + * NULL - error */ +char * simpleUPnPcommand(int s, const char * url, const char * service, + const char * action, struct UPNParg * args, + int * bufsize) +{ + char * buf; + + buf = simpleUPnPcommand2(s, url, service, action, args, bufsize, "1.1"); +/* + buf = simpleUPnPcommand2(s, url, service, action, args, bufsize, "1.0"); + if (!buf || *bufsize == 0) + { +#if DEBUG + printf("Error or no result from SOAP request; retrying with HTTP/1.1\n"); +#endif + buf = simpleUPnPcommand2(s, url, service, action, args, bufsize, "1.1"); + } +*/ + return buf; +} + +/* parseMSEARCHReply() + * the last 4 arguments are filled during the parsing : + * - location/locationsize : "location:" field of the SSDP reply packet + * - st/stsize : "st:" field of the SSDP reply packet. + * The strings are NOT null terminated */ +static void +parseMSEARCHReply(const char * reply, int size, + const char * * location, int * locationsize, + const char * * st, int * stsize) +{ + int a, b, i; + i = 0; + a = i; /* start of the line */ + b = 0; /* end of the "header" (position of the colon) */ + while(i<size) + { + switch(reply[i]) + { + case ':': + if(b==0) + { + b = i; /* end of the "header" */ + /*for(j=a; j<b; j++) + { + putchar(reply[j]); + } + */ + } + break; + case '\x0a': + case '\x0d': + if(b!=0) + { + /*for(j=b+1; j<i; j++) + { + putchar(reply[j]); + } + putchar('\n');*/ + /* skip the colon and white spaces */ + do { b++; } while(reply[b]==' '); + if(0==strncasecmp(reply+a, "location", 8)) + { + *location = reply+b; + *locationsize = i-b; + } + else if(0==strncasecmp(reply+a, "st", 2)) + { + *st = reply+b; + *stsize = i-b; + } + b = 0; + } + a = i+1; + break; + default: + break; + } + i++; + } +} + +/* port upnp discover : SSDP protocol */ +#define PORT 1900 +#define XSTR(s) STR(s) +#define STR(s) #s +#define UPNP_MCAST_ADDR "239.255.255.250" +/* for IPv6 */ +#define UPNP_MCAST_LL_ADDR "FF02::C" /* link-local */ +#define UPNP_MCAST_SL_ADDR "FF05::C" /* site-local */ + +/* upnpDiscover() : + * return a chained list of all devices found or NULL if + * no devices was found. + * It is up to the caller to free the chained list + * delay is in millisecond (poll) */ +LIBSPEC struct UPNPDev * +upnpDiscover(int delay, const char * multicastif, + const char * minissdpdsock, int sameport, + int ipv6, + int * error) +{ + struct UPNPDev * tmp; + struct UPNPDev * devlist = 0; + int opt = 1; + static const char MSearchMsgFmt[] = + "M-SEARCH * HTTP/1.1\r\n" + "HOST: %s:" XSTR(PORT) "\r\n" + "ST: %s\r\n" + "MAN: \"ssdp:discover\"\r\n" + "MX: %u\r\n" + "\r\n"; + static const char * const deviceList[] = { +#if 0 + "urn:schemas-upnp-org:device:InternetGatewayDevice:2", + "urn:schemas-upnp-org:service:WANIPConnection:2", +#endif + "urn:schemas-upnp-org:device:InternetGatewayDevice:1", + "urn:schemas-upnp-org:service:WANIPConnection:1", + "urn:schemas-upnp-org:service:WANPPPConnection:1", + "upnp:rootdevice", + 0 + }; + int deviceIndex = 0; + char bufr[1536]; /* reception and emission buffer */ + int sudp; + int n; + struct sockaddr_storage sockudp_r; + unsigned int mx; +#ifdef NO_GETADDRINFO + struct sockaddr_storage sockudp_w; +#else + int rv; + struct addrinfo hints, *servinfo, *p; +#endif +#ifdef WIN32 + MIB_IPFORWARDROW ip_forward; +#endif + int linklocal = 1; + + if(error) + *error = UPNPDISCOVER_UNKNOWN_ERROR; +#if !defined(WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) + /* first try to get infos from minissdpd ! */ + if(!minissdpdsock) + minissdpdsock = "/var/run/minissdpd.sock"; + while(!devlist && deviceList[deviceIndex]) { + devlist = getDevicesFromMiniSSDPD(deviceList[deviceIndex], + minissdpdsock); + /* We return what we have found if it was not only a rootdevice */ + if(devlist && !strstr(deviceList[deviceIndex], "rootdevice")) { + if(error) + *error = UPNPDISCOVER_SUCCESS; + return devlist; + } + deviceIndex++; + } + deviceIndex = 0; +#endif + /* fallback to direct discovery */ +#ifdef WIN32 + sudp = socket(ipv6 ? PF_INET6 : PF_INET, SOCK_DGRAM, IPPROTO_UDP); +#else + sudp = socket(ipv6 ? PF_INET6 : PF_INET, SOCK_DGRAM, 0); +#endif + if(sudp < 0) + { + if(error) + *error = UPNPDISCOVER_SOCKET_ERROR; + PRINT_SOCKET_ERROR("socket"); + return NULL; + } + /* reception */ + memset(&sockudp_r, 0, sizeof(struct sockaddr_storage)); + if(ipv6) { + struct sockaddr_in6 * p = (struct sockaddr_in6 *)&sockudp_r; + p->sin6_family = AF_INET6; + if(sameport) + p->sin6_port = htons(PORT); + p->sin6_addr = in6addr_any; /* in6addr_any is not available with MinGW32 3.4.2 */ + } else { + struct sockaddr_in * p = (struct sockaddr_in *)&sockudp_r; + p->sin_family = AF_INET; + if(sameport) + p->sin_port = htons(PORT); + p->sin_addr.s_addr = INADDR_ANY; + } +#ifdef WIN32 +/* This code could help us to use the right Network interface for + * SSDP multicast traffic */ +/* Get IP associated with the index given in the ip_forward struct + * in order to give this ip to setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF) */ + if(!ipv6 + && (GetBestRoute(inet_addr("223.255.255.255"), 0, &ip_forward) == NO_ERROR)) { + DWORD dwRetVal = 0; + PMIB_IPADDRTABLE pIPAddrTable; + DWORD dwSize = 0; +#ifdef DEBUG + IN_ADDR IPAddr; +#endif + int i; +#ifdef DEBUG + printf("ifIndex=%lu nextHop=%lx \n", ip_forward.dwForwardIfIndex, ip_forward.dwForwardNextHop); +#endif + pIPAddrTable = (MIB_IPADDRTABLE *) malloc(sizeof (MIB_IPADDRTABLE)); + if (GetIpAddrTable(pIPAddrTable, &dwSize, 0) == ERROR_INSUFFICIENT_BUFFER) { + free(pIPAddrTable); + pIPAddrTable = (MIB_IPADDRTABLE *) malloc(dwSize); + } + if(pIPAddrTable) { + dwRetVal = GetIpAddrTable( pIPAddrTable, &dwSize, 0 ); +#ifdef DEBUG + printf("\tNum Entries: %ld\n", pIPAddrTable->dwNumEntries); +#endif + for (i=0; i < (int) pIPAddrTable->dwNumEntries; i++) { +#ifdef DEBUG + printf("\n\tInterface Index[%d]:\t%ld\n", i, pIPAddrTable->table[i].dwIndex); + IPAddr.S_un.S_addr = (u_long) pIPAddrTable->table[i].dwAddr; + printf("\tIP Address[%d]: \t%s\n", i, inet_ntoa(IPAddr) ); + IPAddr.S_un.S_addr = (u_long) pIPAddrTable->table[i].dwMask; + printf("\tSubnet Mask[%d]: \t%s\n", i, inet_ntoa(IPAddr) ); + IPAddr.S_un.S_addr = (u_long) pIPAddrTable->table[i].dwBCastAddr; + printf("\tBroadCast[%d]: \t%s (%ld)\n", i, inet_ntoa(IPAddr), pIPAddrTable->table[i].dwBCastAddr); + printf("\tReassembly size[%d]:\t%ld\n", i, pIPAddrTable->table[i].dwReasmSize); + printf("\tType and State[%d]:", i); + printf("\n"); +#endif + if (pIPAddrTable->table[i].dwIndex == ip_forward.dwForwardIfIndex) { + /* Set the address of this interface to be used */ + struct in_addr mc_if; + memset(&mc_if, 0, sizeof(mc_if)); + mc_if.s_addr = pIPAddrTable->table[i].dwAddr; + if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&mc_if, sizeof(mc_if)) < 0) { + PRINT_SOCKET_ERROR("setsockopt"); + } + ((struct sockaddr_in *)&sockudp_r)->sin_addr.s_addr = pIPAddrTable->table[i].dwAddr; +#ifndef DEBUG + break; +#endif + } + } + free(pIPAddrTable); + pIPAddrTable = NULL; + } + } +#endif + +#ifdef WIN32 + if (setsockopt(sudp, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof (opt)) < 0) +#else + if (setsockopt(sudp, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt)) < 0) +#endif + { + if(error) + *error = UPNPDISCOVER_SOCKET_ERROR; + PRINT_SOCKET_ERROR("setsockopt"); + return NULL; + } + + if(multicastif) + { + if(ipv6) { +#if !defined(WIN32) + /* according to MSDN, if_nametoindex() is supported since + * MS Windows Vista and MS Windows Server 2008. + * http://msdn.microsoft.com/en-us/library/bb408409%28v=vs.85%29.aspx */ + unsigned int ifindex = if_nametoindex(multicastif); /* eth0, etc. */ + if(setsockopt(sudp, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifindex, sizeof(&ifindex)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt"); + } +#else +#ifdef DEBUG + printf("Setting of multicast interface not supported in IPv6 under Windows.\n"); +#endif +#endif + } else { + struct in_addr mc_if; + mc_if.s_addr = inet_addr(multicastif); /* ex: 192.168.x.x */ + ((struct sockaddr_in *)&sockudp_r)->sin_addr.s_addr = mc_if.s_addr; + if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&mc_if, sizeof(mc_if)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt"); + } + } + } + + /* Avant d'envoyer le paquet on bind pour recevoir la reponse */ + if (bind(sudp, (const struct sockaddr *)&sockudp_r, + ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) != 0) + { + if(error) + *error = UPNPDISCOVER_SOCKET_ERROR; + PRINT_SOCKET_ERROR("bind"); + closesocket(sudp); + return NULL; + } + + if(error) + *error = UPNPDISCOVER_SUCCESS; + /* Calculating maximum response time in seconds */ + mx = ((unsigned int)delay) / 1000u; + /* receiving SSDP response packet */ + for(n = 0; deviceList[deviceIndex]; deviceIndex++) + { + if(n == 0) + { + /* sending the SSDP M-SEARCH packet */ + n = snprintf(bufr, sizeof(bufr), + MSearchMsgFmt, + ipv6 ? + (linklocal ? "[" UPNP_MCAST_LL_ADDR "]" : "[" UPNP_MCAST_SL_ADDR "]") + : UPNP_MCAST_ADDR, + deviceList[deviceIndex], mx); +#ifdef DEBUG + printf("Sending %s", bufr); +#endif +#ifdef NO_GETADDRINFO + /* the following code is not using getaddrinfo */ + /* emission */ + memset(&sockudp_w, 0, sizeof(struct sockaddr_storage)); + if(ipv6) { + struct sockaddr_in6 * p = (struct sockaddr_in6 *)&sockudp_w; + p->sin6_family = AF_INET6; + p->sin6_port = htons(PORT); + inet_pton(AF_INET6, + linklocal ? UPNP_MCAST_LL_ADDR : UPNP_MCAST_SL_ADDR, + &(p->sin6_addr)); + } else { + struct sockaddr_in * p = (struct sockaddr_in *)&sockudp_w; + p->sin_family = AF_INET; + p->sin_port = htons(PORT); + p->sin_addr.s_addr = inet_addr(UPNP_MCAST_ADDR); + } + n = sendto(sudp, bufr, n, 0, + &sockudp_w, + ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)); + if (n < 0) { + if(error) + *error = UPNPDISCOVER_SOCKET_ERROR; + PRINT_SOCKET_ERROR("sendto"); + break; + } +#else /* #ifdef NO_GETADDRINFO */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; // AF_INET6 or AF_INET + hints.ai_socktype = SOCK_DGRAM; + /*hints.ai_flags = */ + if ((rv = getaddrinfo(ipv6 + ? (linklocal ? UPNP_MCAST_LL_ADDR : UPNP_MCAST_SL_ADDR) + : UPNP_MCAST_ADDR, + XSTR(PORT), &hints, &servinfo)) != 0) { + if(error) + *error = UPNPDISCOVER_SOCKET_ERROR; +#ifdef WIN32 + fprintf(stderr, "getaddrinfo() failed: %d\n", rv); +#else + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); +#endif + break; + } + for(p = servinfo; p; p = p->ai_next) { + n = sendto(sudp, bufr, n, 0, p->ai_addr, p->ai_addrlen); + if (n < 0) { + PRINT_SOCKET_ERROR("sendto"); + continue; + } + } + freeaddrinfo(servinfo); + if(n < 0) { + if(error) + *error = UPNPDISCOVER_SOCKET_ERROR; + break; + } +#endif /* #ifdef NO_GETADDRINFO */ + } + /* Waiting for SSDP REPLY packet to M-SEARCH */ + n = receivedata(sudp, bufr, sizeof(bufr), delay); + if (n < 0) { + /* error */ + if(error) + *error = UPNPDISCOVER_SOCKET_ERROR; + break; + } else if (n == 0) { + /* no data or Time Out */ + if (devlist) { + /* no more device type to look for... */ + if(error) + *error = UPNPDISCOVER_SUCCESS; + break; + } + if(ipv6) { + if(linklocal) { + linklocal = 0; + --deviceIndex; + } else { + linklocal = 1; + } + } + } else { + const char * descURL=NULL; + int urlsize=0; + const char * st=NULL; + int stsize=0; + /*printf("%d byte(s) :\n%s\n", n, bufr);*/ /* affichage du message */ + parseMSEARCHReply(bufr, n, &descURL, &urlsize, &st, &stsize); + if(st&&descURL) + { +#ifdef DEBUG + printf("M-SEARCH Reply:\nST: %.*s\nLocation: %.*s\n", + stsize, st, urlsize, descURL); +#endif + for(tmp=devlist; tmp; tmp = tmp->pNext) { + if(memcmp(tmp->descURL, descURL, urlsize) == 0 && + tmp->descURL[urlsize] == '\0' && + memcmp(tmp->st, st, stsize) == 0 && + tmp->st[stsize] == '\0') + break; + } + /* at the exit of the loop above, tmp is null if + * no duplicate device was found */ + if(tmp) + continue; + tmp = (struct UPNPDev *)malloc(sizeof(struct UPNPDev)+urlsize+stsize); + if(!tmp) { + /* memory allocation error */ + if(error) + *error = UPNPDISCOVER_MEMORY_ERROR; + break; + } + tmp->pNext = devlist; + tmp->descURL = tmp->buffer; + tmp->st = tmp->buffer + 1 + urlsize; + memcpy(tmp->buffer, descURL, urlsize); + tmp->buffer[urlsize] = '\0'; + memcpy(tmp->buffer + urlsize + 1, st, stsize); + tmp->buffer[urlsize+1+stsize] = '\0'; + devlist = tmp; + } + } + } + closesocket(sudp); + return devlist; +} + +/* freeUPNPDevlist() should be used to + * free the chained list returned by upnpDiscover() */ +LIBSPEC void freeUPNPDevlist(struct UPNPDev * devlist) +{ + struct UPNPDev * next; + while(devlist) + { + next = devlist->pNext; + free(devlist); + devlist = next; + } +} + +static void +url_cpy_or_cat(char * dst, const char * src, int n) +{ + if( (src[0] == 'h') + &&(src[1] == 't') + &&(src[2] == 't') + &&(src[3] == 'p') + &&(src[4] == ':') + &&(src[5] == '/') + &&(src[6] == '/')) + { + strncpy(dst, src, n); + } + else + { + int l = strlen(dst); + if(src[0] != '/') + dst[l++] = '/'; + if(l<=n) + strncpy(dst + l, src, n - l); + } +} + +/* Prepare the Urls for usage... + */ +LIBSPEC void GetUPNPUrls(struct UPNPUrls * urls, struct IGDdatas * data, + const char * descURL) +{ + char * p; + int n1, n2, n3, n4; + n1 = strlen(data->urlbase); + if(n1==0) + n1 = strlen(descURL); + n1 += 2; /* 1 byte more for Null terminator, 1 byte for '/' if needed */ + n2 = n1; n3 = n1; n4 = n1; + n1 += strlen(data->first.scpdurl); + n2 += strlen(data->first.controlurl); + n3 += strlen(data->CIF.controlurl); + n4 += strlen(data->IPv6FC.controlurl); + + urls->ipcondescURL = (char *)malloc(n1); + urls->controlURL = (char *)malloc(n2); + urls->controlURL_CIF = (char *)malloc(n3); + urls->controlURL_6FC = (char *)malloc(n4); + /* maintenant on chope la desc du WANIPConnection */ + if(data->urlbase[0] != '\0') + strncpy(urls->ipcondescURL, data->urlbase, n1); + else + strncpy(urls->ipcondescURL, descURL, n1); + p = strchr(urls->ipcondescURL+7, '/'); + if(p) p[0] = '\0'; + strncpy(urls->controlURL, urls->ipcondescURL, n2); + strncpy(urls->controlURL_CIF, urls->ipcondescURL, n3); + strncpy(urls->controlURL_6FC, urls->ipcondescURL, n4); + + url_cpy_or_cat(urls->ipcondescURL, data->first.scpdurl, n1); + + url_cpy_or_cat(urls->controlURL, data->first.controlurl, n2); + + url_cpy_or_cat(urls->controlURL_CIF, data->CIF.controlurl, n3); + + url_cpy_or_cat(urls->controlURL_6FC, data->IPv6FC.controlurl, n4); + +#ifdef DEBUG + printf("urls->ipcondescURL='%s' %u n1=%d\n", urls->ipcondescURL, + (unsigned)strlen(urls->ipcondescURL), n1); + printf("urls->controlURL='%s' %u n2=%d\n", urls->controlURL, + (unsigned)strlen(urls->controlURL), n2); + printf("urls->controlURL_CIF='%s' %u n3=%d\n", urls->controlURL_CIF, + (unsigned)strlen(urls->controlURL_CIF), n3); + printf("urls->controlURL_6FC='%s' %u n4=%d\n", urls->controlURL_6FC, + (unsigned)strlen(urls->controlURL_6FC), n4); +#endif +} + +LIBSPEC void +FreeUPNPUrls(struct UPNPUrls * urls) +{ + if(!urls) + return; + free(urls->controlURL); + urls->controlURL = 0; + free(urls->ipcondescURL); + urls->ipcondescURL = 0; + free(urls->controlURL_CIF); + urls->controlURL_CIF = 0; + free(urls->controlURL_6FC); + urls->controlURL_6FC = 0; +} + +int +UPNPIGD_IsConnected(struct UPNPUrls * urls, struct IGDdatas * data) +{ + char status[64]; + unsigned int uptime; + status[0] = '\0'; + UPNP_GetStatusInfo(urls->controlURL, data->first.servicetype, + status, &uptime, NULL); + if(0 == strcmp("Connected", status)) + { + return 1; + } + else + return 0; +} + + +/* UPNP_GetValidIGD() : + * return values : + * 0 = NO IGD found + * 1 = A valid connected IGD has been found + * 2 = A valid IGD has been found but it reported as + * not connected + * 3 = an UPnP device has been found but was not recognized as an IGD + * + * In any non zero return case, the urls and data structures + * passed as parameters are set. Donc forget to call FreeUPNPUrls(urls) to + * free allocated memory. + */ +LIBSPEC int +UPNP_GetValidIGD(struct UPNPDev * devlist, + struct UPNPUrls * urls, + struct IGDdatas * data, + char * lanaddr, int lanaddrlen) +{ + char * descXML; + int descXMLsize = 0; + struct UPNPDev * dev; + int ndev = 0; + int state; /* state 1 : IGD connected. State 2 : IGD. State 3 : anything */ + if(!devlist) + { +#ifdef DEBUG + printf("Empty devlist\n"); +#endif + return 0; + } + for(state = 1; state <= 3; state++) + { + for(dev = devlist; dev; dev = dev->pNext) + { + /* we should choose an internet gateway device. + * with st == urn:schemas-upnp-org:device:InternetGatewayDevice:1 */ + descXML = miniwget_getaddr(dev->descURL, &descXMLsize, + lanaddr, lanaddrlen); + if(descXML) + { + ndev++; + memset(data, 0, sizeof(struct IGDdatas)); + memset(urls, 0, sizeof(struct UPNPUrls)); + parserootdesc(descXML, descXMLsize, data); + free(descXML); + descXML = NULL; + if(0==strcmp(data->CIF.servicetype, + "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1") + || state >= 3 ) + { + GetUPNPUrls(urls, data, dev->descURL); + +#ifdef DEBUG + printf("UPNPIGD_IsConnected(%s) = %d\n", + urls->controlURL, + UPNPIGD_IsConnected(urls, data)); +#endif + if((state >= 2) || UPNPIGD_IsConnected(urls, data)) + return state; + FreeUPNPUrls(urls); + if(data->second.servicetype[0] != '\0') { +#ifdef DEBUG + printf("We tried %s, now we try %s !\n", + data->first.servicetype, data->second.servicetype); +#endif + /* swaping WANPPPConnection and WANIPConnection ! */ + memcpy(&data->tmp, &data->first, sizeof(struct IGDdatas_service)); + memcpy(&data->first, &data->second, sizeof(struct IGDdatas_service)); + memcpy(&data->second, &data->tmp, sizeof(struct IGDdatas_service)); + GetUPNPUrls(urls, data, dev->descURL); +#ifdef DEBUG + printf("UPNPIGD_IsConnected(%s) = %d\n", + urls->controlURL, + UPNPIGD_IsConnected(urls, data)); +#endif + if((state >= 2) || UPNPIGD_IsConnected(urls, data)) + return state; + FreeUPNPUrls(urls); + } + } + memset(data, 0, sizeof(struct IGDdatas)); + } +#ifdef DEBUG + else + { + printf("error getting XML description %s\n", dev->descURL); + } +#endif + } + } + return 0; +} + +/* UPNP_GetIGDFromUrl() + * Used when skipping the discovery process. + * return value : + * 0 - Not ok + * 1 - OK */ +int +UPNP_GetIGDFromUrl(const char * rootdescurl, + struct UPNPUrls * urls, + struct IGDdatas * data, + char * lanaddr, int lanaddrlen) +{ + char * descXML; + int descXMLsize = 0; + descXML = miniwget_getaddr(rootdescurl, &descXMLsize, + lanaddr, lanaddrlen); + if(descXML) { + memset(data, 0, sizeof(struct IGDdatas)); + memset(urls, 0, sizeof(struct UPNPUrls)); + parserootdesc(descXML, descXMLsize, data); + free(descXML); + descXML = NULL; + GetUPNPUrls(urls, data, rootdescurl); + return 1; + } else { + return 0; + } +} + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/miniupnpc.h b/3rdParty/LibMiniUPnPc/src/miniupnpc/miniupnpc.h new file mode 100644 index 0000000..50df017 --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/miniupnpc.h @@ -0,0 +1,121 @@ +/* $Id: miniupnpc.h,v 1.23 2011/04/11 08:21:46 nanard Exp $ */ +/* Project: miniupnp + * http://miniupnp.free.fr/ + * Author: Thomas Bernard + * Copyright (c) 2005-2011 Thomas Bernard + * This software is subjects to the conditions detailed + * in the LICENCE file provided within this distribution */ +#ifndef __MINIUPNPC_H__ +#define __MINIUPNPC_H__ + +#include "declspec.h" +#include "igd_desc_parse.h" + +/* error codes : */ +#define UPNPDISCOVER_SUCCESS (0) +#define UPNPDISCOVER_UNKNOWN_ERROR (-1) +#define UPNPDISCOVER_SOCKET_ERROR (-101) +#define UPNPDISCOVER_MEMORY_ERROR (-102) + +#ifdef __cplusplus +extern "C" { +#endif + +/* Structures definitions : */ +struct UPNParg { const char * elt; const char * val; }; + +char * +simpleUPnPcommand(int, const char *, const char *, + const char *, struct UPNParg *, + int *); + +struct UPNPDev { + struct UPNPDev * pNext; + char * descURL; + char * st; + char buffer[2]; +}; + +/* upnpDiscover() + * discover UPnP devices on the network. + * The discovered devices are returned as a chained list. + * It is up to the caller to free the list with freeUPNPDevlist(). + * delay (in millisecond) is the maximum time for waiting any device + * response. + * If available, device list will be obtained from MiniSSDPd. + * Default path for minissdpd socket will be used if minissdpdsock argument + * is NULL. + * If multicastif is not NULL, it will be used instead of the default + * multicast interface for sending SSDP discover packets. + * If sameport is not null, SSDP packets will be sent from the source port + * 1900 (same as destination port) otherwise system assign a source port. */ +LIBSPEC struct UPNPDev * +upnpDiscover(int delay, const char * multicastif, + const char * minissdpdsock, int sameport, + int ipv6, + int * error); +/* freeUPNPDevlist() + * free list returned by upnpDiscover() */ +LIBSPEC void freeUPNPDevlist(struct UPNPDev * devlist); + +/* parserootdesc() : + * parse root XML description of a UPnP device and fill the IGDdatas + * structure. */ +LIBSPEC void parserootdesc(const char *, int, struct IGDdatas *); + +/* structure used to get fast access to urls + * controlURL: controlURL of the WANIPConnection + * ipcondescURL: url of the description of the WANIPConnection + * controlURL_CIF: controlURL of the WANCommonInterfaceConfig + * controlURL_6FC: controlURL of the WANIPv6FirewallControl + */ +struct UPNPUrls { + char * controlURL; + char * ipcondescURL; + char * controlURL_CIF; + char * controlURL_6FC; +}; + +/* UPNP_GetValidIGD() : + * return values : + * 0 = NO IGD found + * 1 = A valid connected IGD has been found + * 2 = A valid IGD has been found but it reported as + * not connected + * 3 = an UPnP device has been found but was not recognized as an IGD + * + * In any non zero return case, the urls and data structures + * passed as parameters are set. Donc forget to call FreeUPNPUrls(urls) to + * free allocated memory. + */ +LIBSPEC int +UPNP_GetValidIGD(struct UPNPDev * devlist, + struct UPNPUrls * urls, + struct IGDdatas * data, + char * lanaddr, int lanaddrlen); + +/* UPNP_GetIGDFromUrl() + * Used when skipping the discovery process. + * return value : + * 0 - Not ok + * 1 - OK */ +LIBSPEC int +UPNP_GetIGDFromUrl(const char * rootdescurl, + struct UPNPUrls * urls, + struct IGDdatas * data, + char * lanaddr, int lanaddrlen); + +LIBSPEC void GetUPNPUrls(struct UPNPUrls *, struct IGDdatas *, const char *); + +LIBSPEC void FreeUPNPUrls(struct UPNPUrls *); + +/* return 0 or 1 */ +LIBSPEC int UPNPIGD_IsConnected(struct UPNPUrls *, struct IGDdatas *); + + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/miniupnpctypes.h b/3rdParty/LibMiniUPnPc/src/miniupnpc/miniupnpctypes.h new file mode 100644 index 0000000..86081c3 --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/miniupnpctypes.h @@ -0,0 +1,19 @@ +/* $Id: miniupnpctypes.h,v 1.1 2011/02/15 11:10:40 nanard Exp $ */ +/* Miniupnp project : http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org + * Author : Thomas Bernard + * Copyright (c) 2011 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided within this distribution */ +#ifndef __MINIUPNPCTYPES_H__ +#define __MINIUPNPCTYPES_H__ + +#if (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) +#define UNSIGNED_INTEGER unsigned long long +#define STRTOUI strtoull +#else +#define UNSIGNED_INTEGER unsigned int +#define STRTOUI strtoul +#endif + +#endif + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/miniwget.c b/3rdParty/LibMiniUPnPc/src/miniupnpc/miniwget.c new file mode 100644 index 0000000..87f6155 --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/miniwget.c @@ -0,0 +1,522 @@ +/* $Id: miniwget.c,v 1.52 2011/06/17 22:59:42 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2005-2011 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#ifdef WIN32 +#include <winsock2.h> +#include <ws2tcpip.h> +#include <io.h> +#define MAXHOSTNAMELEN 64 +#define MIN(x,y) (((x)<(y))?(x):(y)) +#define snprintf _snprintf +#define socklen_t int +#ifndef strncasecmp +#if defined(_MSC_VER) && (_MSC_VER >= 1400) +#define strncasecmp _memicmp +#else /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ +#define strncasecmp memicmp +#endif /* defined(_MSC_VER) && (_MSC_VER >= 1400) */ +#endif /* #ifndef strncasecmp */ +#else /* #ifdef WIN32 */ +#include <unistd.h> +#include <sys/param.h> +#if defined(__amigaos__) && !defined(__amigaos4__) +#define socklen_t int +#else /* #if defined(__amigaos__) && !defined(__amigaos4__) */ +#include <sys/select.h> +#endif /* #else defined(__amigaos__) && !defined(__amigaos4__) */ +#include <sys/socket.h> +#include <arpa/inet.h> +#include <netdb.h> +#define closesocket close +/* defining MINIUPNPC_IGNORE_EINTR enable the ignore of interruptions + * during the connect() call */ +#define MINIUPNPC_IGNORE_EINTR +#endif /* #else WIN32 */ +#if defined(__sun) || defined(sun) +#define MIN(x,y) (((x)<(y))?(x):(y)) +#endif + +#include "miniupnpcstrings.h" +#include "miniwget.h" +#include "connecthostport.h" +#include "receivedata.h" + +/* + * Read a HTTP response from a socket. + * Process Content-Length and Transfer-encoding headers. + * return a pointer to the content buffer, which length is saved + * to the length parameter. + */ +void * +getHTTPResponse(int s, int * size) +{ + char buf[2048]; + int n; + int endofheaders = 0; + int chunked = 0; + int content_length = -1; + unsigned int chunksize = 0; + unsigned int bytestocopy = 0; + /* buffers : */ + char * header_buf; + int header_buf_len = 2048; + int header_buf_used = 0; + char * content_buf; + int content_buf_len = 2048; + int content_buf_used = 0; + char chunksize_buf[32]; + int chunksize_buf_index; + + header_buf = malloc(header_buf_len); + content_buf = malloc(content_buf_len); + chunksize_buf[0] = '\0'; + chunksize_buf_index = 0; + + while((n = receivedata(s, buf, 2048, 5000)) > 0) + { + if(endofheaders == 0) + { + int i; + int linestart=0; + int colon=0; + int valuestart=0; + if(header_buf_used + n > header_buf_len) { + header_buf = realloc(header_buf, header_buf_used + n); + header_buf_len = header_buf_used + n; + } + memcpy(header_buf + header_buf_used, buf, n); + header_buf_used += n; + /* search for CR LF CR LF (end of headers) + * recognize also LF LF */ + i = 0; + while(i < (header_buf_used-1) && (endofheaders == 0)) { + if(header_buf[i] == '\r') { + i++; + if(header_buf[i] == '\n') { + i++; + if(i < header_buf_used && header_buf[i] == '\r') { + i++; + if(i < header_buf_used && header_buf[i] == '\n') { + endofheaders = i+1; + } + } + } + } else if(header_buf[i] == '\n') { + i++; + if(header_buf[i] == '\n') { + endofheaders = i+1; + } + } + i++; + } + if(endofheaders == 0) + continue; + /* parse header lines */ + for(i = 0; i < endofheaders - 1; i++) { + if(colon <= linestart && header_buf[i]==':') + { + colon = i; + while(i < (endofheaders-1) + && (header_buf[i+1] == ' ' || header_buf[i+1] == '\t')) + i++; + valuestart = i + 1; + } + /* detecting end of line */ + else if(header_buf[i]=='\r' || header_buf[i]=='\n') + { + if(colon > linestart && valuestart > colon) + { +#ifdef DEBUG + printf("header='%.*s', value='%.*s'\n", + colon-linestart, header_buf+linestart, + i-valuestart, header_buf+valuestart); +#endif + if(0==strncasecmp(header_buf+linestart, "content-length", colon-linestart)) + { + content_length = atoi(header_buf+valuestart); +#ifdef DEBUG + printf("Content-Length: %d\n", content_length); +#endif + } + else if(0==strncasecmp(header_buf+linestart, "transfer-encoding", colon-linestart) + && 0==strncasecmp(header_buf+valuestart, "chunked", 7)) + { +#ifdef DEBUG + printf("chunked transfer-encoding!\n"); +#endif + chunked = 1; + } + } + while(header_buf[i]=='\r' || header_buf[i] == '\n') + i++; + linestart = i; + colon = linestart; + valuestart = 0; + } + } + /* copy the remaining of the received data back to buf */ + n = header_buf_used - endofheaders; + memcpy(buf, header_buf + endofheaders, n); + /* if(headers) */ + } + if(endofheaders) + { + /* content */ + if(chunked) + { + int i = 0; + while(i < n) + { + if(chunksize == 0) + { + /* reading chunk size */ + if(chunksize_buf_index == 0) { + /* skipping any leading CR LF */ + if(i<n && buf[i] == '\r') i++; + if(i<n && buf[i] == '\n') i++; + } + while(i<n && isxdigit(buf[i]) + && chunksize_buf_index < (sizeof(chunksize_buf)-1)) + { + chunksize_buf[chunksize_buf_index++] = buf[i]; + chunksize_buf[chunksize_buf_index] = '\0'; + i++; + } + while(i<n && buf[i] != '\r' && buf[i] != '\n') + i++; /* discarding chunk-extension */ + if(i<n && buf[i] == '\r') i++; + if(i<n && buf[i] == '\n') { + int j; + for(j = 0; j < chunksize_buf_index; j++) { + if(chunksize_buf[j] >= '0' + && chunksize_buf[j] <= '9') + chunksize = (chunksize << 4) + (chunksize_buf[j] - '0'); + else + chunksize = (chunksize << 4) + ((chunksize_buf[j] | 32) - 'a' + 10); + } + chunksize_buf[0] = '\0'; + chunksize_buf_index = 0; + i++; + } else { + /* not finished to get chunksize */ + continue; + } +#ifdef DEBUG + printf("chunksize = %u (%x)\n", chunksize, chunksize); +#endif + if(chunksize == 0) + { +#ifdef DEBUG + printf("end of HTTP content - %d %d\n", i, n); + /*printf("'%.*s'\n", n-i, buf+i);*/ +#endif + goto end_of_stream; + } + } + bytestocopy = ((int)chunksize < n - i)?chunksize:(n - i); + if((int)(content_buf_used + bytestocopy) > content_buf_len) + { + if(content_length >= content_buf_used + (int)bytestocopy) { + content_buf_len = content_length; + } else { + content_buf_len = content_buf_used + (int)bytestocopy; + } + content_buf = (char *)realloc((void *)content_buf, + content_buf_len); + } + memcpy(content_buf + content_buf_used, buf + i, bytestocopy); + content_buf_used += bytestocopy; + i += bytestocopy; + chunksize -= bytestocopy; + } + } + else + { + /* not chunked */ + if(content_length > 0 + && (content_buf_used + n) > content_length) { + /* skipping additional bytes */ + n = content_length - content_buf_used; + } + if(content_buf_used + n > content_buf_len) + { + if(content_length >= content_buf_used + n) { + content_buf_len = content_length; + } else { + content_buf_len = content_buf_used + n; + } + content_buf = (char *)realloc((void *)content_buf, + content_buf_len); + } + memcpy(content_buf + content_buf_used, buf, n); + content_buf_used += n; + } + } + /* use the Content-Length header value if available */ + if(content_length > 0 && content_buf_used >= content_length) + { +#ifdef DEBUG + printf("End of HTTP content\n"); +#endif + break; + } + } +end_of_stream: + free(header_buf); header_buf = NULL; + *size = content_buf_used; + if(content_buf_used == 0) + { + free(content_buf); + content_buf = NULL; + } + return content_buf; +} + +/* miniwget3() : + * do all the work. + * Return NULL if something failed. */ +static void * +miniwget3(const char * url, const char * host, + unsigned short port, const char * path, + int * size, char * addr_str, int addr_str_len, + const char * httpversion) +{ + char buf[2048]; + int s; + int n; + int len; + int sent; + void * content; + + *size = 0; + s = connecthostport(host, port); + if(s < 0) + return NULL; + + /* get address for caller ! */ + if(addr_str) + { + struct sockaddr_storage saddr; + socklen_t saddrlen; + + saddrlen = sizeof(saddr); + if(getsockname(s, (struct sockaddr *)&saddr, &saddrlen) < 0) + { + perror("getsockname"); + } + else + { +#if defined(__amigaos__) && !defined(__amigaos4__) + /* using INT WINAPI WSAAddressToStringA(LPSOCKADDR, DWORD, LPWSAPROTOCOL_INFOA, LPSTR, LPDWORD); + * But his function make a string with the port : nn.nn.nn.nn:port */ +/* if(WSAAddressToStringA((SOCKADDR *)&saddr, sizeof(saddr), + NULL, addr_str, (DWORD *)&addr_str_len)) + { + printf("WSAAddressToStringA() failed : %d\n", WSAGetLastError()); + }*/ + /* the following code is only compatible with ip v4 addresses */ + strncpy(addr_str, inet_ntoa(((struct sockaddr_in *)&saddr)->sin_addr), addr_str_len); +#else +#if 0 + if(saddr.sa_family == AF_INET6) { + inet_ntop(AF_INET6, + &(((struct sockaddr_in6 *)&saddr)->sin6_addr), + addr_str, addr_str_len); + } else { + inet_ntop(AF_INET, + &(((struct sockaddr_in *)&saddr)->sin_addr), + addr_str, addr_str_len); + } +#endif + /* getnameinfo return ip v6 address with the scope identifier + * such as : 2a01:e35:8b2b:7330::%4281128194 */ + n = getnameinfo((const struct sockaddr *)&saddr, saddrlen, + addr_str, addr_str_len, + NULL, 0, + NI_NUMERICHOST | NI_NUMERICSERV); + if(n != 0) { +#ifdef WIN32 + fprintf(stderr, "getnameinfo() failed : %d\n", n); +#else + fprintf(stderr, "getnameinfo() failed : %s\n", gai_strerror(n)); +#endif + } +#endif + } +#ifdef DEBUG + printf("address miniwget : %s\n", addr_str); +#endif + } + + len = snprintf(buf, sizeof(buf), + "GET %s HTTP/%s\r\n" + "Host: %s:%d\r\n" + "Connection: Close\r\n" + "User-Agent: " OS_STRING ", UPnP/1.0, MiniUPnPc/" MINIUPNPC_VERSION_STRING "\r\n" + + "\r\n", + path, httpversion, host, port); + sent = 0; + /* sending the HTTP request */ + while(sent < len) + { + n = send(s, buf+sent, len-sent, 0); + if(n < 0) + { + perror("send"); + closesocket(s); + return NULL; + } + else + { + sent += n; + } + } + content = getHTTPResponse(s, size); + closesocket(s); + return content; +} + +/* miniwget2() : + * Call miniwget3(); retry with HTTP/1.1 if 1.0 fails. */ +static void * +miniwget2(const char * url, const char * host, + unsigned short port, const char * path, + int * size, char * addr_str, int addr_str_len) +{ + char * respbuffer; + + respbuffer = miniwget3(url, host, port, path, size, addr_str, addr_str_len, "1.1"); +/* + respbuffer = miniwget3(url, host, port, path, size, addr_str, addr_str_len, "1.0"); + if (*size == 0) + { +#ifdef DEBUG + printf("Retrying with HTTP/1.1\n"); +#endif + free(respbuffer); + respbuffer = miniwget3(url, host, port, path, size, addr_str, addr_str_len, "1.1"); + } +*/ + return respbuffer; +} + + + + +/* parseURL() + * arguments : + * url : source string not modified + * hostname : hostname destination string (size of MAXHOSTNAMELEN+1) + * port : port (destination) + * path : pointer to the path part of the URL + * + * Return values : + * 0 - Failure + * 1 - Success */ +int parseURL(const char * url, char * hostname, unsigned short * port, char * * path) +{ + char * p1, *p2, *p3; + if(!url) + return 0; + p1 = strstr(url, "://"); + if(!p1) + return 0; + p1 += 3; + if( (url[0]!='h') || (url[1]!='t') + ||(url[2]!='t') || (url[3]!='p')) + return 0; + memset(hostname, 0, MAXHOSTNAMELEN + 1); + if(*p1 == '[') + { + /* IP v6 : http://[2a00:1450:8002::6a]/path/abc */ + p2 = strchr(p1, ']'); + p3 = strchr(p1, '/'); + if(p2 && p3) + { + p2++; + strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1))); + if(*p2 == ':') + { + *port = 0; + p2++; + while( (*p2 >= '0') && (*p2 <= '9')) + { + *port *= 10; + *port += (unsigned short)(*p2 - '0'); + p2++; + } + } + else + { + *port = 80; + } + *path = p3; + return 1; + } + } + p2 = strchr(p1, ':'); + p3 = strchr(p1, '/'); + if(!p3) + return 0; + if(!p2 || (p2>p3)) + { + strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p3-p1))); + *port = 80; + } + else + { + strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1))); + *port = 0; + p2++; + while( (*p2 >= '0') && (*p2 <= '9')) + { + *port *= 10; + *port += (unsigned short)(*p2 - '0'); + p2++; + } + } + *path = p3; + return 1; +} + +void * miniwget(const char * url, int * size) +{ + unsigned short port; + char * path; + /* protocol://host:port/chemin */ + char hostname[MAXHOSTNAMELEN+1]; + *size = 0; + if(!parseURL(url, hostname, &port, &path)) + return NULL; +#ifdef DEBUG + printf("parsed url : hostname='%s' port=%hu path='%s'\n", hostname, port, path); +#endif + return miniwget2(url, hostname, port, path, size, 0, 0); +} + +void * miniwget_getaddr(const char * url, int * size, char * addr, int addrlen) +{ + unsigned short port; + char * path; + /* protocol://host:port/chemin */ + char hostname[MAXHOSTNAMELEN+1]; + *size = 0; + if(addr) + addr[0] = '\0'; + if(!parseURL(url, hostname, &port, &path)) + return NULL; +#ifdef DEBUG + printf("parsed url : hostname='%s' port=%hu path='%s'\n", hostname, port, path); +#endif + return miniwget2(url, hostname, port, path, size, addr, addrlen); +} + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/miniwget.h b/3rdParty/LibMiniUPnPc/src/miniupnpc/miniwget.h new file mode 100644 index 0000000..8314b40 --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/miniwget.h @@ -0,0 +1,30 @@ +/* $Id: miniwget.h,v 1.6 2010/12/09 16:11:33 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2005 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#ifndef __MINIWGET_H__ +#define __MINIWGET_H__ + +#include "declspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +LIBSPEC void * getHTTPResponse(int s, int * size); + +LIBSPEC void * miniwget(const char *, int *); + +LIBSPEC void * miniwget_getaddr(const char *, int *, char *, int); + +int parseURL(const char *, char *, unsigned short *, char * *); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/minixml.c b/3rdParty/LibMiniUPnPc/src/miniupnpc/minixml.c new file mode 100644 index 0000000..8b5594c --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/minixml.c @@ -0,0 +1,216 @@ +/* $Id: minixml.c,v 1.9 2011/02/07 13:44:57 nanard Exp $ */ +/* minixml.c : the minimum size a xml parser can be ! */ +/* Project : miniupnp + * webpage: http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * Author : Thomas Bernard + +Copyright (c) 2005-2011, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ +#include <string.h> +#include "minixml.h" + +/* parseatt : used to parse the argument list + * return 0 (false) in case of success and -1 (true) if the end + * of the xmlbuffer is reached. */ +static int parseatt(struct xmlparser * p) +{ + const char * attname; + int attnamelen; + const char * attvalue; + int attvaluelen; + while(p->xml < p->xmlend) + { + if(*p->xml=='/' || *p->xml=='>') + return 0; + if( !IS_WHITE_SPACE(*p->xml) ) + { + char sep; + attname = p->xml; + attnamelen = 0; + while(*p->xml!='=' && !IS_WHITE_SPACE(*p->xml) ) + { + attnamelen++; p->xml++; + if(p->xml >= p->xmlend) + return -1; + } + while(*(p->xml++) != '=') + { + if(p->xml >= p->xmlend) + return -1; + } + while(IS_WHITE_SPACE(*p->xml)) + { + p->xml++; + if(p->xml >= p->xmlend) + return -1; + } + sep = *p->xml; + if(sep=='\'' || sep=='\"') + { + p->xml++; + if(p->xml >= p->xmlend) + return -1; + attvalue = p->xml; + attvaluelen = 0; + while(*p->xml != sep) + { + attvaluelen++; p->xml++; + if(p->xml >= p->xmlend) + return -1; + } + } + else + { + attvalue = p->xml; + attvaluelen = 0; + while( !IS_WHITE_SPACE(*p->xml) + && *p->xml != '>' && *p->xml != '/') + { + attvaluelen++; p->xml++; + if(p->xml >= p->xmlend) + return -1; + } + } + /*printf("%.*s='%.*s'\n", + attnamelen, attname, attvaluelen, attvalue);*/ + if(p->attfunc) + p->attfunc(p->data, attname, attnamelen, attvalue, attvaluelen); + } + p->xml++; + } + return -1; +} + +/* parseelt parse the xml stream and + * call the callback functions when needed... */ +static void parseelt(struct xmlparser * p) +{ + int i; + const char * elementname; + while(p->xml < (p->xmlend - 1)) + { + if((p->xml)[0]=='<' && (p->xml)[1]!='?') + { + i = 0; elementname = ++p->xml; + while( !IS_WHITE_SPACE(*p->xml) + && (*p->xml!='>') && (*p->xml!='/') + ) + { + i++; p->xml++; + if (p->xml >= p->xmlend) + return; + /* to ignore namespace : */ + if(*p->xml==':') + { + i = 0; + elementname = ++p->xml; + } + } + if(i>0) + { + if(p->starteltfunc) + p->starteltfunc(p->data, elementname, i); + if(parseatt(p)) + return; + if(*p->xml!='/') + { + const char * data; + i = 0; data = ++p->xml; + if (p->xml >= p->xmlend) + return; + while( IS_WHITE_SPACE(*p->xml) ) + { + i++; p->xml++; + if (p->xml >= p->xmlend) + return; + } + if(memcmp(p->xml, "<![CDATA[", 9) == 0) + { + /* CDATA handling */ + p->xml += 9; + data = p->xml; + i = 0; + while(memcmp(p->xml, "]]>", 3) != 0) + { + i++; p->xml++; + if ((p->xml + 3) >= p->xmlend) + return; + } + if(i>0 && p->datafunc) + p->datafunc(p->data, data, i); + while(*p->xml!='<') + { + p->xml++; + if (p->xml >= p->xmlend) + return; + } + } + else + { + while(*p->xml!='<') + { + i++; p->xml++; + if ((p->xml + 1) >= p->xmlend) + return; + } + if(i>0 && p->datafunc && *(p->xml + 1) == '/') + p->datafunc(p->data, data, i); + } + } + } + else if(*p->xml == '/') + { + i = 0; elementname = ++p->xml; + if (p->xml >= p->xmlend) + return; + while((*p->xml != '>')) + { + i++; p->xml++; + if (p->xml >= p->xmlend) + return; + } + if(p->endeltfunc) + p->endeltfunc(p->data, elementname, i); + p->xml++; + } + } + else + { + p->xml++; + } + } +} + +/* the parser must be initialized before calling this function */ +void parsexml(struct xmlparser * parser) +{ + parser->xml = parser->xmlstart; + parser->xmlend = parser->xmlstart + parser->xmlsize; + parseelt(parser); +} + + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/minixml.h b/3rdParty/LibMiniUPnPc/src/miniupnpc/minixml.h new file mode 100644 index 0000000..857c70e --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/minixml.h @@ -0,0 +1,37 @@ +/* $Id: minixml.h,v 1.6 2006/11/30 11:47:21 nanard Exp $ */ +/* minimal xml parser + * + * Project : miniupnp + * Website : http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2005 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#ifndef __MINIXML_H__ +#define __MINIXML_H__ +#define IS_WHITE_SPACE(c) ((c==' ') || (c=='\t') || (c=='\r') || (c=='\n')) + +/* if a callback function pointer is set to NULL, + * the function is not called */ +struct xmlparser { + const char *xmlstart; + const char *xmlend; + const char *xml; /* pointer to current character */ + int xmlsize; + void * data; + void (*starteltfunc) (void *, const char *, int); + void (*endeltfunc) (void *, const char *, int); + void (*datafunc) (void *, const char *, int); + void (*attfunc) (void *, const char *, int, const char *, int); +}; + +/* parsexml() + * the xmlparser structure must be initialized before the call + * the following structure members have to be initialized : + * xmlstart, xmlsize, data, *func + * xml is for internal usage, xmlend is computed automatically */ +void parsexml(struct xmlparser *); + +#endif + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/minixmlvalid.c b/3rdParty/LibMiniUPnPc/src/miniupnpc/minixmlvalid.c new file mode 100644 index 0000000..766211b --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/minixmlvalid.c @@ -0,0 +1,156 @@ +/* $Id: minixmlvalid.c,v 1.4 2011/02/07 13:44:57 nanard Exp $ */ +/* MiniUPnP Project + * http://miniupnp.tuxfamily.org/ or http://miniupnp.free.fr/ + * minixmlvalid.c : + * validation program for the minixml parser + * + * (c) 2006-2011 Thomas Bernard */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "minixml.h" + +/* xml event structure */ +struct event { + enum { ELTSTART, ELTEND, ATT, CHARDATA } type; + const char * data; + int len; +}; + +struct eventlist { + int n; + struct event * events; +}; + +/* compare 2 xml event lists + * return 0 if the two lists are equals */ +int evtlistcmp(struct eventlist * a, struct eventlist * b) +{ + int i; + struct event * ae, * be; + if(a->n != b->n) + { + printf("event number not matching : %d != %d\n", a->n, b->n); + //return 1; + } + for(i=0; i<a->n; i++) + { + ae = a->events + i; + be = b->events + i; + if( (ae->type != be->type) + ||(ae->len != be->len) + ||memcmp(ae->data, be->data, ae->len)) + { + printf("Found a difference : %d '%.*s' != %d '%.*s'\n", + ae->type, ae->len, ae->data, + be->type, be->len, be->data); + return 1; + } + } + return 0; +} + +/* Test data */ +static const char xmldata[] = +"<xmlroot>\n" +" <elt1 att1=\"attvalue1\" att2=\"attvalue2\">" +"character data" +"</elt1> \n \t" +"<elt1b/>" +"<elt1>\n<![CDATA[ <html>stuff !\n ]]> \n</elt1>\n" +"<elt2a> \t<elt2b>chardata1</elt2b><elt2b> chardata2 </elt2b></elt2a>" +"</xmlroot>"; + +static const struct event evtref[] = +{ + {ELTSTART, "xmlroot", 7}, + {ELTSTART, "elt1", 4}, + /* attributes */ + {CHARDATA, "character data", 14}, + {ELTEND, "elt1", 4}, + {ELTSTART, "elt1b", 5}, + {ELTSTART, "elt1", 4}, + {CHARDATA, " <html>stuff !\n ", 16}, + {ELTEND, "elt1", 4}, + {ELTSTART, "elt2a", 5}, + {ELTSTART, "elt2b", 5}, + {CHARDATA, "chardata1", 9}, + {ELTEND, "elt2b", 5}, + {ELTSTART, "elt2b", 5}, + {CHARDATA, " chardata2 ", 11}, + {ELTEND, "elt2b", 5}, + {ELTEND, "elt2a", 5}, + {ELTEND, "xmlroot", 7} +}; + +void startelt(void * data, const char * p, int l) +{ + struct eventlist * evtlist = data; + struct event * evt; + evt = evtlist->events + evtlist->n; + /*printf("startelt : %.*s\n", l, p);*/ + evt->type = ELTSTART; + evt->data = p; + evt->len = l; + evtlist->n++; +} + +void endelt(void * data, const char * p, int l) +{ + struct eventlist * evtlist = data; + struct event * evt; + evt = evtlist->events + evtlist->n; + /*printf("endelt : %.*s\n", l, p);*/ + evt->type = ELTEND; + evt->data = p; + evt->len = l; + evtlist->n++; +} + +void chardata(void * data, const char * p, int l) +{ + struct eventlist * evtlist = data; + struct event * evt; + evt = evtlist->events + evtlist->n; + /*printf("chardata : '%.*s'\n", l, p);*/ + evt->type = CHARDATA; + evt->data = p; + evt->len = l; + evtlist->n++; +} + +int testxmlparser(const char * xml, int size) +{ + int r; + struct eventlist evtlist; + struct eventlist evtlistref; + struct xmlparser parser; + evtlist.n = 0; + evtlist.events = malloc(sizeof(struct event)*100); + memset(&parser, 0, sizeof(parser)); + parser.xmlstart = xml; + parser.xmlsize = size; + parser.data = &evtlist; + parser.starteltfunc = startelt; + parser.endeltfunc = endelt; + parser.datafunc = chardata; + parsexml(&parser); + printf("%d events\n", evtlist.n); + /* compare */ + evtlistref.n = sizeof(evtref)/sizeof(struct event); + evtlistref.events = (struct event *)evtref; + r = evtlistcmp(&evtlistref, &evtlist); + free(evtlist.events); + return r; +} + +int main(int argc, char * * argv) +{ + int r; + r = testxmlparser(xmldata, sizeof(xmldata)-1); + if(r) + printf("minixml validation test failed\n"); + return r; +} + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/portlistingparse.c b/3rdParty/LibMiniUPnPc/src/miniupnpc/portlistingparse.c new file mode 100644 index 0000000..e09e80f --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/portlistingparse.c @@ -0,0 +1,157 @@ +/* $Id: portlistingparse.c,v 1.4 2011/03/18 11:02:17 nanard Exp $ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2011 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ +#include <string.h> +#include <stdlib.h> +#include "portlistingparse.h" +#include "minixml.h" + +/* list of the elements */ +static const struct { + const portMappingElt code; + const char * const str; +} elements[] = { + { PortMappingEntry, "PortMappingEntry"}, + { NewRemoteHost, "NewRemoteHost"}, + { NewExternalPort, "NewExternalPort"}, + { NewProtocol, "NewProtocol"}, + { NewInternalPort, "NewInternalPort"}, + { NewInternalClient, "NewInternalClient"}, + { NewEnabled, "NewEnabled"}, + { NewDescription, "NewDescription"}, + { NewLeaseTime, "NewLeaseTime"}, + { PortMappingEltNone, NULL} +}; + +/* Helper function */ +static UNSIGNED_INTEGER +atoui(const char * p, int l) +{ + UNSIGNED_INTEGER r = 0; + while(l > 0 && *p) + { + if(*p >= '0' && *p <= '9') + r = r*10 + (*p - '0'); + else + break; + p++; + l--; + } + return r; +} + +/* Start element handler */ +static void +startelt(void * d, const char * name, int l) +{ + int i; + struct PortMappingParserData * pdata = (struct PortMappingParserData *)d; + pdata->curelt = PortMappingEltNone; + for(i = 0; elements[i].str; i++) + { + if(memcmp(name, elements[i].str, l) == 0) + { + pdata->curelt = elements[i].code; + break; + } + } + if(pdata->curelt == PortMappingEntry) + { + struct PortMapping * pm; + pm = calloc(1, sizeof(struct PortMapping)); + LIST_INSERT_HEAD( &(pdata->head), pm, entries); + } +} + +/* End element handler */ +static void +endelt(void * d, const char * name, int l) +{ + struct PortMappingParserData * pdata = (struct PortMappingParserData *)d; + pdata->curelt = PortMappingEltNone; +} + +/* Data handler */ +static void +data(void * d, const char * data, int l) +{ + struct PortMapping * pm; + struct PortMappingParserData * pdata = (struct PortMappingParserData *)d; + pm = pdata->head.lh_first; + if(!pm) + return; + if(l > 63) + l = 63; + switch(pdata->curelt) + { + case NewRemoteHost: + memcpy(pm->remoteHost, data, l); + pm->remoteHost[l] = '\0'; + break; + case NewExternalPort: + pm->externalPort = (unsigned short)atoui(data, l); + break; + case NewProtocol: + if(l > 3) + l = 3; + memcpy(pm->protocol, data, l); + pm->protocol[l] = '\0'; + break; + case NewInternalPort: + pm->internalPort = (unsigned short)atoui(data, l); + break; + case NewInternalClient: + memcpy(pm->internalClient, data, l); + pm->internalClient[l] = '\0'; + break; + case NewEnabled: + pm->enabled = (unsigned char)atoui(data, l); + break; + case NewDescription: + memcpy(pm->description, data, l); + pm->description[l] = '\0'; + break; + case NewLeaseTime: + pm->leaseTime = atoui(data, l); + break; + default: + break; + } +} + + +/* Parse the PortMappingList XML document for IGD version 2 + */ +void +ParsePortListing(const char * buffer, int bufsize, + struct PortMappingParserData * pdata) +{ + struct xmlparser parser; + + memset(pdata, 0, sizeof(struct PortMappingParserData)); + LIST_INIT(&(pdata->head)); + /* init xmlparser */ + parser.xmlstart = buffer; + parser.xmlsize = bufsize; + parser.data = pdata; + parser.starteltfunc = startelt; + parser.endeltfunc = endelt; + parser.datafunc = data; + parser.attfunc = 0; + parsexml(&parser); +} + +void +FreePortListing(struct PortMappingParserData * pdata) +{ + struct PortMapping * pm; + while((pm = pdata->head.lh_first) != NULL) + { + LIST_REMOVE(pm, entries); + free(pm); + } +} + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/portlistingparse.h b/3rdParty/LibMiniUPnPc/src/miniupnpc/portlistingparse.h new file mode 100644 index 0000000..1852478 --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/portlistingparse.h @@ -0,0 +1,71 @@ +/* $Id: portlistingparse.h,v 1.4 2011/02/15 23:03:56 nanard Exp $ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2011 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ +#ifndef __PORTLISTINGPARSE_H__ +#define __PORTLISTINGPARSE_H__ + +#include "declspec.h" +/* for the definition of UNSIGNED_INTEGER */ +#include "miniupnpctypes.h" + +#if defined(NO_SYS_QUEUE_H) || defined(WIN32) || defined(__HAIKU__) +#include "bsdqueue.h" +#else +#include <sys/queue.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* sample of PortMappingEntry : + <p:PortMappingEntry> + <p:NewRemoteHost>202.233.2.1</p:NewRemoteHost> + <p:NewExternalPort>2345</p:NewExternalPort> + <p:NewProtocol>TCP</p:NewProtocol> + <p:NewInternalPort>2345</p:NewInternalPort> + <p:NewInternalClient>192.168.1.137</p:NewInternalClient> + <p:NewEnabled>1</p:NewEnabled> + <p:NewDescription>dooom</p:NewDescription> + <p:NewLeaseTime>345</p:NewLeaseTime> + </p:PortMappingEntry> + */ +typedef enum { PortMappingEltNone, + PortMappingEntry, NewRemoteHost, + NewExternalPort, NewProtocol, + NewInternalPort, NewInternalClient, + NewEnabled, NewDescription, + NewLeaseTime } portMappingElt; + +struct PortMapping { + LIST_ENTRY(PortMapping) entries; + UNSIGNED_INTEGER leaseTime; + unsigned short externalPort; + unsigned short internalPort; + char remoteHost[64]; + char internalClient[64]; + char description[64]; + char protocol[4]; + unsigned char enabled; +}; + +struct PortMappingParserData { + LIST_HEAD(portmappinglisthead, PortMapping) head; + portMappingElt curelt; +}; + +LIBSPEC void +ParsePortListing(const char * buffer, int bufsize, + struct PortMappingParserData * pdata); + +LIBSPEC void +FreePortListing(struct PortMappingParserData * pdata); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/receivedata.c b/3rdParty/LibMiniUPnPc/src/miniupnpc/receivedata.c new file mode 100644 index 0000000..a1eadfc --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/receivedata.c @@ -0,0 +1,81 @@ +/* $Id: receivedata.c,v 1.1 2011/04/11 08:21:47 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2011 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. */ + +#include <stdio.h> +#ifdef WIN32 +#include <winsock2.h> +#include <ws2tcpip.h> +#else +#include <unistd.h> +#if defined(__amigaos__) && !defined(__amigaos4__) +#define socklen_t int +#else /* #if defined(__amigaos__) && !defined(__amigaos4__) */ +#include <sys/select.h> +#endif /* #else defined(__amigaos__) && !defined(__amigaos4__) */ +#include <sys/socket.h> +#if !defined(__amigaos__) && !defined(__amigaos4__) +#include <poll.h> +#endif +#include <errno.h> +#define MINIUPNPC_IGNORE_EINTR +#endif + +#ifdef WIN32 +#define PRINT_SOCKET_ERROR(x) printf("Socket error: %s, %d\n", x, WSAGetLastError()); +#else +#define PRINT_SOCKET_ERROR(x) perror(x) +#endif + +#include "receivedata.h" + +int +receivedata(int socket, char * data, int length, int timeout) +{ + int n; +#if !defined(WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) + /* using poll */ + struct pollfd fds[1]; /* for the poll */ +#ifdef MINIUPNPC_IGNORE_EINTR + do { +#endif + fds[0].fd = socket; + fds[0].events = POLLIN; + n = poll(fds, 1, timeout); +#ifdef MINIUPNPC_IGNORE_EINTR + } while(n < 0 && errno == EINTR); +#endif + if(n < 0) { + PRINT_SOCKET_ERROR("poll"); + return -1; + } else if(n == 0) { + /* timeout */ + return 0; + } +#else /* !defined(WIN32) && !defined(__amigaos__) && !defined(__amigaos4__) */ + /* using select under WIN32 and amigaos */ + fd_set socketSet; + TIMEVAL timeval; + FD_ZERO(&socketSet); + FD_SET(socket, &socketSet); + timeval.tv_sec = timeout / 1000; + timeval.tv_usec = (timeout % 1000) * 1000; + n = select(FD_SETSIZE, &socketSet, NULL, NULL, &timeval); + if(n < 0) { + PRINT_SOCKET_ERROR("select"); + return -1; + } else if(n == 0) { + return 0; + } +#endif + n = recv(socket, data, length, 0); + if(n<0) { + PRINT_SOCKET_ERROR("recv"); + } + return n; +} + + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/receivedata.h b/3rdParty/LibMiniUPnPc/src/miniupnpc/receivedata.h new file mode 100644 index 0000000..7a551b9 --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/receivedata.h @@ -0,0 +1,17 @@ +/* $Id: receivedata.h,v 1.1 2011/04/11 08:21:47 nanard Exp $ */ +/* Project: miniupnp + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * Author: Thomas Bernard + * Copyright (c) 2011 Thomas Bernard + * This software is subjects to the conditions detailed + * in the LICENCE file provided within this distribution */ +#ifndef __RECEIVEDATA_H__ +#define __RECEIVEDATA_H__ + +/* Reads data from the specified socket. + * Returns the number of bytes read if successful, zero if no bytes were + * read or if we timed out. Returns negative if there was an error. */ +int receivedata(int socket, char * data, int length, int timeout); + +#endif + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/upnpc.c b/3rdParty/LibMiniUPnPc/src/miniupnpc/upnpc.c new file mode 100644 index 0000000..b136d9d --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/upnpc.c @@ -0,0 +1,683 @@ +/* $Id: upnpc.c,v 1.88 2011/06/17 23:31:01 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2005-2011 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#ifdef WIN32 +#include <winsock2.h> +#define snprintf _snprintf +#endif +#include "miniwget.h" +#include "miniupnpc.h" +#include "upnpcommands.h" +#include "upnperrors.h" + +/* protofix() checks if protocol is "UDP" or "TCP" + * returns NULL if not */ +const char * protofix(const char * proto) +{ + static const char proto_tcp[4] = { 'T', 'C', 'P', 0}; + static const char proto_udp[4] = { 'U', 'D', 'P', 0}; + int i, b; + for(i=0, b=1; i<4; i++) + b = b && ( (proto[i] == proto_tcp[i]) + || (proto[i] == (proto_tcp[i] | 32)) ); + if(b) + return proto_tcp; + for(i=0, b=1; i<4; i++) + b = b && ( (proto[i] == proto_udp[i]) + || (proto[i] == (proto_udp[i] | 32)) ); + if(b) + return proto_udp; + return 0; +} + +static void DisplayInfos(struct UPNPUrls * urls, + struct IGDdatas * data) +{ + char externalIPAddress[40]; + char connectionType[64]; + char status[64]; + char lastconnerr[64]; + unsigned int uptime; + unsigned int brUp, brDown; + time_t timenow, timestarted; + int r; + UPNP_GetConnectionTypeInfo(urls->controlURL, + data->first.servicetype, + connectionType); + if(connectionType[0]) + printf("Connection Type : %s\n", connectionType); + else + printf("GetConnectionTypeInfo failed.\n"); + UPNP_GetStatusInfo(urls->controlURL, data->first.servicetype, + status, &uptime, lastconnerr); + printf("Status : %s, uptime=%us, LastConnectionError : %s\n", + status, uptime, lastconnerr); + timenow = time(NULL); + timestarted = timenow - uptime; + printf(" Time started : %s", ctime(×tarted)); + UPNP_GetLinkLayerMaxBitRates(urls->controlURL_CIF, data->CIF.servicetype, + &brDown, &brUp); + printf("MaxBitRateDown : %u bps", brDown); + if(brDown >= 1000000) { + printf(" (%u.%u Mbps)", brDown / 1000000, (brDown / 100000) % 10); + } else if(brDown >= 1000) { + printf(" (%u Kbps)", brDown / 1000); + } + printf(" MaxBitRateUp %u bps", brUp); + if(brUp >= 1000000) { + printf(" (%u.%u Mbps)", brUp / 1000000, (brUp / 100000) % 10); + } else if(brUp >= 1000) { + printf(" (%u Kbps)", brUp / 1000); + } + printf("\n"); + r = UPNP_GetExternalIPAddress(urls->controlURL, + data->first.servicetype, + externalIPAddress); + if(r != UPNPCOMMAND_SUCCESS) + printf("GetExternalIPAddress() returned %d\n", r); + if(externalIPAddress[0]) + printf("ExternalIPAddress = %s\n", externalIPAddress); + else + printf("GetExternalIPAddress failed.\n"); +} + +static void GetConnectionStatus(struct UPNPUrls * urls, + struct IGDdatas * data) +{ + unsigned int bytessent, bytesreceived, packetsreceived, packetssent; + DisplayInfos(urls, data); + bytessent = UPNP_GetTotalBytesSent(urls->controlURL_CIF, data->CIF.servicetype); + bytesreceived = UPNP_GetTotalBytesReceived(urls->controlURL_CIF, data->CIF.servicetype); + packetssent = UPNP_GetTotalPacketsSent(urls->controlURL_CIF, data->CIF.servicetype); + packetsreceived = UPNP_GetTotalPacketsReceived(urls->controlURL_CIF, data->CIF.servicetype); + printf("Bytes: Sent: %8u\tRecv: %8u\n", bytessent, bytesreceived); + printf("Packets: Sent: %8u\tRecv: %8u\n", packetssent, packetsreceived); +} + +static void ListRedirections(struct UPNPUrls * urls, + struct IGDdatas * data) +{ + int r; + int i = 0; + char index[6]; + char intClient[40]; + char intPort[6]; + char extPort[6]; + char protocol[4]; + char desc[80]; + char enabled[6]; + char rHost[64]; + char duration[16]; + /*unsigned int num=0; + UPNP_GetPortMappingNumberOfEntries(urls->controlURL, data->servicetype, &num); + printf("PortMappingNumberOfEntries : %u\n", num);*/ + do { + snprintf(index, 6, "%d", i); + rHost[0] = '\0'; enabled[0] = '\0'; + duration[0] = '\0'; desc[0] = '\0'; + extPort[0] = '\0'; intPort[0] = '\0'; intClient[0] = '\0'; + r = UPNP_GetGenericPortMappingEntry(urls->controlURL, + data->first.servicetype, + index, + extPort, intClient, intPort, + protocol, desc, enabled, + rHost, duration); + if(r==0) + /* + printf("%02d - %s %s->%s:%s\tenabled=%s leaseDuration=%s\n" + " desc='%s' rHost='%s'\n", + i, protocol, extPort, intClient, intPort, + enabled, duration, + desc, rHost); + */ + printf("%2d %s %5s->%s:%-5s '%s' '%s' %s\n", + i, protocol, extPort, intClient, intPort, + desc, rHost, duration); + else + printf("GetGenericPortMappingEntry() returned %d (%s)\n", + r, strupnperror(r)); + i++; + } while(r==0); +} + +static void NewListRedirections(struct UPNPUrls * urls, + struct IGDdatas * data) +{ + int r; + int i = 0; + struct PortMappingParserData pdata; + struct PortMapping * pm; + + memset(&pdata, 0, sizeof(struct PortMappingParserData)); + r = UPNP_GetListOfPortMappings(urls->controlURL, + data->first.servicetype, + "0", + "65535", + "TCP", + "1000", + &pdata); + if(r == UPNPCOMMAND_SUCCESS) + { + for(pm = pdata.head.lh_first; pm != NULL; pm = pm->entries.le_next) + { + printf("%2d %s %5hu->%s:%-5hu '%s' '%s' %u\n", + i, pm->protocol, pm->externalPort, pm->internalClient, + pm->internalPort, + pm->description, pm->remoteHost, + (unsigned)pm->leaseTime); + i++; + } + FreePortListing(&pdata); + } + else + { + printf("GetListOfPortMappings() returned %d (%s)\n", + r, strupnperror(r)); + } + r = UPNP_GetListOfPortMappings(urls->controlURL, + data->first.servicetype, + "0", + "65535", + "UDP", + "1000", + &pdata); + if(r == UPNPCOMMAND_SUCCESS) + { + for(pm = pdata.head.lh_first; pm != NULL; pm = pm->entries.le_next) + { + printf("%2d %s %5hu->%s:%-5hu '%s' '%s' %u\n", + i, pm->protocol, pm->externalPort, pm->internalClient, + pm->internalPort, + pm->description, pm->remoteHost, + (unsigned)pm->leaseTime); + i++; + } + FreePortListing(&pdata); + } + else + { + printf("GetListOfPortMappings() returned %d (%s)\n", + r, strupnperror(r)); + } +} + +/* Test function + * 1 - get connection type + * 2 - get extenal ip address + * 3 - Add port mapping + * 4 - get this port mapping from the IGD */ +static void SetRedirectAndTest(struct UPNPUrls * urls, + struct IGDdatas * data, + const char * iaddr, + const char * iport, + const char * eport, + const char * proto, + const char * leaseDuration) +{ + char externalIPAddress[40]; + char intClient[40]; + char intPort[6]; + char duration[16]; + int r; + + if(!iaddr || !iport || !eport || !proto) + { + fprintf(stderr, "Wrong arguments\n"); + return; + } + proto = protofix(proto); + if(!proto) + { + fprintf(stderr, "invalid protocol\n"); + return; + } + + UPNP_GetExternalIPAddress(urls->controlURL, + data->first.servicetype, + externalIPAddress); + if(externalIPAddress[0]) + printf("ExternalIPAddress = %s\n", externalIPAddress); + else + printf("GetExternalIPAddress failed.\n"); + + r = UPNP_AddPortMapping(urls->controlURL, data->first.servicetype, + eport, iport, iaddr, 0, proto, 0, leaseDuration); + if(r!=UPNPCOMMAND_SUCCESS) + printf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n", + eport, iport, iaddr, r, strupnperror(r)); + + r = UPNP_GetSpecificPortMappingEntry(urls->controlURL, + data->first.servicetype, + eport, proto, + intClient, intPort, NULL/*desc*/, + NULL/*enabled*/, duration); + if(r!=UPNPCOMMAND_SUCCESS) + printf("GetSpecificPortMappingEntry() failed with code %d (%s)\n", + r, strupnperror(r)); + + if(intClient[0]) { + printf("InternalIP:Port = %s:%s\n", intClient, intPort); + printf("external %s:%s %s is redirected to internal %s:%s (duration=%s)\n", + externalIPAddress, eport, proto, intClient, intPort, duration); + } +} + +static void +RemoveRedirect(struct UPNPUrls * urls, + struct IGDdatas * data, + const char * eport, + const char * proto) +{ + int r; + if(!proto || !eport) + { + fprintf(stderr, "invalid arguments\n"); + return; + } + proto = protofix(proto); + if(!proto) + { + fprintf(stderr, "protocol invalid\n"); + return; + } + r = UPNP_DeletePortMapping(urls->controlURL, data->first.servicetype, eport, proto, 0); + printf("UPNP_DeletePortMapping() returned : %d\n", r); +} + +/* IGD:2, functions for service WANIPv6FirewallControl:1 */ +static void GetFirewallStatus(struct UPNPUrls * urls, struct IGDdatas * data) +{ + unsigned int bytessent, bytesreceived, packetsreceived, packetssent; + int firewallEnabled = 0, inboundPinholeAllowed = 0; + + UPNP_GetFirewallStatus(urls->controlURL_6FC, data->IPv6FC.servicetype, &firewallEnabled, &inboundPinholeAllowed); + printf("FirewallEnabled: %d & Inbound Pinhole Allowed: %d\n", firewallEnabled, inboundPinholeAllowed); + printf("GetFirewallStatus:\n Firewall Enabled: %s\n Inbound Pinhole Allowed: %s\n", (firewallEnabled)? "Yes":"No", (inboundPinholeAllowed)? "Yes":"No"); + + bytessent = UPNP_GetTotalBytesSent(urls->controlURL_CIF, data->CIF.servicetype); + bytesreceived = UPNP_GetTotalBytesReceived(urls->controlURL_CIF, data->CIF.servicetype); + packetssent = UPNP_GetTotalPacketsSent(urls->controlURL_CIF, data->CIF.servicetype); + packetsreceived = UPNP_GetTotalPacketsReceived(urls->controlURL_CIF, data->CIF.servicetype); + printf("Bytes: Sent: %8u\tRecv: %8u\n", bytessent, bytesreceived); + printf("Packets: Sent: %8u\tRecv: %8u\n", packetssent, packetsreceived); +} + +/* Test function + * 1 - Add pinhole + * 2 - Check if pinhole is working from the IGD side */ +static void SetPinholeAndTest(struct UPNPUrls * urls, struct IGDdatas * data, + const char * remoteaddr, const char * eport, + const char * intaddr, const char * iport, + const char * proto, const char * lease_time) +{ + char uniqueID[8]; + //int isWorking = 0; + int r; + + if(!intaddr || !remoteaddr || !iport || !eport || !proto || !lease_time) + { + fprintf(stderr, "Wrong arguments\n"); + return; + } + /*proto = protofix(proto); + if(!proto) + { + fprintf(stderr, "invalid protocol\n"); + return; + }*/ + r = UPNP_AddPinhole(urls->controlURL_6FC, data->IPv6FC.servicetype, remoteaddr, eport, intaddr, iport, proto, lease_time, uniqueID); + if(r!=UPNPCOMMAND_SUCCESS) + printf("AddPinhole([%s]:%s -> [%s]:%s) failed with code %d (%s)\n", + intaddr, iport, remoteaddr, eport, r, strupnperror(r)); + else + { + printf("AddPinhole: ([%s]:%s -> [%s]:%s) / Pinhole ID = %s\n", intaddr, iport, remoteaddr, eport, uniqueID); + /*r = UPNP_CheckPinholeWorking(urls->controlURL_6FC, data->servicetype_6FC, uniqueID, &isWorking); + if(r!=UPNPCOMMAND_SUCCESS) + printf("CheckPinholeWorking() failed with code %d (%s)\n", r, strupnperror(r)); + printf("CheckPinholeWorking: Pinhole ID = %s / IsWorking = %s\n", uniqueID, (isWorking)? "Yes":"No");*/ + } +} + +/* Test function + * 1 - Check if pinhole is working from the IGD side + * 2 - Update pinhole */ +static void GetPinholeAndUpdate(struct UPNPUrls * urls, struct IGDdatas * data, + const char * uniqueID, const char * lease_time) +{ + int isWorking = 0; + int r; + + if(!uniqueID || !lease_time) + { + fprintf(stderr, "Wrong arguments\n"); + return; + } + r = UPNP_CheckPinholeWorking(urls->controlURL_6FC, data->IPv6FC.servicetype, uniqueID, &isWorking); + printf("CheckPinholeWorking: Pinhole ID = %s / IsWorking = %s\n", uniqueID, (isWorking)? "Yes":"No"); + if(r!=UPNPCOMMAND_SUCCESS) + printf("CheckPinholeWorking() failed with code %d (%s)\n", r, strupnperror(r)); + if(isWorking || r==709) + { + r = UPNP_UpdatePinhole(urls->controlURL_6FC, data->IPv6FC.servicetype, uniqueID, lease_time); + printf("UpdatePinhole: Pinhole ID = %s with Lease Time: %s\n", uniqueID, lease_time); + if(r!=UPNPCOMMAND_SUCCESS) + printf("UpdatePinhole: ID (%s) failed with code %d (%s)\n", uniqueID, r, strupnperror(r)); + } +} + +/* Test function + * Get pinhole timeout + */ +static void GetPinholeOutboundTimeout(struct UPNPUrls * urls, struct IGDdatas * data, + const char * remoteaddr, const char * eport, + const char * intaddr, const char * iport, + const char * proto) +{ + int timeout = 0; + int r; + + if(!intaddr || !remoteaddr || !iport || !eport || !proto) + { + fprintf(stderr, "Wrong arguments\n"); + return; + } + + r = UPNP_GetOutboundPinholeTimeout(urls->controlURL_6FC, data->IPv6FC.servicetype, remoteaddr, eport, intaddr, iport, proto, &timeout); + if(r!=UPNPCOMMAND_SUCCESS) + printf("GetOutboundPinholeTimeout([%s]:%s -> [%s]:%s) failed with code %d (%s)\n", + intaddr, iport, remoteaddr, eport, r, strupnperror(r)); + else + printf("GetOutboundPinholeTimeout: ([%s]:%s -> [%s]:%s) / Timeout = %d\n", intaddr, iport, remoteaddr, eport, timeout); +} + +static void +GetPinholePackets(struct UPNPUrls * urls, + struct IGDdatas * data, const char * uniqueID) +{ + int r, pinholePackets = 0; + if(!uniqueID) + { + fprintf(stderr, "invalid arguments\n"); + return; + } + r = UPNP_GetPinholePackets(urls->controlURL_6FC, data->IPv6FC.servicetype, uniqueID, &pinholePackets); + if(r!=UPNPCOMMAND_SUCCESS) + printf("GetPinholePackets() failed with code %d (%s)\n", r, strupnperror(r)); + else + printf("GetPinholePackets: Pinhole ID = %s / PinholePackets = %d\n", uniqueID, pinholePackets); +} + +static void +CheckPinhole(struct UPNPUrls * urls, + struct IGDdatas * data, const char * uniqueID) +{ + int r, isWorking = 0; + if(!uniqueID) + { + fprintf(stderr, "invalid arguments\n"); + return; + } + r = UPNP_CheckPinholeWorking(urls->controlURL_6FC, data->IPv6FC.servicetype, uniqueID, &isWorking); + if(r!=UPNPCOMMAND_SUCCESS) + printf("CheckPinholeWorking() failed with code %d (%s)\n", r, strupnperror(r)); + else + printf("CheckPinholeWorking: Pinhole ID = %s / IsWorking = %s\n", uniqueID, (isWorking)? "Yes":"No"); +} + +static void +RemovePinhole(struct UPNPUrls * urls, + struct IGDdatas * data, const char * uniqueID) +{ + int r; + if(!uniqueID) + { + fprintf(stderr, "invalid arguments\n"); + return; + } + r = UPNP_DeletePinhole(urls->controlURL_6FC, data->IPv6FC.servicetype, uniqueID); + printf("UPNP_DeletePinhole() returned : %d\n", r); +} + + +/* sample upnp client program */ +int main(int argc, char ** argv) +{ + char command = 0; + char ** commandargv = 0; + int commandargc = 0; + struct UPNPDev * devlist = 0; + char lanaddr[64]; /* my ip address on the LAN */ + int i; + const char * rootdescurl = 0; + const char * multicastif = 0; + const char * minissdpdpath = 0; + int retcode = 0; + int error = 0; + int ipv6 = 0; + +#ifdef WIN32 + WSADATA wsaData; + int nResult = WSAStartup(MAKEWORD(2,2), &wsaData); + if(nResult != NO_ERROR) + { + fprintf(stderr, "WSAStartup() failed.\n"); + return -1; + } +#endif + printf("upnpc : miniupnpc library test client. (c) 2006-2011 Thomas Bernard\n"); + printf("Go to http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/\n" + "for more information.\n"); + /* command line processing */ + for(i=1; i<argc; i++) + { + if(argv[i][0] == '-') + { + if(argv[i][1] == 'u') + rootdescurl = argv[++i]; + else if(argv[i][1] == 'm') + multicastif = argv[++i]; + else if(argv[i][1] == 'p') + minissdpdpath = argv[++i]; + else if(argv[i][1] == '6') + ipv6 = 1; + else + { + command = argv[i][1]; + i++; + commandargv = argv + i; + commandargc = argc - i; + break; + } + } + else + { + fprintf(stderr, "option '%s' invalid\n", argv[i]); + } + } + + if(!command || (command == 'a' && commandargc<4) + || (command == 'd' && argc<2) + || (command == 'r' && argc<2) + || (command == 'A' && commandargc<6) + || (command == 'U' && commandargc<2) + || (command == 'D' && commandargc<1)) + { + fprintf(stderr, "Usage :\t%s [options] -a ip port external_port protocol [duration]\n\t\tAdd port redirection\n", argv[0]); + fprintf(stderr, " \t%s [options] -d external_port protocol [port2 protocol2] [...]\n\t\tDelete port redirection\n", argv[0]); + fprintf(stderr, " \t%s [options] -s\n\t\tGet Connection status\n", argv[0]); + fprintf(stderr, " \t%s [options] -l\n\t\tList redirections\n", argv[0]); + fprintf(stderr, " \t%s [options] -L\n\t\tList redirections (using GetListOfPortMappings, IGD v2)\n", argv[0]); + fprintf(stderr, " \t%s [options] -r port1 protocol1 [port2 protocol2] [...]\n\t\tAdd all redirections to the current host\n", argv[0]); + fprintf(stderr, " \t%s [options] -A remote_ip remote_port internal_ip internal_port protocol lease_time\n\t\tAdd Pinhole (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -U uniqueID new_lease_time\n\t\tUpdate Pinhole (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -C uniqueID\n\t\tCheck if Pinhole is Working (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -K uniqueID\n\t\tGet Number of packets going through the rule (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -D uniqueID\n\t\tDelete Pinhole (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -S\n\t\tGet Firewall status (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -G remote_ip remote_port internal_ip internal_port protocol\n\t\tGet Outbound Pinhole Timeout (for IGD:2 only)\n", argv[0]); + fprintf(stderr, " \t%s [options] -P\n\t\tGet Presentation url\n", argv[0]); + fprintf(stderr, "\nprotocol is UDP or TCP\n"); + fprintf(stderr, "Options:\n"); + fprintf(stderr, " -6 : use ip v6 instead of ip v4.\n"); + fprintf(stderr, " -u url : bypass discovery process by providing the XML root description url.\n"); + fprintf(stderr, " -m address/interface : provide ip address (ip v4) or interface name (ip v6) to use for sending SSDP multicast packets.\n"); + fprintf(stderr, " -p path : use this path for MiniSSDPd socket.\n"); + return 1; + } + + if( rootdescurl + || (devlist = upnpDiscover(2000, multicastif, minissdpdpath, + 0/*sameport*/, ipv6, &error))) + { + struct UPNPDev * device; + struct UPNPUrls urls; + struct IGDdatas data; + if(devlist) + { + printf("List of UPNP devices found on the network :\n"); + for(device = devlist; device; device = device->pNext) + { + printf(" desc: %s\n st: %s\n\n", + device->descURL, device->st); + } + } + else + { + printf("upnpDiscover() error code=%d\n", error); + } + i = 1; + if( (rootdescurl && UPNP_GetIGDFromUrl(rootdescurl, &urls, &data, lanaddr, sizeof(lanaddr))) + || (i = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr)))) + { + switch(i) { + case 1: + printf("Found valid IGD : %s\n", urls.controlURL); + break; + case 2: + printf("Found a (not connected?) IGD : %s\n", urls.controlURL); + printf("Trying to continue anyway\n"); + break; + case 3: + printf("UPnP device found. Is it an IGD ? : %s\n", urls.controlURL); + printf("Trying to continue anyway\n"); + break; + default: + printf("Found device (igd ?) : %s\n", urls.controlURL); + printf("Trying to continue anyway\n"); + } + printf("Local LAN ip address : %s\n", lanaddr); + #if 0 + printf("getting \"%s\"\n", urls.ipcondescURL); + descXML = miniwget(urls.ipcondescURL, &descXMLsize); + if(descXML) + { + /*fwrite(descXML, 1, descXMLsize, stdout);*/ + free(descXML); descXML = NULL; + } + #endif + + switch(command) + { + case 'l': + DisplayInfos(&urls, &data); + ListRedirections(&urls, &data); + break; + case 'L': + NewListRedirections(&urls, &data); + break; + case 'a': + SetRedirectAndTest(&urls, &data, + commandargv[0], commandargv[1], + commandargv[2], commandargv[3], + (commandargc > 4)?commandargv[4]:"0"); + break; + case 'd': + for(i=0; i<commandargc; i+=2) + { + RemoveRedirect(&urls, &data, commandargv[i], commandargv[i+1]); + } + break; + case 's': + GetConnectionStatus(&urls, &data); + break; + case 'r': + for(i=0; i<commandargc; i+=2) + { + /*printf("port %s protocol %s\n", argv[i], argv[i+1]);*/ + SetRedirectAndTest(&urls, &data, + lanaddr, commandargv[i], + commandargv[i], commandargv[i+1], "0"); + } + break; + case 'A': + SetPinholeAndTest(&urls, &data, + commandargv[0], commandargv[1], + commandargv[2], commandargv[3], + commandargv[4], commandargv[5]); + break; + case 'U': + GetPinholeAndUpdate(&urls, &data, + commandargv[0], commandargv[1]); + break; + case 'C': + for(i=0; i<commandargc; i++) + { + CheckPinhole(&urls, &data, commandargv[i]); + } + break; + case 'K': + for(i=0; i<commandargc; i++) + { + GetPinholePackets(&urls, &data, commandargv[i]); + } + break; + case 'D': + for(i=0; i<commandargc; i++) + { + RemovePinhole(&urls, &data, commandargv[i]); + } + break; + case 'S': + GetFirewallStatus(&urls, &data); + break; + case 'G': + GetPinholeOutboundTimeout(&urls, &data, + commandargv[0], commandargv[1], + commandargv[2], commandargv[3], + commandargv[4]); + break; + case 'P': + printf("Presentation URL found:\n"); + printf(" %s\n", data.presentationurl); + break; + default: + fprintf(stderr, "Unknown switch -%c\n", command); + retcode = 1; + } + + FreeUPNPUrls(&urls); + } + else + { + fprintf(stderr, "No valid UPNP Internet Gateway Device found.\n"); + retcode = 1; + } + freeUPNPDevlist(devlist); devlist = 0; + } + else + { + fprintf(stderr, "No IGD UPnP Device found on the network !\n"); + retcode = 1; + } + return retcode; +} + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/upnpcommands.c b/3rdParty/LibMiniUPnPc/src/miniupnpc/upnpcommands.c new file mode 100644 index 0000000..1114759 --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/upnpcommands.c @@ -0,0 +1,1097 @@ +/* $Id: upnpcommands.c,v 1.37 2011/06/04 15:56:23 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas Bernard + * Copyright (c) 2005-2011 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided in this distribution. + * */ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "upnpcommands.h" +#include "miniupnpc.h" +#include "portlistingparse.h" + +static UNSIGNED_INTEGER +my_atoui(const char * s) +{ + return s ? ((UNSIGNED_INTEGER)STRTOUI(s, NULL, 0)) : 0; +} + +/* + * */ +LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalBytesSent(const char * controlURL, + const char * servicetype) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + unsigned int r = 0; + char * p; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetTotalBytesSent", 0, &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + /*DisplayNameValueList(buffer, bufsize);*/ + free(buffer); buffer = NULL; + p = GetValueFromNameValueList(&pdata, "NewTotalBytesSent"); + r = my_atoui(p); + ClearNameValueList(&pdata); + return r; +} + +/* + * */ +LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalBytesReceived(const char * controlURL, + const char * servicetype) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + unsigned int r = 0; + char * p; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetTotalBytesReceived", 0, &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + /*DisplayNameValueList(buffer, bufsize);*/ + free(buffer); buffer = NULL; + p = GetValueFromNameValueList(&pdata, "NewTotalBytesReceived"); + r = my_atoui(p); + ClearNameValueList(&pdata); + return r; +} + +/* + * */ +LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalPacketsSent(const char * controlURL, + const char * servicetype) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + unsigned int r = 0; + char * p; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetTotalPacketsSent", 0, &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + /*DisplayNameValueList(buffer, bufsize);*/ + free(buffer); buffer = NULL; + p = GetValueFromNameValueList(&pdata, "NewTotalPacketsSent"); + r = my_atoui(p); + ClearNameValueList(&pdata); + return r; +} + +/* + * */ +LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalPacketsReceived(const char * controlURL, + const char * servicetype) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + unsigned int r = 0; + char * p; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetTotalPacketsReceived", 0, &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + /*DisplayNameValueList(buffer, bufsize);*/ + free(buffer); buffer = NULL; + p = GetValueFromNameValueList(&pdata, "NewTotalPacketsReceived"); + r = my_atoui(p); + ClearNameValueList(&pdata); + return r; +} + +/* UPNP_GetStatusInfo() call the corresponding UPNP method + * returns the current status and uptime */ +LIBSPEC int +UPNP_GetStatusInfo(const char * controlURL, + const char * servicetype, + char * status, + unsigned int * uptime, + char * lastconnerror) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + char * p; + char * up; + char * err; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!status && !uptime) + return UPNPCOMMAND_INVALID_ARGS; + + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetStatusInfo", 0, &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + /*DisplayNameValueList(buffer, bufsize);*/ + free(buffer); buffer = NULL; + up = GetValueFromNameValueList(&pdata, "NewUptime"); + p = GetValueFromNameValueList(&pdata, "NewConnectionStatus"); + err = GetValueFromNameValueList(&pdata, "NewLastConnectionError"); + if(p && up) + ret = UPNPCOMMAND_SUCCESS; + + if(status) { + if(p){ + strncpy(status, p, 64 ); + status[63] = '\0'; + }else + status[0]= '\0'; + } + + if(uptime) { + if(up) + sscanf(up,"%u",uptime); + else + uptime = 0; + } + + if(lastconnerror) { + if(err) { + strncpy(lastconnerror, err, 64 ); + lastconnerror[63] = '\0'; + } else + lastconnerror[0] = '\0'; + } + + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + ClearNameValueList(&pdata); + return ret; +} + +/* UPNP_GetConnectionTypeInfo() call the corresponding UPNP method + * returns the connection type */ +LIBSPEC int +UPNP_GetConnectionTypeInfo(const char * controlURL, + const char * servicetype, + char * connectionType) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + char * p; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!connectionType) + return UPNPCOMMAND_INVALID_ARGS; + + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetConnectionTypeInfo", 0, &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + p = GetValueFromNameValueList(&pdata, "NewConnectionType"); + /*p = GetValueFromNameValueList(&pdata, "NewPossibleConnectionTypes");*/ + /* PossibleConnectionTypes will have several values.... */ + if(p) { + strncpy(connectionType, p, 64 ); + connectionType[63] = '\0'; + ret = UPNPCOMMAND_SUCCESS; + } else + connectionType[0] = '\0'; + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + ClearNameValueList(&pdata); + return ret; +} + +/* UPNP_GetLinkLayerMaxBitRate() call the corresponding UPNP method. + * Returns 2 values: Downloadlink bandwidth and Uplink bandwidth. + * One of the values can be null + * Note : GetLinkLayerMaxBitRates belongs to WANPPPConnection:1 only + * We can use the GetCommonLinkProperties from WANCommonInterfaceConfig:1 */ +LIBSPEC int +UPNP_GetLinkLayerMaxBitRates(const char * controlURL, + const char * servicetype, + unsigned int * bitrateDown, + unsigned int * bitrateUp) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + char * down; + char * up; + char * p; + + if(!bitrateDown && !bitrateUp) + return UPNPCOMMAND_INVALID_ARGS; + + /* shouldn't we use GetCommonLinkProperties ? */ + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetCommonLinkProperties", 0, &bufsize))) { + /*"GetLinkLayerMaxBitRates", 0, &bufsize);*/ + return UPNPCOMMAND_HTTP_ERROR; + } + /*DisplayNameValueList(buffer, bufsize);*/ + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + /*down = GetValueFromNameValueList(&pdata, "NewDownstreamMaxBitRate");*/ + /*up = GetValueFromNameValueList(&pdata, "NewUpstreamMaxBitRate");*/ + down = GetValueFromNameValueList(&pdata, "NewLayer1DownstreamMaxBitRate"); + up = GetValueFromNameValueList(&pdata, "NewLayer1UpstreamMaxBitRate"); + /*GetValueFromNameValueList(&pdata, "NewWANAccessType");*/ + /*GetValueFromNameValueList(&pdata, "NewPhysicalLinkStatus");*/ + if(down && up) + ret = UPNPCOMMAND_SUCCESS; + + if(bitrateDown) { + if(down) + sscanf(down,"%u",bitrateDown); + else + *bitrateDown = 0; + } + + if(bitrateUp) { + if(up) + sscanf(up,"%u",bitrateUp); + else + *bitrateUp = 0; + } + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + ClearNameValueList(&pdata); + return ret; +} + + +/* UPNP_GetExternalIPAddress() call the corresponding UPNP method. + * if the third arg is not null the value is copied to it. + * at least 16 bytes must be available + * + * Return values : + * 0 : SUCCESS + * NON ZERO : ERROR Either an UPnP error code or an unknown error. + * + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 501 Action Failed - See UPnP Device Architecture section on Control. + */ +LIBSPEC int +UPNP_GetExternalIPAddress(const char * controlURL, + const char * servicetype, + char * extIpAdd) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + char * p; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!extIpAdd || !controlURL || !servicetype) + return UPNPCOMMAND_INVALID_ARGS; + + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetExternalIPAddress", 0, &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } + /*DisplayNameValueList(buffer, bufsize);*/ + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + /*printf("external ip = %s\n", GetValueFromNameValueList(&pdata, "NewExternalIPAddress") );*/ + p = GetValueFromNameValueList(&pdata, "NewExternalIPAddress"); + if(p) { + strncpy(extIpAdd, p, 16 ); + extIpAdd[15] = '\0'; + ret = UPNPCOMMAND_SUCCESS; + } else + extIpAdd[0] = '\0'; + + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + + ClearNameValueList(&pdata); + return ret; +} + +LIBSPEC int +UPNP_AddPortMapping(const char * controlURL, const char * servicetype, + const char * extPort, + const char * inPort, + const char * inClient, + const char * desc, + const char * proto, + const char * remoteHost, + const char * leaseDuration) +{ + struct UPNParg * AddPortMappingArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + int ret; + + if(!inPort || !inClient || !proto || !extPort) + return UPNPCOMMAND_INVALID_ARGS; + + AddPortMappingArgs = calloc(9, sizeof(struct UPNParg)); + AddPortMappingArgs[0].elt = "NewRemoteHost"; + AddPortMappingArgs[0].val = remoteHost; + AddPortMappingArgs[1].elt = "NewExternalPort"; + AddPortMappingArgs[1].val = extPort; + AddPortMappingArgs[2].elt = "NewProtocol"; + AddPortMappingArgs[2].val = proto; + AddPortMappingArgs[3].elt = "NewInternalPort"; + AddPortMappingArgs[3].val = inPort; + AddPortMappingArgs[4].elt = "NewInternalClient"; + AddPortMappingArgs[4].val = inClient; + AddPortMappingArgs[5].elt = "NewEnabled"; + AddPortMappingArgs[5].val = "1"; + AddPortMappingArgs[6].elt = "NewPortMappingDescription"; + AddPortMappingArgs[6].val = desc?desc:"libminiupnpc"; + AddPortMappingArgs[7].elt = "NewLeaseDuration"; + AddPortMappingArgs[7].val = leaseDuration?leaseDuration:"0"; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "AddPortMapping", AddPortMappingArgs, + &bufsize))) { + free(AddPortMappingArgs); + return UPNPCOMMAND_HTTP_ERROR; + } + /*DisplayNameValueList(buffer, bufsize);*/ + /*buffer[bufsize] = '\0';*/ + /*puts(buffer);*/ + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) { + /*printf("AddPortMapping errorCode = '%s'\n", resVal); */ + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } else { + ret = UPNPCOMMAND_SUCCESS; + } + ClearNameValueList(&pdata); + free(AddPortMappingArgs); + return ret; +} + +LIBSPEC int +UPNP_DeletePortMapping(const char * controlURL, const char * servicetype, + const char * extPort, const char * proto, + const char * remoteHost) +{ + /*struct NameValueParserData pdata;*/ + struct UPNParg * DeletePortMappingArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + int ret; + + if(!extPort || !proto) + return UPNPCOMMAND_INVALID_ARGS; + + DeletePortMappingArgs = calloc(4, sizeof(struct UPNParg)); + DeletePortMappingArgs[0].elt = "NewRemoteHost"; + DeletePortMappingArgs[0].val = remoteHost; + DeletePortMappingArgs[1].elt = "NewExternalPort"; + DeletePortMappingArgs[1].val = extPort; + DeletePortMappingArgs[2].elt = "NewProtocol"; + DeletePortMappingArgs[2].val = proto; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "DeletePortMapping", + DeletePortMappingArgs, &bufsize))) { + free(DeletePortMappingArgs); + return UPNPCOMMAND_HTTP_ERROR; + } + /*DisplayNameValueList(buffer, bufsize);*/ + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } else { + ret = UPNPCOMMAND_SUCCESS; + } + ClearNameValueList(&pdata); + free(DeletePortMappingArgs); + return ret; +} + +LIBSPEC int +UPNP_GetGenericPortMappingEntry(const char * controlURL, + const char * servicetype, + const char * index, + char * extPort, + char * intClient, + char * intPort, + char * protocol, + char * desc, + char * enabled, + char * rHost, + char * duration) +{ + struct NameValueParserData pdata; + struct UPNParg * GetPortMappingArgs; + char * buffer; + int bufsize; + char * p; + int r = UPNPCOMMAND_UNKNOWN_ERROR; + if(!index) + return UPNPCOMMAND_INVALID_ARGS; + intClient[0] = '\0'; + intPort[0] = '\0'; + GetPortMappingArgs = calloc(2, sizeof(struct UPNParg)); + GetPortMappingArgs[0].elt = "NewPortMappingIndex"; + GetPortMappingArgs[0].val = index; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetGenericPortMappingEntry", + GetPortMappingArgs, &bufsize))) { + free(GetPortMappingArgs); + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + + p = GetValueFromNameValueList(&pdata, "NewRemoteHost"); + if(p && rHost) + { + strncpy(rHost, p, 64); + rHost[63] = '\0'; + } + p = GetValueFromNameValueList(&pdata, "NewExternalPort"); + if(p && extPort) + { + strncpy(extPort, p, 6); + extPort[5] = '\0'; + r = UPNPCOMMAND_SUCCESS; + } + p = GetValueFromNameValueList(&pdata, "NewProtocol"); + if(p && protocol) + { + strncpy(protocol, p, 4); + protocol[3] = '\0'; + } + p = GetValueFromNameValueList(&pdata, "NewInternalClient"); + if(p && intClient) + { + strncpy(intClient, p, 16); + intClient[15] = '\0'; + r = 0; + } + p = GetValueFromNameValueList(&pdata, "NewInternalPort"); + if(p && intPort) + { + strncpy(intPort, p, 6); + intPort[5] = '\0'; + } + p = GetValueFromNameValueList(&pdata, "NewEnabled"); + if(p && enabled) + { + strncpy(enabled, p, 4); + enabled[3] = '\0'; + } + p = GetValueFromNameValueList(&pdata, "NewPortMappingDescription"); + if(p && desc) + { + strncpy(desc, p, 80); + desc[79] = '\0'; + } + p = GetValueFromNameValueList(&pdata, "NewLeaseDuration"); + if(p && duration) + { + strncpy(duration, p, 16); + duration[15] = '\0'; + } + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + r = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &r); + } + ClearNameValueList(&pdata); + free(GetPortMappingArgs); + return r; +} + +LIBSPEC int +UPNP_GetPortMappingNumberOfEntries(const char * controlURL, + const char * servicetype, + unsigned int * numEntries) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + char* p; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetPortMappingNumberOfEntries", 0, + &bufsize))) { + return UPNPCOMMAND_HTTP_ERROR; + } +#ifdef DEBUG + DisplayNameValueList(buffer, bufsize); +#endif + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + + p = GetValueFromNameValueList(&pdata, "NewPortMappingNumberOfEntries"); + if(numEntries && p) { + *numEntries = 0; + sscanf(p, "%u", numEntries); + ret = UPNPCOMMAND_SUCCESS; + } + + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + + ClearNameValueList(&pdata); + return ret; +} + +/* UPNP_GetSpecificPortMappingEntry retrieves an existing port mapping + * the result is returned in the intClient and intPort strings + * please provide 16 and 6 bytes of data */ +LIBSPEC int +UPNP_GetSpecificPortMappingEntry(const char * controlURL, + const char * servicetype, + const char * extPort, + const char * proto, + char * intClient, + char * intPort, + char * desc, + char * enabled, + char * leaseDuration) +{ + struct NameValueParserData pdata; + struct UPNParg * GetPortMappingArgs; + char * buffer; + int bufsize; + char * p; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!intPort || !intClient || !extPort || !proto) + return UPNPCOMMAND_INVALID_ARGS; + + GetPortMappingArgs = calloc(4, sizeof(struct UPNParg)); + GetPortMappingArgs[0].elt = "NewRemoteHost"; + /* TODO : add remote host ? */ + GetPortMappingArgs[1].elt = "NewExternalPort"; + GetPortMappingArgs[1].val = extPort; + GetPortMappingArgs[2].elt = "NewProtocol"; + GetPortMappingArgs[2].val = proto; + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetSpecificPortMappingEntry", + GetPortMappingArgs, &bufsize))) { + free(GetPortMappingArgs); + return UPNPCOMMAND_HTTP_ERROR; + } + /*DisplayNameValueList(buffer, bufsize);*/ + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + + p = GetValueFromNameValueList(&pdata, "NewInternalClient"); + if(p) { + strncpy(intClient, p, 16); + intClient[15] = '\0'; + ret = UPNPCOMMAND_SUCCESS; + } else + intClient[0] = '\0'; + + p = GetValueFromNameValueList(&pdata, "NewInternalPort"); + if(p) { + strncpy(intPort, p, 6); + intPort[5] = '\0'; + } else + intPort[0] = '\0'; + + p = GetValueFromNameValueList(&pdata, "NewEnabled"); + if(p && enabled) { + strncpy(enabled, p, 4); + enabled[3] = '\0'; + } + + p = GetValueFromNameValueList(&pdata, "NewPortMappingDescription"); + if(p && desc) { + strncpy(desc, p, 80); + desc[79] = '\0'; + } + + p = GetValueFromNameValueList(&pdata, "NewLeaseDuration"); + if(p && leaseDuration) + { + strncpy(leaseDuration, p, 16); + leaseDuration[15] = '\0'; + } + + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + + ClearNameValueList(&pdata); + free(GetPortMappingArgs); + return ret; +} + +/* UPNP_GetListOfPortMappings() + * + * Possible UPNP Error codes : + * 606 Action not Authorized + * 730 PortMappingNotFound - no port mapping is found in the specified range. + * 733 InconsistantParameters - NewStartPort and NewEndPort values are not + * consistent. + */ +LIBSPEC int +UPNP_GetListOfPortMappings(const char * controlURL, + const char * servicetype, + const char * startPort, + const char * endPort, + const char * protocol, + const char * numberOfPorts, + struct PortMappingParserData * data) +{ + struct NameValueParserData pdata; + struct UPNParg * GetListOfPortMappingsArgs; + const char * p; + char * buffer; + int bufsize; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!startPort || !endPort || !protocol) + return UPNPCOMMAND_INVALID_ARGS; + + GetListOfPortMappingsArgs = calloc(6, sizeof(struct UPNParg)); + GetListOfPortMappingsArgs[0].elt = "NewStartPort"; + GetListOfPortMappingsArgs[0].val = startPort; + GetListOfPortMappingsArgs[1].elt = "NewEndPort"; + GetListOfPortMappingsArgs[1].val = endPort; + GetListOfPortMappingsArgs[2].elt = "NewProtocol"; + GetListOfPortMappingsArgs[2].val = protocol; + GetListOfPortMappingsArgs[3].elt = "NewManage"; + GetListOfPortMappingsArgs[3].val = "1"; + GetListOfPortMappingsArgs[4].elt = "NewNumberOfPorts"; + GetListOfPortMappingsArgs[4].val = numberOfPorts?numberOfPorts:"1000"; + + if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetListOfPortMappings", + GetListOfPortMappingsArgs, &bufsize))) { + free(GetListOfPortMappingsArgs); + return UPNPCOMMAND_HTTP_ERROR; + } + free(GetListOfPortMappingsArgs); + + /*DisplayNameValueList(buffer, bufsize);*/ + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + + /*p = GetValueFromNameValueList(&pdata, "NewPortListing");*/ + /*if(p) { + printf("NewPortListing : %s\n", p); + }*/ + /*printf("NewPortListing(%d chars) : %s\n", + pdata.portListingLength, pdata.portListing);*/ + if(pdata.portListing) + { + /*struct PortMapping * pm; + int i = 0;*/ + ParsePortListing(pdata.portListing, pdata.portListingLength, + data); + ret = UPNPCOMMAND_SUCCESS; + /* + for(pm = data->head.lh_first; pm != NULL; pm = pm->entries.le_next) + { + printf("%2d %s %5hu->%s:%-5hu '%s' '%s'\n", + i, pm->protocol, pm->externalPort, pm->internalClient, + pm->internalPort, + pm->description, pm->remoteHost); + i++; + } + */ + /*FreePortListing(&data);*/ + } + + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + ClearNameValueList(&pdata); + + //printf("%.*s", bufsize, buffer); + + return ret; +} + +/* IGD:2, functions for service WANIPv6FirewallControl:1 */ +LIBSPEC int +UPNP_GetFirewallStatus(const char * controlURL, + const char * servicetype, + int * firewallEnabled, + int * inboundPinholeAllowed) +{ + struct NameValueParserData pdata; + char * buffer; + int bufsize; + char * fe, *ipa, *p; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!firewallEnabled && !inboundPinholeAllowed) + return UPNPCOMMAND_INVALID_ARGS; + + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetFirewallStatus", 0, &bufsize); + if(!buffer) { + return UPNPCOMMAND_HTTP_ERROR; + } + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + fe = GetValueFromNameValueList(&pdata, "FirewallEnabled"); + ipa = GetValueFromNameValueList(&pdata, "InboundPinholeAllowed"); + if(ipa && fe) + ret = UPNPCOMMAND_SUCCESS; + if(fe) + *firewallEnabled = my_atoui(fe); + /*else + *firewallEnabled = 0;*/ + if(ipa) + *inboundPinholeAllowed = my_atoui(ipa); + /*else + *inboundPinholeAllowed = 0;*/ + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) + { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + ClearNameValueList(&pdata); + return ret; +} + +LIBSPEC int +UPNP_GetOutboundPinholeTimeout(const char * controlURL, const char * servicetype, + const char * remoteHost, + const char * remotePort, + const char * intClient, + const char * intPort, + const char * proto, + int * opTimeout) +{ + struct UPNParg * GetOutboundPinholeTimeoutArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + char * p; + int ret; + + if(!intPort || !intClient || !proto || !remotePort || !remoteHost) + return UPNPCOMMAND_INVALID_ARGS; + + GetOutboundPinholeTimeoutArgs = calloc(6, sizeof(struct UPNParg)); + GetOutboundPinholeTimeoutArgs[0].elt = "RemoteHost"; + GetOutboundPinholeTimeoutArgs[0].val = remoteHost; + GetOutboundPinholeTimeoutArgs[1].elt = "RemotePort"; + GetOutboundPinholeTimeoutArgs[1].val = remotePort; + GetOutboundPinholeTimeoutArgs[2].elt = "Protocol"; + GetOutboundPinholeTimeoutArgs[2].val = proto; + GetOutboundPinholeTimeoutArgs[3].elt = "InternalPort"; + GetOutboundPinholeTimeoutArgs[3].val = intPort; + GetOutboundPinholeTimeoutArgs[4].elt = "InternalClient"; + GetOutboundPinholeTimeoutArgs[4].val = intClient; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetOutboundPinholeTimeout", GetOutboundPinholeTimeoutArgs, &bufsize); + if(!buffer) + return UPNPCOMMAND_HTTP_ERROR; + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) + { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } + else + { + ret = UPNPCOMMAND_SUCCESS; + p = GetValueFromNameValueList(&pdata, "OutboundPinholeTimeout"); + if(p) + *opTimeout = my_atoui(p); + } + ClearNameValueList(&pdata); + free(GetOutboundPinholeTimeoutArgs); + return ret; +} + +LIBSPEC int +UPNP_AddPinhole(const char * controlURL, const char * servicetype, + const char * remoteHost, + const char * remotePort, + const char * intClient, + const char * intPort, + const char * proto, + const char * leaseTime, + char * uniqueID) +{ + struct UPNParg * AddPinholeArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + char * p; + int ret; + + if(!intPort || !intClient || !proto || !remoteHost || !remotePort || !leaseTime) + return UPNPCOMMAND_INVALID_ARGS; + + AddPinholeArgs = calloc(7, sizeof(struct UPNParg)); + // RemoteHost can be wilcarded + if(strncmp(remoteHost, "empty", 5)==0) + { + AddPinholeArgs[0].elt = "RemoteHost"; + AddPinholeArgs[0].val = ""; + } + else + { + AddPinholeArgs[0].elt = "RemoteHost"; + AddPinholeArgs[0].val = remoteHost; + } + AddPinholeArgs[1].elt = "RemotePort"; + AddPinholeArgs[1].val = remotePort; + AddPinholeArgs[2].elt = "Protocol"; + AddPinholeArgs[2].val = proto; + AddPinholeArgs[3].elt = "InternalPort"; + AddPinholeArgs[3].val = intPort; + if(strncmp(intClient, "empty", 5)==0) + { + AddPinholeArgs[4].elt = "InternalClient"; + AddPinholeArgs[4].val = ""; + } + else + { + AddPinholeArgs[4].elt = "InternalClient"; + AddPinholeArgs[4].val = intClient; + } + AddPinholeArgs[5].elt = "LeaseTime"; + AddPinholeArgs[5].val = leaseTime; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "AddPinhole", AddPinholeArgs, &bufsize); + if(!buffer) + return UPNPCOMMAND_HTTP_ERROR; + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + p = GetValueFromNameValueList(&pdata, "UniqueID"); + if(p) + { + strncpy(uniqueID, p, 8); + uniqueID[7] = '\0'; + } + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) + { + //printf("AddPortMapping errorCode = '%s'\n", resVal); + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } + else + { + ret = UPNPCOMMAND_SUCCESS; + } + ClearNameValueList(&pdata); + free(AddPinholeArgs); + return ret; +} + +LIBSPEC int +UPNP_UpdatePinhole(const char * controlURL, const char * servicetype, + const char * uniqueID, + const char * leaseTime) +{ + struct UPNParg * UpdatePinholeArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + int ret; + + if(!uniqueID || !leaseTime) + return UPNPCOMMAND_INVALID_ARGS; + + UpdatePinholeArgs = calloc(3, sizeof(struct UPNParg)); + UpdatePinholeArgs[0].elt = "UniqueID"; + UpdatePinholeArgs[0].val = uniqueID; + UpdatePinholeArgs[1].elt = "NewLeaseTime"; + UpdatePinholeArgs[1].val = leaseTime; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "UpdatePinhole", UpdatePinholeArgs, &bufsize); + if(!buffer) + return UPNPCOMMAND_HTTP_ERROR; + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) + { + /*printf("AddPortMapping errorCode = '%s'\n", resVal); */ + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } + else + { + ret = UPNPCOMMAND_SUCCESS; + } + ClearNameValueList(&pdata); + free(UpdatePinholeArgs); + return ret; +} + +LIBSPEC int +UPNP_DeletePinhole(const char * controlURL, const char * servicetype, const char * uniqueID) +{ + /*struct NameValueParserData pdata;*/ + struct UPNParg * DeletePinholeArgs; + char * buffer; + int bufsize; + struct NameValueParserData pdata; + const char * resVal; + int ret; + + if(!uniqueID) + return UPNPCOMMAND_INVALID_ARGS; + + DeletePinholeArgs = calloc(2, sizeof(struct UPNParg)); + DeletePinholeArgs[0].elt = "UniqueID"; + DeletePinholeArgs[0].val = uniqueID; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "DeletePinhole", DeletePinholeArgs, &bufsize); + if(!buffer) + return UPNPCOMMAND_HTTP_ERROR; + /*DisplayNameValueList(buffer, bufsize);*/ + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + resVal = GetValueFromNameValueList(&pdata, "errorCode"); + if(resVal) + { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(resVal, "%d", &ret); + } + else + { + ret = UPNPCOMMAND_SUCCESS; + } + ClearNameValueList(&pdata); + free(DeletePinholeArgs); + return ret; +} + +LIBSPEC int +UPNP_CheckPinholeWorking(const char * controlURL, const char * servicetype, + const char * uniqueID, int * isWorking) +{ + struct NameValueParserData pdata; + struct UPNParg * CheckPinholeWorkingArgs; + char * buffer; + int bufsize; + char * p; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!uniqueID) + return UPNPCOMMAND_INVALID_ARGS; + + CheckPinholeWorkingArgs = calloc(4, sizeof(struct UPNParg)); + CheckPinholeWorkingArgs[0].elt = "UniqueID"; + CheckPinholeWorkingArgs[0].val = uniqueID; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "CheckPinholeWorking", CheckPinholeWorkingArgs, &bufsize); + if(!buffer) + return UPNPCOMMAND_HTTP_ERROR; + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + + p = GetValueFromNameValueList(&pdata, "IsWorking"); + if(p) + { + *isWorking=my_atoui(p); + ret = UPNPCOMMAND_SUCCESS; + } + else + *isWorking = 0; + + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) + { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + + ClearNameValueList(&pdata); + free(CheckPinholeWorkingArgs); + return ret; +} + +LIBSPEC int +UPNP_GetPinholePackets(const char * controlURL, const char * servicetype, + const char * uniqueID, int * packets) +{ + struct NameValueParserData pdata; + struct UPNParg * GetPinholePacketsArgs; + char * buffer; + int bufsize; + char * p; + int ret = UPNPCOMMAND_UNKNOWN_ERROR; + + if(!uniqueID) + return UPNPCOMMAND_INVALID_ARGS; + + GetPinholePacketsArgs = calloc(4, sizeof(struct UPNParg)); + GetPinholePacketsArgs[0].elt = "UniqueID"; + GetPinholePacketsArgs[0].val = uniqueID; + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetPinholePackets", GetPinholePacketsArgs, &bufsize); + if(!buffer) + return UPNPCOMMAND_HTTP_ERROR; + ParseNameValue(buffer, bufsize, &pdata); + free(buffer); buffer = NULL; + + p = GetValueFromNameValueList(&pdata, "PinholePackets"); + if(p) + { + *packets=my_atoui(p); + ret = UPNPCOMMAND_SUCCESS; + } + + p = GetValueFromNameValueList(&pdata, "errorCode"); + if(p) + { + ret = UPNPCOMMAND_UNKNOWN_ERROR; + sscanf(p, "%d", &ret); + } + + ClearNameValueList(&pdata); + free(GetPinholePacketsArgs); + return ret; +} + + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/upnpcommands.h b/3rdParty/LibMiniUPnPc/src/miniupnpc/upnpcommands.h new file mode 100644 index 0000000..66d95e0 --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/upnpcommands.h @@ -0,0 +1,271 @@ +/* $Id: upnpcommands.h,v 1.23 2011/04/11 09:14:00 nanard Exp $ */ +/* Miniupnp project : http://miniupnp.free.fr/ + * Author : Thomas Bernard + * Copyright (c) 2005-2011 Thomas Bernard + * This software is subject to the conditions detailed in the + * LICENCE file provided within this distribution */ +#ifndef __UPNPCOMMANDS_H__ +#define __UPNPCOMMANDS_H__ + +#include "upnpreplyparse.h" +#include "portlistingparse.h" +#include "declspec.h" +#include "miniupnpctypes.h" + +/* MiniUPnPc return codes : */ +#define UPNPCOMMAND_SUCCESS (0) +#define UPNPCOMMAND_UNKNOWN_ERROR (-1) +#define UPNPCOMMAND_INVALID_ARGS (-2) +#define UPNPCOMMAND_HTTP_ERROR (-3) + +#ifdef __cplusplus +extern "C" { +#endif + +LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalBytesSent(const char * controlURL, + const char * servicetype); + +LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalBytesReceived(const char * controlURL, + const char * servicetype); + +LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalPacketsSent(const char * controlURL, + const char * servicetype); + +LIBSPEC UNSIGNED_INTEGER +UPNP_GetTotalPacketsReceived(const char * controlURL, + const char * servicetype); + +/* UPNP_GetStatusInfo() + * status and lastconnerror are 64 byte buffers + * Return values : + * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR + * or a UPnP Error code */ +LIBSPEC int +UPNP_GetStatusInfo(const char * controlURL, + const char * servicetype, + char * status, + unsigned int * uptime, + char * lastconnerror); + +/* UPNP_GetConnectionTypeInfo() + * argument connectionType is a 64 character buffer + * Return Values : + * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR + * or a UPnP Error code */ +LIBSPEC int +UPNP_GetConnectionTypeInfo(const char * controlURL, + const char * servicetype, + char * connectionType); + +/* UPNP_GetExternalIPAddress() call the corresponding UPNP method. + * if the third arg is not null the value is copied to it. + * at least 16 bytes must be available + * + * Return values : + * 0 : SUCCESS + * NON ZERO : ERROR Either an UPnP error code or an unknown error. + * + * possible UPnP Errors : + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 501 Action Failed - See UPnP Device Architecture section on Control. */ +LIBSPEC int +UPNP_GetExternalIPAddress(const char * controlURL, + const char * servicetype, + char * extIpAdd); + +/* UPNP_GetLinkLayerMaxBitRates() + * call WANCommonInterfaceConfig:1#GetCommonLinkProperties + * + * return values : + * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR + * or a UPnP Error Code. */ +LIBSPEC int +UPNP_GetLinkLayerMaxBitRates(const char* controlURL, + const char* servicetype, + unsigned int * bitrateDown, + unsigned int * bitrateUp); + +/* UPNP_AddPortMapping() + * if desc is NULL, it will be defaulted to "libminiupnpc" + * remoteHost is usually NULL because IGD don't support it. + * + * Return values : + * 0 : SUCCESS + * NON ZERO : ERROR. Either an UPnP error code or an unknown error. + * + * List of possible UPnP errors for AddPortMapping : + * errorCode errorDescription (short) - Description (long) + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 501 Action Failed - See UPnP Device Architecture section on Control. + * 715 WildCardNotPermittedInSrcIP - The source IP address cannot be + * wild-carded + * 716 WildCardNotPermittedInExtPort - The external port cannot be wild-carded + * 718 ConflictInMappingEntry - The port mapping entry specified conflicts + * with a mapping assigned previously to another client + * 724 SamePortValuesRequired - Internal and External port values + * must be the same + * 725 OnlyPermanentLeasesSupported - The NAT implementation only supports + * permanent lease times on port mappings + * 726 RemoteHostOnlySupportsWildcard - RemoteHost must be a wildcard + * and cannot be a specific IP address or DNS name + * 727 ExternalPortOnlySupportsWildcard - ExternalPort must be a wildcard and + * cannot be a specific port value */ +LIBSPEC int +UPNP_AddPortMapping(const char * controlURL, const char * servicetype, + const char * extPort, + const char * inPort, + const char * inClient, + const char * desc, + const char * proto, + const char * remoteHost, + const char * leaseDuration); + +/* UPNP_DeletePortMapping() + * Use same argument values as what was used for AddPortMapping(). + * remoteHost is usually NULL because IGD don't support it. + * Return Values : + * 0 : SUCCESS + * NON ZERO : error. Either an UPnP error code or an undefined error. + * + * List of possible UPnP errors for DeletePortMapping : + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 714 NoSuchEntryInArray - The specified value does not exist in the array */ +LIBSPEC int +UPNP_DeletePortMapping(const char * controlURL, const char * servicetype, + const char * extPort, const char * proto, + const char * remoteHost); + +/* UPNP_GetPortMappingNumberOfEntries() + * not supported by all routers */ +LIBSPEC int +UPNP_GetPortMappingNumberOfEntries(const char* controlURL, + const char* servicetype, + unsigned int * num); + +/* UPNP_GetSpecificPortMappingEntry() + * retrieves an existing port mapping + * params : + * in extPort + * in proto + * out intClient (16 bytes) + * out intPort (6 bytes) + * out desc (80 bytes) + * out enabled (4 bytes) + * out leaseDuration (16 bytes) + * + * return value : + * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR + * or a UPnP Error Code. */ +LIBSPEC int +UPNP_GetSpecificPortMappingEntry(const char * controlURL, + const char * servicetype, + const char * extPort, + const char * proto, + char * intClient, + char * intPort, + char * desc, + char * enabled, + char * leaseDuration); + +/* UPNP_GetGenericPortMappingEntry() + * params : + * in index + * out extPort (6 bytes) + * out intClient (16 bytes) + * out intPort (6 bytes) + * out protocol (4 bytes) + * out desc (80 bytes) + * out enabled (4 bytes) + * out rHost (64 bytes) + * out duration (16 bytes) + * + * return value : + * UPNPCOMMAND_SUCCESS, UPNPCOMMAND_INVALID_ARGS, UPNPCOMMAND_UNKNOWN_ERROR + * or a UPnP Error Code. + * + * Possible UPNP Error codes : + * 402 Invalid Args - See UPnP Device Architecture section on Control. + * 713 SpecifiedArrayIndexInvalid - The specified array index is out of bounds + */ +LIBSPEC int +UPNP_GetGenericPortMappingEntry(const char * controlURL, + const char * servicetype, + const char * index, + char * extPort, + char * intClient, + char * intPort, + char * protocol, + char * desc, + char * enabled, + char * rHost, + char * duration); + +/* UPNP_GetListOfPortMappings() Available in IGD v2 + * + * + * Possible UPNP Error codes : + * 606 Action not Authorized + * 730 PortMappingNotFound - no port mapping is found in the specified range. + * 733 InconsistantParameters - NewStartPort and NewEndPort values are not + * consistent. + */ +LIBSPEC int +UPNP_GetListOfPortMappings(const char * controlURL, + const char * servicetype, + const char * startPort, + const char * endPort, + const char * protocol, + const char * numberOfPorts, + struct PortMappingParserData * data); + +/* IGD:2, functions for service WANIPv6FirewallControl:1 */ +LIBSPEC int +UPNP_GetFirewallStatus(const char * controlURL, + const char * servicetype, + int * firewallEnabled, + int * inboundPinholeAllowed); + +LIBSPEC int +UPNP_GetOutboundPinholeTimeout(const char * controlURL, const char * servicetype, + const char * remoteHost, + const char * remotePort, + const char * intClient, + const char * intPort, + const char * proto, + int * opTimeout); + +LIBSPEC int +UPNP_AddPinhole(const char * controlURL, const char * servicetype, + const char * remoteHost, + const char * remotePort, + const char * intClient, + const char * intPort, + const char * proto, + const char * leaseTime, + char * uniqueID); + +LIBSPEC int +UPNP_UpdatePinhole(const char * controlURL, const char * servicetype, + const char * uniqueID, + const char * leaseTime); + +LIBSPEC int +UPNP_DeletePinhole(const char * controlURL, const char * servicetype, const char * uniqueID); + +LIBSPEC int +UPNP_CheckPinholeWorking(const char * controlURL, const char * servicetype, + const char * uniqueID, int * isWorking); + +LIBSPEC int +UPNP_GetPinholePackets(const char * controlURL, const char * servicetype, + const char * uniqueID, int * packets); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/upnperrors.c b/3rdParty/LibMiniUPnPc/src/miniupnpc/upnperrors.c new file mode 100644 index 0000000..a48ae10 --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/upnperrors.c @@ -0,0 +1,103 @@ +/* $Id: upnperrors.c,v 1.5 2011/04/10 11:19:36 nanard Exp $ */ +/* Project : miniupnp + * Author : Thomas BERNARD + * copyright (c) 2007 Thomas Bernard + * All Right reserved. + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * This software is subjet to the conditions detailed in the + * provided LICENCE file. */ +#include <string.h> +#include "upnperrors.h" +#include "upnpcommands.h" +#include "miniupnpc.h" + +const char * strupnperror(int err) +{ + const char * s = NULL; + switch(err) { + case UPNPCOMMAND_SUCCESS: + s = "Success"; + break; + case UPNPCOMMAND_UNKNOWN_ERROR: + s = "Miniupnpc Unknown Error"; + break; + case UPNPCOMMAND_INVALID_ARGS: + s = "Miniupnpc Invalid Arguments"; + break; + case UPNPDISCOVER_SOCKET_ERROR: + s = "Miniupnpc Socket error"; + break; + case UPNPDISCOVER_MEMORY_ERROR: + s = "Miniupnpc Memory allocation error"; + break; + case 401: + s = "Invalid Action"; + break; + case 402: + s = "Invalid Args"; + break; + case 501: + s = "Action Failed"; + break; + case 606: + s = "Action not authorized"; + break; + case 701: + s = "PinholeSpaceExhausted"; + break; + case 702: + s = "FirewallDisabled"; + break; + case 703: + s = "InboundPinholeNotAllowed"; + break; + case 704: + s = "NoSuchEntry"; + break; + case 705: + s = "ProtocolNotSupported"; + break; + case 706: + s = "InternalPortWildcardingNotAllowed"; + break; + case 707: + s = "ProtocolWildcardingNotAllowed"; + break; + case 708: + s = "WildcardNotPermittedInSrcIP"; + break; + case 709: + s = "NoPacketSent"; + break; + case 713: + s = "SpecifiedArrayIndexInvalid"; + break; + case 714: + s = "NoSuchEntryInArray"; + break; + case 715: + s = "WildCardNotPermittedInSrcIP"; + break; + case 716: + s = "WildCardNotPermittedInExtPort"; + break; + case 718: + s = "ConflictInMappingEntry"; + break; + case 724: + s = "SamePortValuesRequired"; + break; + case 725: + s = "OnlyPermanentLeasesSupported"; + break; + case 726: + s = "RemoteHostOnlySupportsWildcard"; + break; + case 727: + s = "ExternalPortOnlySupportsWildcard"; + break; + default: + s = NULL; + } + return s; +} diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/upnperrors.h b/3rdParty/LibMiniUPnPc/src/miniupnpc/upnperrors.h new file mode 100644 index 0000000..2c544c9 --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/upnperrors.h @@ -0,0 +1,26 @@ +/* $Id: upnperrors.h,v 1.2 2008/07/02 23:31:15 nanard Exp $ */ +/* (c) 2007 Thomas Bernard + * All rights reserved. + * MiniUPnP Project. + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * This software is subjet to the conditions detailed in the + * provided LICENCE file. */ +#ifndef __UPNPERRORS_H__ +#define __UPNPERRORS_H__ + +#include "declspec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* strupnperror() + * Return a string description of the UPnP error code + * or NULL for undefinded errors */ +LIBSPEC const char * strupnperror(int err); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/upnpreplyparse.c b/3rdParty/LibMiniUPnPc/src/miniupnpc/upnpreplyparse.c new file mode 100644 index 0000000..482030b --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/upnpreplyparse.c @@ -0,0 +1,152 @@ +/* $Id: upnpreplyparse.c,v 1.11 2011/02/07 16:17:06 nanard Exp $ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2011 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include "upnpreplyparse.h" +#include "minixml.h" + +static void +NameValueParserStartElt(void * d, const char * name, int l) +{ + struct NameValueParserData * data = (struct NameValueParserData *)d; + if(l>63) + l = 63; + memcpy(data->curelt, name, l); + data->curelt[l] = '\0'; +} + +static void +NameValueParserGetData(void * d, const char * datas, int l) +{ + struct NameValueParserData * data = (struct NameValueParserData *)d; + struct NameValue * nv; + if(strcmp(data->curelt, "NewPortListing") == 0) + { + /* specific case for NewPortListing which is a XML Document */ + data->portListing = malloc(l + 1); + if(!data->portListing) + { + /* malloc error */ + return; + } + memcpy(data->portListing, datas, l); + data->portListing[l] = '\0'; + data->portListingLength = l; + } + else + { + /* standard case. Limited to 63 chars strings */ + nv = malloc(sizeof(struct NameValue)); + if(l>63) + l = 63; + strncpy(nv->name, data->curelt, 64); + nv->name[63] = '\0'; + memcpy(nv->value, datas, l); + nv->value[l] = '\0'; + LIST_INSERT_HEAD( &(data->head), nv, entries); + } +} + +void +ParseNameValue(const char * buffer, int bufsize, + struct NameValueParserData * data) +{ + struct xmlparser parser; + LIST_INIT(&(data->head)); + data->portListing = NULL; + data->portListingLength = 0; + /* init xmlparser object */ + parser.xmlstart = buffer; + parser.xmlsize = bufsize; + parser.data = data; + parser.starteltfunc = NameValueParserStartElt; + parser.endeltfunc = 0; + parser.datafunc = NameValueParserGetData; + parser.attfunc = 0; + parsexml(&parser); +} + +void +ClearNameValueList(struct NameValueParserData * pdata) +{ + struct NameValue * nv; + if(pdata->portListing) + { + free(pdata->portListing); + pdata->portListing = NULL; + pdata->portListingLength = 0; + } + while((nv = pdata->head.lh_first) != NULL) + { + LIST_REMOVE(nv, entries); + free(nv); + } +} + +char * +GetValueFromNameValueList(struct NameValueParserData * pdata, + const char * Name) +{ + struct NameValue * nv; + char * p = NULL; + for(nv = pdata->head.lh_first; + (nv != NULL) && (p == NULL); + nv = nv->entries.le_next) + { + if(strcmp(nv->name, Name) == 0) + p = nv->value; + } + return p; +} + +#if 0 +/* useless now that minixml ignores namespaces by itself */ +char * +GetValueFromNameValueListIgnoreNS(struct NameValueParserData * pdata, + const char * Name) +{ + struct NameValue * nv; + char * p = NULL; + char * pname; + for(nv = pdata->head.lh_first; + (nv != NULL) && (p == NULL); + nv = nv->entries.le_next) + { + pname = strrchr(nv->name, ':'); + if(pname) + pname++; + else + pname = nv->name; + if(strcmp(pname, Name)==0) + p = nv->value; + } + return p; +} +#endif + +/* debug all-in-one function + * do parsing then display to stdout */ +#ifdef DEBUG +void +DisplayNameValueList(char * buffer, int bufsize) +{ + struct NameValueParserData pdata; + struct NameValue * nv; + ParseNameValue(buffer, bufsize, &pdata); + for(nv = pdata.head.lh_first; + nv != NULL; + nv = nv->entries.le_next) + { + printf("%s = %s\n", nv->name, nv->value); + } + ClearNameValueList(&pdata); +} +#endif + diff --git a/3rdParty/LibMiniUPnPc/src/miniupnpc/upnpreplyparse.h b/3rdParty/LibMiniUPnPc/src/miniupnpc/upnpreplyparse.h new file mode 100644 index 0000000..267ea87 --- /dev/null +++ b/3rdParty/LibMiniUPnPc/src/miniupnpc/upnpreplyparse.h @@ -0,0 +1,64 @@ +/* $Id: upnpreplyparse.h,v 1.11 2011/02/07 16:17:06 nanard Exp $ */ +/* MiniUPnP project + * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ + * (c) 2006-2011 Thomas Bernard + * This software is subject to the conditions detailed + * in the LICENCE file provided within the distribution */ + +#ifndef __UPNPREPLYPARSE_H__ +#define __UPNPREPLYPARSE_H__ + +#if defined(NO_SYS_QUEUE_H) || defined(WIN32) || defined(__HAIKU__) +#include "bsdqueue.h" +#else +#include <sys/queue.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct NameValue { + LIST_ENTRY(NameValue) entries; + char name[64]; + char value[64]; +}; + +struct NameValueParserData { + LIST_HEAD(listhead, NameValue) head; + char curelt[64]; + char * portListing; + int portListingLength; +}; + +/* ParseNameValue() */ +void +ParseNameValue(const char * buffer, int bufsize, + struct NameValueParserData * data); + +/* ClearNameValueList() */ +void +ClearNameValueList(struct NameValueParserData * pdata); + +/* GetValueFromNameValueList() */ +char * +GetValueFromNameValueList(struct NameValueParserData * pdata, + const char * Name); + +/* GetValueFromNameValueListIgnoreNS() */ +char * +GetValueFromNameValueListIgnoreNS(struct NameValueParserData * pdata, + const char * Name); + +/* DisplayNameValueList() */ +#ifdef DEBUG +void +DisplayNameValueList(char * buffer, int bufsize); +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/3rdParty/LibNATPMP/SConscript b/3rdParty/LibNATPMP/SConscript new file mode 100644 index 0000000..60e59a0 --- /dev/null +++ b/3rdParty/LibNATPMP/SConscript @@ -0,0 +1,50 @@ +Import(["env", "conf_env"]) + +if env.get("LIBNATPMP_BUNDLED", False) : + +################################################################################ +# Module flags +################################################################################ + + if env["SCONS_STAGE"] == "flags" : + env["LIBNATPMP_FLAGS"] = { + "CPPPATH": [Dir("src/libnatpmp")], + "LIBPATH": [Dir(".")], + "LIBS": ["Swiften_NATPMP"], + } + #if env["PLATFORM"] == "win32" : + # env["LIBIDN_FLAGS"]["CPPPATH"] += [Dir("stubs/win32")] + # if env["MSVC_VERSION"][:3] == "9.0" : + # env["LIBIDN_FLAGS"]["CPPPATH"] += [Dir("stubs/win32/VC2008")] + +################################################################################ +# Build +################################################################################ + + if env["SCONS_STAGE"] == "build" : + myenv = env.Clone() + myenv.Append(CPPPATH = ["src"]) + # Remove warn flags + myenv.Replace(CCFLAGS = [flag for flag in env["CCFLAGS"] if flag not in ["-W", "-Wall"]]) + + myenv.Append(CCFLAGS = ["-DNDEBUG", "-DSTATICLIB"]) + + #if myenv["PLATFORM"] != "win32": + # myenv.Append(CCFLAGS = ["-DMINIUPNPC_SET_SOCKET_TIMEOUT"]) + + if myenv["PLATFORM"] == "darwin": + myenv.Append(CCFLAGS = ["-DMACOSX", "-D_DARWIN_C_SOURCE"]) + + if myenv["PLATFORM"] == "win32": + myenv.Append(CCFLAGS = ["-DWIN32"]) + + src_files = [ + "src/libnatpmp/getgateway.c", + "src/libnatpmp/natpmp.c", + "src/libnatpmp/natpmpc.c", + ] + + if myenv["PLATFORM"] == "win32": + src_files += ["src/libnatpmp/wingettimeofday.c"] + + myenv.StaticLibrary("Swiften_NATPMP", src_files) diff --git a/3rdParty/LibNATPMP/src/libnatpmp/LICENSE b/3rdParty/LibNATPMP/src/libnatpmp/LICENSE new file mode 100644 index 0000000..14db2fe --- /dev/null +++ b/3rdParty/LibNATPMP/src/libnatpmp/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2007-2009, Thomas BERNARD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + diff --git a/3rdParty/LibNATPMP/src/libnatpmp/Makefile b/3rdParty/LibNATPMP/src/libnatpmp/Makefile new file mode 100644 index 0000000..b523e53 --- /dev/null +++ b/3rdParty/LibNATPMP/src/libnatpmp/Makefile @@ -0,0 +1,97 @@ +# $Id: Makefile,v 1.16 2011/01/03 17:31:03 nanard Exp $ +# This Makefile is designed for use with GNU make +# libnatpmp +# (c) 2007-2011 Thomas Bernard +# http://miniupnp.free.fr/libnatpmp.html + +OS = $(shell uname -s) +CC = gcc +INSTALL = install + +# APIVERSION is used in soname +APIVERSION = 1 +LDFLAGS = --no-undefined +CFLAGS = -O -fPIC -Wall -DENABLE_STRNATPMPERR + +LIBOBJS = natpmp.o getgateway.o + +OBJS = $(LIBOBJS) testgetgateway.o natpmpc.o + +STATICLIB = libnatpmp.a +ifeq ($(OS), Darwin) + SHAREDLIB = libnatpmp.dynlib + SONAME = $(basename $(SHAREDLIB)).$(APIVERSION).dylib + CFLAGS := -DMACOSX -D_DARWIN_C_SOURCE $(CFLAGS) +else + SHAREDLIB = libnatpmp.so + SONAME = $(SHAREDLIB).$(APIVERSION) +endif + +HEADERS = natpmp.h + +EXECUTABLES = testgetgateway natpmpc-shared natpmpc-static + +INSTALLPREFIX ?= $(PREFIX)/usr +INSTALLDIRINC = $(INSTALLPREFIX)/include +INSTALLDIRLIB = $(INSTALLPREFIX)/lib +INSTALLDIRBIN = $(INSTALLPREFIX)/bin + +.PHONY: all clean depend install cleaninstall installpythonmodule + +all: $(STATICLIB) $(SHAREDLIB) $(EXECUTABLES) + +pythonmodule: $(STATICLIB) libnatpmpmodule.c setup.py + python setup.py build + touch $@ + +installpythonmodule: pythonmodule + python setup.py install + +clean: + $(RM) $(OBJS) $(EXECUTABLES) $(STATICLIB) $(SHAREDLIB) + $(RM) pythonmodule + $(RM) -r build/ dist/ + +depend: + makedepend -f$(MAKEFILE_LIST) -Y $(OBJS:.o=.c) 2>/dev/null + +install: $(HEADERS) $(STATICLIB) $(SHAREDLIB) natpmpc-shared + $(INSTALL) -d $(INSTALLDIRINC) + $(INSTALL) -m 644 $(HEADERS) $(INSTALLDIRINC) + $(INSTALL) -d $(INSTALLDIRLIB) + $(INSTALL) -m 644 $(STATICLIB) $(INSTALLDIRLIB) + $(INSTALL) -m 644 $(SHAREDLIB) $(INSTALLDIRLIB)/$(SONAME) + $(INSTALL) -d $(INSTALLDIRBIN) + $(INSTALL) -m 755 natpmpc-shared $(INSTALLDIRBIN)/natpmpc + ln -s -f $(SONAME) $(INSTALLDIRLIB)/$(SHAREDLIB) + +cleaninstall: + $(RM) $(addprefix $(INSTALLDIRINC), $(HEADERS)) + $(RM) $(INSTALLDIRLIB)/$(SONAME) + $(RM) $(INSTALLDIRLIB)/$(SHAREDLIB) + $(RM) $(INSTALLDIRLIB)/$(STATICLIB) + +testgetgateway: testgetgateway.o getgateway.o + +natpmpc-static: natpmpc.o $(STATICLIB) + $(CC) -o $@ $^ + +natpmpc-shared: natpmpc.o $(SHAREDLIB) + $(CC) -o $@ $^ + +$(STATICLIB): $(LIBOBJS) + $(AR) crs $@ $? + +$(SHAREDLIB): $(LIBOBJS) +ifeq ($(OS), Darwin) + $(CC) -dynamiclib -Wl,-install_name,$(SONAME) -o $@ $^ +else + $(CC) -shared -Wl,-soname,$(SONAME) -o $@ $^ +endif + +# DO NOT DELETE + +natpmp.o: natpmp.h getgateway.h declspec.h +getgateway.o: getgateway.h declspec.h +testgetgateway.o: getgateway.h declspec.h +natpmpc.o: natpmp.h diff --git a/3rdParty/LibNATPMP/src/libnatpmp/declspec.h b/3rdParty/LibNATPMP/src/libnatpmp/declspec.h new file mode 100644 index 0000000..ea479d1 --- /dev/null +++ b/3rdParty/LibNATPMP/src/libnatpmp/declspec.h @@ -0,0 +1,15 @@ +#ifndef __DECLSPEC_H__ +#define __DECLSPEC_H__ + +#if defined(WIN32) && !defined(STATICLIB) + #ifdef NATPMP_EXPORTS + #define LIBSPEC __declspec(dllexport) + #else + #define LIBSPEC __declspec(dllimport) + #endif +#else + #define LIBSPEC +#endif + +#endif + diff --git a/3rdParty/LibNATPMP/src/libnatpmp/getgateway.c b/3rdParty/LibNATPMP/src/libnatpmp/getgateway.c new file mode 100644 index 0000000..bcde3ad --- /dev/null +++ b/3rdParty/LibNATPMP/src/libnatpmp/getgateway.c @@ -0,0 +1,554 @@ +/* $Id: getgateway.c,v 1.19 2009/12/19 15:20:45 nanard Exp $ */ +/* libnatpmp + * Copyright (c) 2007-2009, Thomas BERNARD <miniupnp@free.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include <stdio.h> +#include <ctype.h> +#ifndef WIN32 +#include <netinet/in.h> +#endif +#if !defined(_MSC_VER) +#include <sys/param.h> +#endif +/* There is no portable method to get the default route gateway. + * So below are four (or five ?) differents functions implementing this. + * Parsing /proc/net/route is for linux. + * sysctl is the way to access such informations on BSD systems. + * Many systems should provide route information through raw PF_ROUTE + * sockets. + * In MS Windows, default gateway is found by looking into the registry + * or by using GetBestRoute(). */ +#ifdef __linux__ +#define USE_PROC_NET_ROUTE +#undef USE_SOCKET_ROUTE +#undef USE_SYSCTL_NET_ROUTE +#endif + +#ifdef BSD +#undef USE_PROC_NET_ROUTE +#define USE_SOCKET_ROUTE +#undef USE_SYSCTL_NET_ROUTE +#endif + +#ifdef __APPLE__ +#undef USE_PROC_NET_ROUTE +#undef USE_SOCKET_ROUTE +#define USE_SYSCTL_NET_ROUTE +#endif + +#if (defined(sun) && defined(__SVR4)) +#undef USE_PROC_NET_ROUTE +#define USE_SOCKET_ROUTE +#undef USE_SYSCTL_NET_ROUTE +#endif + +#ifdef WIN32 +#undef USE_PROC_NET_ROUTE +#undef USE_SOCKET_ROUTE +#undef USE_SYSCTL_NET_ROUTE +//#define USE_WIN32_CODE +#define USE_WIN32_CODE_2 +#endif + +#ifdef __CYGWIN__ +#undef USE_PROC_NET_ROUTE +#undef USE_SOCKET_ROUTE +#undef USE_SYSCTL_NET_ROUTE +#define USE_WIN32_CODE +#include <stdarg.h> +#include <w32api/windef.h> +#include <w32api/winbase.h> +#include <w32api/winreg.h> +#endif + +#ifdef __HAIKU__ +#include <stdlib.h> +#include <unistd.h> +#include <net/if.h> +#include <sys/sockio.h> +#define USE_HAIKU_CODE +#endif + +#ifdef USE_SYSCTL_NET_ROUTE +#include <stdlib.h> +#include <sys/sysctl.h> +#include <sys/socket.h> +#include <net/route.h> +#endif +#ifdef USE_SOCKET_ROUTE +#include <unistd.h> +#include <string.h> +#include <sys/socket.h> +#include <net/if.h> +#include <net/route.h> +#endif + +#ifdef USE_WIN32_CODE +#include <unknwn.h> +#include <winreg.h> +#define MAX_KEY_LENGTH 255 +#define MAX_VALUE_LENGTH 16383 +#endif + +#ifdef USE_WIN32_CODE_2 +#include <windows.h> +#include <iphlpapi.h> +#endif + +#include "getgateway.h" + +#ifndef WIN32 +#define SUCCESS (0) +#define FAILED (-1) +#endif + +#ifdef USE_PROC_NET_ROUTE +/* + parse /proc/net/route which is as follow : + +Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT +wlan0 0001A8C0 00000000 0001 0 0 0 00FFFFFF 0 0 0 +eth0 0000FEA9 00000000 0001 0 0 0 0000FFFF 0 0 0 +wlan0 00000000 0101A8C0 0003 0 0 0 00000000 0 0 0 +eth0 00000000 00000000 0001 0 0 1000 00000000 0 0 0 + + One header line, and then one line by route by route table entry. +*/ +int getdefaultgateway(in_addr_t * addr) +{ + unsigned long d, g; + char buf[256]; + int line = 0; + FILE * f; + char * p; + f = fopen("/proc/net/route", "r"); + if(!f) + return FAILED; + while(fgets(buf, sizeof(buf), f)) { + if(line > 0) { /* skip the first line */ + p = buf; + /* skip the interface name */ + while(*p && !isspace(*p)) + p++; + while(*p && isspace(*p)) + p++; + if(sscanf(p, "%lx%lx", &d, &g)==2) { + if(d == 0 && g != 0) { /* default */ + *addr = g; + fclose(f); + return SUCCESS; + } + } + } + line++; + } + /* default route not found ! */ + if(f) + fclose(f); + return FAILED; +} +#endif /* #ifdef USE_PROC_NET_ROUTE */ + + +#ifdef USE_SYSCTL_NET_ROUTE + +#define ROUNDUP(a) \ + ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) + +int getdefaultgateway(in_addr_t * addr) +{ +#if 0 + /* net.route.0.inet.dump.0.0 ? */ + int mib[] = {CTL_NET, PF_ROUTE, 0, AF_INET, + NET_RT_DUMP, 0, 0/*tableid*/}; +#endif + /* net.route.0.inet.flags.gateway */ + int mib[] = {CTL_NET, PF_ROUTE, 0, AF_INET, + NET_RT_FLAGS, RTF_GATEWAY}; + size_t l; + char * buf, * p; + struct rt_msghdr * rt; + struct sockaddr * sa; + struct sockaddr * sa_tab[RTAX_MAX]; + int i; + int r = FAILED; + if(sysctl(mib, sizeof(mib)/sizeof(int), 0, &l, 0, 0) < 0) { + return FAILED; + } + if(l>0) { + buf = malloc(l); + if(sysctl(mib, sizeof(mib)/sizeof(int), buf, &l, 0, 0) < 0) { + free(buf); + return FAILED; + } + for(p=buf; p<buf+l; p+=rt->rtm_msglen) { + rt = (struct rt_msghdr *)p; + sa = (struct sockaddr *)(rt + 1); + for(i=0; i<RTAX_MAX; i++) { + if(rt->rtm_addrs & (1 << i)) { + sa_tab[i] = sa; + sa = (struct sockaddr *)((char *)sa + ROUNDUP(sa->sa_len)); + } else { + sa_tab[i] = NULL; + } + } + if( ((rt->rtm_addrs & (RTA_DST|RTA_GATEWAY)) == (RTA_DST|RTA_GATEWAY)) + && sa_tab[RTAX_DST]->sa_family == AF_INET + && sa_tab[RTAX_GATEWAY]->sa_family == AF_INET) { + if(((struct sockaddr_in *)sa_tab[RTAX_DST])->sin_addr.s_addr == 0) { + *addr = ((struct sockaddr_in *)(sa_tab[RTAX_GATEWAY]))->sin_addr.s_addr; + r = SUCCESS; + } + } + } + free(buf); + } + return r; +} +#endif /* #ifdef USE_SYSCTL_NET_ROUTE */ + + +#ifdef USE_SOCKET_ROUTE +/* Thanks to Darren Kenny for this code */ +#define NEXTADDR(w, u) \ + if (rtm_addrs & (w)) {\ + l = sizeof(struct sockaddr); memmove(cp, &(u), l); cp += l;\ + } + +#define rtm m_rtmsg.m_rtm + +struct { + struct rt_msghdr m_rtm; + char m_space[512]; +} m_rtmsg; + +int getdefaultgateway(in_addr_t *addr) +{ + int s, seq, l, rtm_addrs, i; + pid_t pid; + struct sockaddr so_dst, so_mask; + char *cp = m_rtmsg.m_space; + struct sockaddr *gate = NULL, *sa; + struct rt_msghdr *msg_hdr; + + pid = getpid(); + seq = 0; + rtm_addrs = RTA_DST | RTA_NETMASK; + + memset(&so_dst, 0, sizeof(so_dst)); + memset(&so_mask, 0, sizeof(so_mask)); + memset(&rtm, 0, sizeof(struct rt_msghdr)); + + rtm.rtm_type = RTM_GET; + rtm.rtm_flags = RTF_UP | RTF_GATEWAY; + rtm.rtm_version = RTM_VERSION; + rtm.rtm_seq = ++seq; + rtm.rtm_addrs = rtm_addrs; + + so_dst.sa_family = AF_INET; + so_mask.sa_family = AF_INET; + + NEXTADDR(RTA_DST, so_dst); + NEXTADDR(RTA_NETMASK, so_mask); + + rtm.rtm_msglen = l = cp - (char *)&m_rtmsg; + + s = socket(PF_ROUTE, SOCK_RAW, 0); + + if (write(s, (char *)&m_rtmsg, l) < 0) { + close(s); + return FAILED; + } + + do { + l = read(s, (char *)&m_rtmsg, sizeof(m_rtmsg)); + } while (l > 0 && (rtm.rtm_seq != seq || rtm.rtm_pid != pid)); + + close(s); + + msg_hdr = &rtm; + + cp = ((char *)(msg_hdr + 1)); + if (msg_hdr->rtm_addrs) { + for (i = 1; i; i <<= 1) + if (i & msg_hdr->rtm_addrs) { + sa = (struct sockaddr *)cp; + if (i == RTA_GATEWAY ) + gate = sa; + + cp += sizeof(struct sockaddr); + } + } else { + return FAILED; + } + + + if (gate != NULL ) { + *addr = ((struct sockaddr_in *)gate)->sin_addr.s_addr; + return SUCCESS; + } else { + return FAILED; + } +} +#endif /* #ifdef USE_SOCKET_ROUTE */ + +#ifdef USE_WIN32_CODE +LIBSPEC int getdefaultgateway(in_addr_t * addr) +{ + HKEY networkCardsKey; + HKEY networkCardKey; + HKEY interfacesKey; + HKEY interfaceKey; + DWORD i = 0; + DWORD numSubKeys = 0; + TCHAR keyName[MAX_KEY_LENGTH]; + DWORD keyNameLength = MAX_KEY_LENGTH; + TCHAR keyValue[MAX_VALUE_LENGTH]; + DWORD keyValueLength = MAX_VALUE_LENGTH; + DWORD keyValueType = REG_SZ; + TCHAR gatewayValue[MAX_VALUE_LENGTH]; + DWORD gatewayValueLength = MAX_VALUE_LENGTH; + DWORD gatewayValueType = REG_MULTI_SZ; + int done = 0; + + //const char * networkCardsPath = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards"; + //const char * interfacesPath = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces"; +#ifdef UNICODE + LPCTSTR networkCardsPath = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards"; + LPCTSTR interfacesPath = L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces"; +#define STR_SERVICENAME L"ServiceName" +#define STR_DHCPDEFAULTGATEWAY L"DhcpDefaultGateway" +#define STR_DEFAULTGATEWAY L"DefaultGateway" +#else + LPCTSTR networkCardsPath = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards"; + LPCTSTR interfacesPath = "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces"; +#define STR_SERVICENAME "ServiceName" +#define STR_DHCPDEFAULTGATEWAY "DhcpDefaultGateway" +#define STR_DEFAULTGATEWAY "DefaultGateway" +#endif + // The windows registry lists its primary network devices in the following location: + // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkCards + // + // Each network device has its own subfolder, named with an index, with various properties: + // -NetworkCards + // -5 + // -Description = Broadcom 802.11n Network Adapter + // -ServiceName = {E35A72F8-5065-4097-8DFE-C7790774EE4D} + // -8 + // -Description = Marvell Yukon 88E8058 PCI-E Gigabit Ethernet Controller + // -ServiceName = {86226414-5545-4335-A9D1-5BD7120119AD} + // + // The above service name is the name of a subfolder within: + // HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces + // + // There may be more subfolders in this interfaces path than listed in the network cards path above: + // -Interfaces + // -{3a539854-6a70-11db-887c-806e6f6e6963} + // -DhcpIPAddress = 0.0.0.0 + // -[more] + // -{E35A72F8-5065-4097-8DFE-C7790774EE4D} + // -DhcpIPAddress = 10.0.1.4 + // -DhcpDefaultGateway = 10.0.1.1 + // -[more] + // -{86226414-5545-4335-A9D1-5BD7120119AD} + // -DhcpIpAddress = 10.0.1.5 + // -DhcpDefaultGateay = 10.0.1.1 + // -[more] + // + // In order to extract this information, we enumerate each network card, and extract the ServiceName value. + // This is then used to open the interface subfolder, and attempt to extract a DhcpDefaultGateway value. + // Once one is found, we're done. + // + // It may be possible to simply enumerate the interface folders until we find one with a DhcpDefaultGateway value. + // However, the technique used is the technique most cited on the web, and we assume it to be more correct. + + if(ERROR_SUCCESS != RegOpenKeyEx(HKEY_LOCAL_MACHINE, // Open registry key or predifined key + networkCardsPath, // Name of registry subkey to open + 0, // Reserved - must be zero + KEY_READ, // Mask - desired access rights + &networkCardsKey)) // Pointer to output key + { + // Unable to open network cards keys + return -1; + } + + if(ERROR_SUCCESS != RegOpenKeyEx(HKEY_LOCAL_MACHINE, // Open registry key or predefined key + interfacesPath, // Name of registry subkey to open + 0, // Reserved - must be zero + KEY_READ, // Mask - desired access rights + &interfacesKey)) // Pointer to output key + { + // Unable to open interfaces key + RegCloseKey(networkCardsKey); + return -1; + } + + // Figure out how many subfolders are within the NetworkCards folder + RegQueryInfoKey(networkCardsKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + + //printf( "Number of subkeys: %u\n", (unsigned int)numSubKeys); + + // Enumrate through each subfolder within the NetworkCards folder + for(i = 0; i < numSubKeys && !done; i++) + { + keyNameLength = MAX_KEY_LENGTH; + if(ERROR_SUCCESS == RegEnumKeyEx(networkCardsKey, // Open registry key + i, // Index of subkey to retrieve + keyName, // Buffer that receives the name of the subkey + &keyNameLength, // Variable that receives the size of the above buffer + NULL, // Reserved - must be NULL + NULL, // Buffer that receives the class string + NULL, // Variable that receives the size of the above buffer + NULL)) // Variable that receives the last write time of subkey + { + if(RegOpenKeyEx(networkCardsKey, keyName, 0, KEY_READ, &networkCardKey) == ERROR_SUCCESS) + { + keyValueLength = MAX_VALUE_LENGTH; + if(ERROR_SUCCESS == RegQueryValueEx(networkCardKey, // Open registry key + STR_SERVICENAME, // Name of key to query + NULL, // Reserved - must be NULL + &keyValueType, // Receives value type + (LPBYTE)keyValue, // Receives value + &keyValueLength)) // Receives value length in bytes + { +// printf("keyValue: %s\n", keyValue); + if(RegOpenKeyEx(interfacesKey, keyValue, 0, KEY_READ, &interfaceKey) == ERROR_SUCCESS) + { + gatewayValueLength = MAX_VALUE_LENGTH; + if(ERROR_SUCCESS == RegQueryValueEx(interfaceKey, // Open registry key + STR_DHCPDEFAULTGATEWAY, // Name of key to query + NULL, // Reserved - must be NULL + &gatewayValueType, // Receives value type + (LPBYTE)gatewayValue, // Receives value + &gatewayValueLength)) // Receives value length in bytes + { + // Check to make sure it's a string + if((gatewayValueType == REG_MULTI_SZ || gatewayValueType == REG_SZ) && (gatewayValueLength > 1)) + { + //printf("gatewayValue: %s\n", gatewayValue); + done = 1; + } + } + else if(ERROR_SUCCESS == RegQueryValueEx(interfaceKey, // Open registry key + STR_DEFAULTGATEWAY, // Name of key to query + NULL, // Reserved - must be NULL + &gatewayValueType, // Receives value type + (LPBYTE)gatewayValue,// Receives value + &gatewayValueLength)) // Receives value length in bytes + { + // Check to make sure it's a string + if((gatewayValueType == REG_MULTI_SZ || gatewayValueType == REG_SZ) && (gatewayValueLength > 1)) + { + //printf("gatewayValue: %s\n", gatewayValue); + done = 1; + } + } + RegCloseKey(interfaceKey); + } + } + RegCloseKey(networkCardKey); + } + } + } + + RegCloseKey(interfacesKey); + RegCloseKey(networkCardsKey); + + if(done) + { +#if UNICODE + char tmp[32]; + for(i = 0; i < 32; i++) { + tmp[i] = (char)gatewayValue[i]; + if(!tmp[i]) + break; + } + tmp[31] = '\0'; + *addr = inet_addr(tmp); +#else + *addr = inet_addr(gatewayValue); +#endif + return 0; + } + + return -1; +} +#endif /* #ifdef USE_WIN32_CODE */ + +#ifdef USE_WIN32_CODE_2 +int getdefaultgateway(in_addr_t *addr) +{ + MIB_IPFORWARDROW ip_forward; + memset(&ip_forward, 0, sizeof(ip_forward)); + if(GetBestRoute(inet_addr("0.0.0.0"), 0, &ip_forward) != NO_ERROR) + return -1; + *addr = ip_forward.dwForwardNextHop; + return 0; +} +#endif /* #ifdef USE_WIN32_CODE_2 */ + +#ifdef USE_HAIKU_CODE +int getdefaultgateway(in_addr_t *addr) +{ + int fd, ret = -1; + struct ifconf config; + void *buffer = NULL; + struct ifreq *interface; + + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + return -1; + } + if (ioctl(fd, SIOCGRTSIZE, &config, sizeof(config)) != 0) { + goto fail; + } + if (config.ifc_value < 1) { + goto fail; /* No routes */ + } + if ((buffer = malloc(config.ifc_value)) == NULL) { + goto fail; + } + config.ifc_len = config.ifc_value; + config.ifc_buf = buffer; + if (ioctl(fd, SIOCGRTTABLE, &config, sizeof(config)) != 0) { + goto fail; + } + for (interface = buffer; + (uint8_t *)interface < (uint8_t *)buffer + config.ifc_len; ) { + struct route_entry route = interface->ifr_route; + int intfSize; + if (route.flags & (RTF_GATEWAY | RTF_DEFAULT)) { + *addr = ((struct sockaddr_in *)route.gateway)->sin_addr.s_addr; + ret = 0; + break; + } + intfSize = sizeof(route) + IF_NAMESIZE; + if (route.destination != NULL) { + intfSize += route.destination->sa_len; + } + if (route.mask != NULL) { + intfSize += route.mask->sa_len; + } + if (route.gateway != NULL) { + intfSize += route.gateway->sa_len; + } + interface = (struct ifreq *)((uint8_t *)interface + intfSize); + } +fail: + free(buffer); + close(fd); + return ret; +} +#endif /* #ifdef USE_HAIKU_CODE */ + + diff --git a/3rdParty/LibNATPMP/src/libnatpmp/getgateway.h b/3rdParty/LibNATPMP/src/libnatpmp/getgateway.h new file mode 100644 index 0000000..9432528 --- /dev/null +++ b/3rdParty/LibNATPMP/src/libnatpmp/getgateway.h @@ -0,0 +1,36 @@ +/* $Id: getgateway.h,v 1.4 2009/12/19 12:00:00 nanard Exp $ */ +/* libnatpmp + * Copyright (c) 2007, Thomas BERNARD <miniupnp@free.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#ifndef __GETGATEWAY_H__ +#define __GETGATEWAY_H__ + +#ifdef WIN32 +#if !defined(_MSC_VER) +#include <stdint.h> +#else +typedef unsigned long uint32_t; +typedef unsigned short uint16_t; +#endif +#define in_addr_t uint32_t +#endif +#include "declspec.h" + +/* getdefaultgateway() : + * return value : + * 0 : success + * -1 : failure */ +LIBSPEC int getdefaultgateway(in_addr_t * addr); + +#endif diff --git a/3rdParty/LibNATPMP/src/libnatpmp/natpmp.c b/3rdParty/LibNATPMP/src/libnatpmp/natpmp.c new file mode 100644 index 0000000..53869c3 --- /dev/null +++ b/3rdParty/LibNATPMP/src/libnatpmp/natpmp.c @@ -0,0 +1,350 @@ +/* $Id: natpmp.c,v 1.13 2011/01/03 17:31:03 nanard Exp $ */ +/* libnatpmp + * Copyright (c) 2007-2011, Thomas BERNARD <miniupnp@free.fr> + * http://miniupnp.free.fr/libnatpmp.html + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#ifdef __linux__ +#define _BSD_SOURCE 1 +#endif +#include <string.h> +#include <time.h> +#if !defined(_MSC_VER) +#include <sys/time.h> +#endif +#ifdef WIN32 +#include <errno.h> +#include <winsock2.h> +#include <ws2tcpip.h> +#include <io.h> +#define EWOULDBLOCK WSAEWOULDBLOCK +#define ECONNREFUSED WSAECONNREFUSED +#include "wingettimeofday.h" +#else +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/socket.h> +#define closesocket close +#endif +#include "natpmp.h" +#include "getgateway.h" + +LIBSPEC int initnatpmp(natpmp_t * p, int forcegw, in_addr_t forcedgw) +{ +#ifdef WIN32 + u_long ioctlArg = 1; +#else + int flags; +#endif + struct sockaddr_in addr; + if(!p) + return NATPMP_ERR_INVALIDARGS; + memset(p, 0, sizeof(natpmp_t)); + p->s = socket(PF_INET, SOCK_DGRAM, 0); + if(p->s < 0) + return NATPMP_ERR_SOCKETERROR; +#ifdef WIN32 + if(ioctlsocket(p->s, FIONBIO, &ioctlArg) == SOCKET_ERROR) + return NATPMP_ERR_FCNTLERROR; +#else + if((flags = fcntl(p->s, F_GETFL, 0)) < 0) + return NATPMP_ERR_FCNTLERROR; + if(fcntl(p->s, F_SETFL, flags | O_NONBLOCK) < 0) + return NATPMP_ERR_FCNTLERROR; +#endif + + if(forcegw) { + p->gateway = forcedgw; + } else { + if(getdefaultgateway(&(p->gateway)) < 0) + return NATPMP_ERR_CANNOTGETGATEWAY; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(NATPMP_PORT); + addr.sin_addr.s_addr = p->gateway; + if(connect(p->s, (struct sockaddr *)&addr, sizeof(addr)) < 0) + return NATPMP_ERR_CONNECTERR; + return 0; +} + +LIBSPEC int closenatpmp(natpmp_t * p) +{ + if(!p) + return NATPMP_ERR_INVALIDARGS; + if(closesocket(p->s) < 0) + return NATPMP_ERR_CLOSEERR; + return 0; +} + +int sendpendingrequest(natpmp_t * p) +{ + int r; +/* struct sockaddr_in addr;*/ + if(!p) + return NATPMP_ERR_INVALIDARGS; +/* memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(NATPMP_PORT); + addr.sin_addr.s_addr = p->gateway; + r = (int)sendto(p->s, p->pending_request, p->pending_request_len, 0, + (struct sockaddr *)&addr, sizeof(addr));*/ + r = (int)send(p->s, p->pending_request, p->pending_request_len, 0); + return (r<0) ? NATPMP_ERR_SENDERR : r; +} + +int sendnatpmprequest(natpmp_t * p) +{ + int n; + if(!p) + return NATPMP_ERR_INVALIDARGS; + /* TODO : check if no request is allready pending */ + p->has_pending_request = 1; + p->try_number = 1; + n = sendpendingrequest(p); + gettimeofday(&p->retry_time, NULL); // check errors ! + p->retry_time.tv_usec += 250000; /* add 250ms */ + if(p->retry_time.tv_usec >= 1000000) { + p->retry_time.tv_usec -= 1000000; + p->retry_time.tv_sec++; + } + return n; +} + +LIBSPEC int getnatpmprequesttimeout(natpmp_t * p, struct timeval * timeout) +{ + struct timeval now; + if(!p || !timeout) + return NATPMP_ERR_INVALIDARGS; + if(!p->has_pending_request) + return NATPMP_ERR_NOPENDINGREQ; + if(gettimeofday(&now, NULL) < 0) + return NATPMP_ERR_GETTIMEOFDAYERR; + timeout->tv_sec = p->retry_time.tv_sec - now.tv_sec; + timeout->tv_usec = p->retry_time.tv_usec - now.tv_usec; + if(timeout->tv_usec < 0) { + timeout->tv_usec += 1000000; + timeout->tv_sec--; + } + return 0; +} + +LIBSPEC int sendpublicaddressrequest(natpmp_t * p) +{ + if(!p) + return NATPMP_ERR_INVALIDARGS; + //static const unsigned char request[] = { 0, 0 }; + p->pending_request[0] = 0; + p->pending_request[1] = 0; + p->pending_request_len = 2; + // TODO: return 0 instead of sizeof(request) ?? + return sendnatpmprequest(p); +} + +LIBSPEC int sendnewportmappingrequest(natpmp_t * p, int protocol, + uint16_t privateport, uint16_t publicport, + uint32_t lifetime) +{ + if(!p || (protocol!=NATPMP_PROTOCOL_TCP && protocol!=NATPMP_PROTOCOL_UDP)) + return NATPMP_ERR_INVALIDARGS; + p->pending_request[0] = 0; + p->pending_request[1] = protocol; + p->pending_request[2] = 0; + p->pending_request[3] = 0; + *((uint16_t *)(p->pending_request + 4)) = htons(privateport); + *((uint16_t *)(p->pending_request + 6)) = htons(publicport); + *((uint32_t *)(p->pending_request + 8)) = htonl(lifetime); + p->pending_request_len = 12; + return sendnatpmprequest(p); +} + +LIBSPEC int readnatpmpresponse(natpmp_t * p, natpmpresp_t * response) +{ + unsigned char buf[16]; + struct sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + int n; + if(!p) + return NATPMP_ERR_INVALIDARGS; + n = recvfrom(p->s, buf, sizeof(buf), 0, + (struct sockaddr *)&addr, &addrlen); + if(n<0) + switch(errno) { + /*case EAGAIN:*/ + case EWOULDBLOCK: + n = NATPMP_TRYAGAIN; + break; + case ECONNREFUSED: + n = NATPMP_ERR_NOGATEWAYSUPPORT; + break; + default: + n = NATPMP_ERR_RECVFROM; + } + /* check that addr is correct (= gateway) */ + else if(addr.sin_addr.s_addr != p->gateway) + n = NATPMP_ERR_WRONGPACKETSOURCE; + else { + response->resultcode = ntohs(*((uint16_t *)(buf + 2))); + response->epoch = ntohl(*((uint32_t *)(buf + 4))); + if(buf[0] != 0) + n = NATPMP_ERR_UNSUPPORTEDVERSION; + else if(buf[1] < 128 || buf[1] > 130) + n = NATPMP_ERR_UNSUPPORTEDOPCODE; + else if(response->resultcode != 0) { + switch(response->resultcode) { + case 1: + n = NATPMP_ERR_UNSUPPORTEDVERSION; + break; + case 2: + n = NATPMP_ERR_NOTAUTHORIZED; + break; + case 3: + n = NATPMP_ERR_NETWORKFAILURE; + break; + case 4: + n = NATPMP_ERR_OUTOFRESOURCES; + break; + case 5: + n = NATPMP_ERR_UNSUPPORTEDOPCODE; + break; + default: + n = NATPMP_ERR_UNDEFINEDERROR; + } + } else { + response->type = buf[1] & 0x7f; + if(buf[1] == 128) + //response->publicaddress.addr = *((uint32_t *)(buf + 8)); + response->pnu.publicaddress.addr.s_addr = *((uint32_t *)(buf + 8)); + else { + response->pnu.newportmapping.privateport = ntohs(*((uint16_t *)(buf + 8))); + response->pnu.newportmapping.mappedpublicport = ntohs(*((uint16_t *)(buf + 10))); + response->pnu.newportmapping.lifetime = ntohl(*((uint32_t *)(buf + 12))); + } + n = 0; + } + } + return n; +} + +int readnatpmpresponseorretry(natpmp_t * p, natpmpresp_t * response) +{ + int n; + if(!p || !response) + return NATPMP_ERR_INVALIDARGS; + if(!p->has_pending_request) + return NATPMP_ERR_NOPENDINGREQ; + n = readnatpmpresponse(p, response); + if(n<0) { + if(n==NATPMP_TRYAGAIN) { + struct timeval now; + gettimeofday(&now, NULL); // check errors ! + if(timercmp(&now, &p->retry_time, >=)) { + int delay, r; + if(p->try_number >= 9) { + return NATPMP_ERR_NOGATEWAYSUPPORT; + } + /*printf("retry! %d\n", p->try_number);*/ + delay = 250 * (1<<p->try_number); // ms + /*for(i=0; i<p->try_number; i++) + delay += delay;*/ + p->retry_time.tv_sec += (delay / 1000); + p->retry_time.tv_usec += (delay % 1000) * 1000; + if(p->retry_time.tv_usec >= 1000000) { + p->retry_time.tv_usec -= 1000000; + p->retry_time.tv_sec++; + } + p->try_number++; + r = sendpendingrequest(p); + if(r<0) + return r; + } + } + } else { + p->has_pending_request = 0; + } + return n; +} + +#ifdef ENABLE_STRNATPMPERR +LIBSPEC const char * strnatpmperr(int r) +{ + const char * s; + switch(r) { + case NATPMP_ERR_INVALIDARGS: + s = "invalid arguments"; + break; + case NATPMP_ERR_SOCKETERROR: + s = "socket() failed"; + break; + case NATPMP_ERR_CANNOTGETGATEWAY: + s = "cannot get default gateway ip address"; + break; + case NATPMP_ERR_CLOSEERR: +#ifdef WIN32 + s = "closesocket() failed"; +#else + s = "close() failed"; +#endif + break; + case NATPMP_ERR_RECVFROM: + s = "recvfrom() failed"; + break; + case NATPMP_ERR_NOPENDINGREQ: + s = "no pending request"; + break; + case NATPMP_ERR_NOGATEWAYSUPPORT: + s = "the gateway does not support nat-pmp"; + break; + case NATPMP_ERR_CONNECTERR: + s = "connect() failed"; + break; + case NATPMP_ERR_WRONGPACKETSOURCE: + s = "packet not received from the default gateway"; + break; + case NATPMP_ERR_SENDERR: + s = "send() failed"; + break; + case NATPMP_ERR_FCNTLERROR: + s = "fcntl() failed"; + break; + case NATPMP_ERR_GETTIMEOFDAYERR: + s = "gettimeofday() failed"; + break; + case NATPMP_ERR_UNSUPPORTEDVERSION: + s = "unsupported nat-pmp version error from server"; + break; + case NATPMP_ERR_UNSUPPORTEDOPCODE: + s = "unsupported nat-pmp opcode error from server"; + break; + case NATPMP_ERR_UNDEFINEDERROR: + s = "undefined nat-pmp server error"; + break; + case NATPMP_ERR_NOTAUTHORIZED: + s = "not authorized"; + break; + case NATPMP_ERR_NETWORKFAILURE: + s = "network failure"; + break; + case NATPMP_ERR_OUTOFRESOURCES: + s = "nat-pmp server out of resources"; + break; + default: + s = "Unknown libnatpmp error"; + } + return s; +} +#endif + diff --git a/3rdParty/LibNATPMP/src/libnatpmp/natpmp.h b/3rdParty/LibNATPMP/src/libnatpmp/natpmp.h new file mode 100644 index 0000000..1175b58 --- /dev/null +++ b/3rdParty/LibNATPMP/src/libnatpmp/natpmp.h @@ -0,0 +1,203 @@ +/* $Id: natpmp.h,v 1.14 2011/01/03 17:31:03 nanard Exp $ */ +/* libnatpmp + * Copyright (c) 2007-2011, Thomas BERNARD <miniupnp@free.fr> + * http://miniupnp.free.fr/libnatpmp.html + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#ifndef __NATPMP_H__ +#define __NATPMP_H__ + +/* NAT-PMP Port as defined by the NAT-PMP draft */ +#define NATPMP_PORT (5351) + +#include <time.h> +#if !defined(_MSC_VER) +#include <sys/time.h> +#endif +#ifdef WIN32 +#include <winsock2.h> +#if !defined(_MSC_VER) || _MSC_VER >= 1600 +#include <stdint.h> +#else +typedef unsigned long uint32_t; +typedef unsigned short uint16_t; +#endif +#define in_addr_t uint32_t +#include "declspec.h" +#else +#define LIBSPEC +#include <netinet/in.h> +#endif + +typedef struct { + int s; /* socket */ + in_addr_t gateway; /* default gateway (IPv4) */ + int has_pending_request; + unsigned char pending_request[12]; + int pending_request_len; + int try_number; + struct timeval retry_time; +} natpmp_t; + +typedef struct { + uint16_t type; /* NATPMP_RESPTYPE_* */ + uint16_t resultcode; /* NAT-PMP response code */ + uint32_t epoch; /* Seconds since start of epoch */ + union { + struct { + //in_addr_t addr; + struct in_addr addr; + } publicaddress; + struct { + uint16_t privateport; + uint16_t mappedpublicport; + uint32_t lifetime; + } newportmapping; + } pnu; +} natpmpresp_t; + +/* possible values for type field of natpmpresp_t */ +#define NATPMP_RESPTYPE_PUBLICADDRESS (0) +#define NATPMP_RESPTYPE_UDPPORTMAPPING (1) +#define NATPMP_RESPTYPE_TCPPORTMAPPING (2) + +/* Values to pass to sendnewportmappingrequest() */ +#define NATPMP_PROTOCOL_UDP (1) +#define NATPMP_PROTOCOL_TCP (2) + +/* return values */ +/* NATPMP_ERR_INVALIDARGS : invalid arguments passed to the function */ +#define NATPMP_ERR_INVALIDARGS (-1) +/* NATPMP_ERR_SOCKETERROR : socket() failed. check errno for details */ +#define NATPMP_ERR_SOCKETERROR (-2) +/* NATPMP_ERR_CANNOTGETGATEWAY : can't get default gateway IP */ +#define NATPMP_ERR_CANNOTGETGATEWAY (-3) +/* NATPMP_ERR_CLOSEERR : close() failed. check errno for details */ +#define NATPMP_ERR_CLOSEERR (-4) +/* NATPMP_ERR_RECVFROM : recvfrom() failed. check errno for details */ +#define NATPMP_ERR_RECVFROM (-5) +/* NATPMP_ERR_NOPENDINGREQ : readnatpmpresponseorretry() called while + * no NAT-PMP request was pending */ +#define NATPMP_ERR_NOPENDINGREQ (-6) +/* NATPMP_ERR_NOGATEWAYSUPPORT : the gateway does not support NAT-PMP */ +#define NATPMP_ERR_NOGATEWAYSUPPORT (-7) +/* NATPMP_ERR_CONNECTERR : connect() failed. check errno for details */ +#define NATPMP_ERR_CONNECTERR (-8) +/* NATPMP_ERR_WRONGPACKETSOURCE : packet not received from the network gateway */ +#define NATPMP_ERR_WRONGPACKETSOURCE (-9) +/* NATPMP_ERR_SENDERR : send() failed. check errno for details */ +#define NATPMP_ERR_SENDERR (-10) +/* NATPMP_ERR_FCNTLERROR : fcntl() failed. check errno for details */ +#define NATPMP_ERR_FCNTLERROR (-11) +/* NATPMP_ERR_GETTIMEOFDAYERR : gettimeofday() failed. check errno for details */ +#define NATPMP_ERR_GETTIMEOFDAYERR (-12) + +/* */ +#define NATPMP_ERR_UNSUPPORTEDVERSION (-14) +#define NATPMP_ERR_UNSUPPORTEDOPCODE (-15) + +/* Errors from the server : */ +#define NATPMP_ERR_UNDEFINEDERROR (-49) +#define NATPMP_ERR_NOTAUTHORIZED (-51) +#define NATPMP_ERR_NETWORKFAILURE (-52) +#define NATPMP_ERR_OUTOFRESOURCES (-53) + +/* NATPMP_TRYAGAIN : no data available for the moment. try again later */ +#define NATPMP_TRYAGAIN (-100) + +#ifdef __cplusplus +extern "C" { +#endif + +/* initnatpmp() + * initialize a natpmp_t object + * With forcegw=1 the gateway is not detected automaticaly. + * Return values : + * 0 = OK + * NATPMP_ERR_INVALIDARGS + * NATPMP_ERR_SOCKETERROR + * NATPMP_ERR_FCNTLERROR + * NATPMP_ERR_CANNOTGETGATEWAY + * NATPMP_ERR_CONNECTERR */ +LIBSPEC int initnatpmp(natpmp_t * p, int forcegw, in_addr_t forcedgw); + +/* closenatpmp() + * close resources associated with a natpmp_t object + * Return values : + * 0 = OK + * NATPMP_ERR_INVALIDARGS + * NATPMP_ERR_CLOSEERR */ +LIBSPEC int closenatpmp(natpmp_t * p); + +/* sendpublicaddressrequest() + * send a public address NAT-PMP request to the network gateway + * Return values : + * 2 = OK (size of the request) + * NATPMP_ERR_INVALIDARGS + * NATPMP_ERR_SENDERR */ +LIBSPEC int sendpublicaddressrequest(natpmp_t * p); + +/* sendnewportmappingrequest() + * send a new port mapping NAT-PMP request to the network gateway + * Arguments : + * protocol is either NATPMP_PROTOCOL_TCP or NATPMP_PROTOCOL_UDP, + * lifetime is in seconds. + * To remove a port mapping, set lifetime to zero. + * To remove all port mappings to the host, set lifetime and both ports + * to zero. + * Return values : + * 12 = OK (size of the request) + * NATPMP_ERR_INVALIDARGS + * NATPMP_ERR_SENDERR */ +LIBSPEC int sendnewportmappingrequest(natpmp_t * p, int protocol, + uint16_t privateport, uint16_t publicport, + uint32_t lifetime); + +/* getnatpmprequesttimeout() + * fills the timeval structure with the timeout duration of the + * currently pending NAT-PMP request. + * Return values : + * 0 = OK + * NATPMP_ERR_INVALIDARGS + * NATPMP_ERR_GETTIMEOFDAYERR + * NATPMP_ERR_NOPENDINGREQ */ +LIBSPEC int getnatpmprequesttimeout(natpmp_t * p, struct timeval * timeout); + +/* readnatpmpresponseorretry() + * fills the natpmpresp_t structure if possible + * Return values : + * 0 = OK + * NATPMP_TRYAGAIN + * NATPMP_ERR_INVALIDARGS + * NATPMP_ERR_NOPENDINGREQ + * NATPMP_ERR_NOGATEWAYSUPPORT + * NATPMP_ERR_RECVFROM + * NATPMP_ERR_WRONGPACKETSOURCE + * NATPMP_ERR_UNSUPPORTEDVERSION + * NATPMP_ERR_UNSUPPORTEDOPCODE + * NATPMP_ERR_NOTAUTHORIZED + * NATPMP_ERR_NETWORKFAILURE + * NATPMP_ERR_OUTOFRESOURCES + * NATPMP_ERR_UNSUPPORTEDOPCODE + * NATPMP_ERR_UNDEFINEDERROR */ +LIBSPEC int readnatpmpresponseorretry(natpmp_t * p, natpmpresp_t * response); + +#ifdef ENABLE_STRNATPMPERR +LIBSPEC const char * strnatpmperr(int t); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/3rdParty/LibNATPMP/src/libnatpmp/natpmpc.c b/3rdParty/LibNATPMP/src/libnatpmp/natpmpc.c new file mode 100644 index 0000000..d869572 --- /dev/null +++ b/3rdParty/LibNATPMP/src/libnatpmp/natpmpc.c @@ -0,0 +1,229 @@ +/* $Id: natpmpc.c,v 1.9 2011/04/18 18:25:21 nanard Exp $ */ +/* libnatpmp + * Copyright (c) 2007-2011, Thomas BERNARD <miniupnp@free.fr> + * http://miniupnp.free.fr/libnatpmp.html + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include <stdio.h> +#include <errno.h> +#include <string.h> +#if defined(_MSC_VER) +#if _MSC_VER >= 1400 +#define strcasecmp _stricmp +#else +#define strcasecmp stricmp +#endif +#else +#include <unistd.h> +#endif +#ifdef WIN32 +#include <winsock2.h> +#else +#include <netinet/in.h> +#include <arpa/inet.h> +#endif +#include "natpmp.h" + +void usage(FILE * out, const char * argv0) +{ + fprintf(out, "Usage :\n"); + fprintf(out, " %s [options]\n", argv0); + fprintf(out, "\tdisplay the public IP address.\n"); + fprintf(out, " %s -h\n", argv0); + fprintf(out, "\tdisplay this help screen.\n"); + fprintf(out, " %s [options] -a <public port> <private port> <protocol> [lifetime]\n", argv0); + fprintf(out, "\tadd a port mapping.\n"); + fprintf(out, "\nOption available :\n"); + fprintf(out, " -g ipv4address\n"); + fprintf(out, "\tforce the gateway to be used as destination for NAT-PMP commands.\n"); + fprintf(out, "\n In order to remove a mapping, set it with a lifetime of 0 seconds.\n"); + fprintf(out, " To remove all mappings for your machine, use 0 as private port and lifetime.\n"); +} + +/* sample code for using libnatpmp */ +int main(int argc, char * * argv) +{ + natpmp_t natpmp; + natpmpresp_t response; + int r; + int sav_errno; + struct timeval timeout; + fd_set fds; + int i; + int protocol = 0; + uint16_t privateport = 0; + uint16_t publicport = 0; + uint32_t lifetime = 3600; + int command = 0; + int forcegw = 0; + in_addr_t gateway = 0; + +#ifdef WIN32 + WSADATA wsaData; + int nResult = WSAStartup(MAKEWORD(2,2), &wsaData); + if(nResult != NO_ERROR) + { + fprintf(stderr, "WSAStartup() failed.\n"); + return -1; + } +#endif + + /* argument parsing */ + for(i=1; i<argc; i++) { + if(argv[i][0] == '-') { + switch(argv[i][1]) { + case 'h': + usage(stdout, argv[0]); + return 0; + case 'g': + forcegw = 1; + if(argc < i + 1) { + fprintf(stderr, "Not enough arguments for option -%c\n", argv[i][1]); + return 1; + } + gateway = inet_addr(argv[++i]); + break; + case 'a': + command = 'a'; + if(argc < i + 3) { + fprintf(stderr, "Not enough arguments for option -%c\n", argv[i][1]); + return 1; + } + i++; + if(1 != sscanf(argv[i], "%hu", &publicport)) { + fprintf(stderr, "%s is not a correct 16bits unsigned integer\n", argv[i]); + return 1; + } + i++; + if(1 != sscanf(argv[i], "%hu", &privateport)) { + fprintf(stderr, "%s is not a correct 16bits unsigned integer\n", argv[i]); + return 1; + } + i++; + if(0 == strcasecmp(argv[i], "tcp")) + protocol = NATPMP_PROTOCOL_TCP; + else if(0 == strcasecmp(argv[i], "udp")) + protocol = NATPMP_PROTOCOL_UDP; + else { + fprintf(stderr, "%s is not a valid protocol\n", argv[i]); + return 1; + } + if(argc >= i) { + i++; + if(1 != sscanf(argv[i], "%u", &lifetime)) { + fprintf(stderr, "%s is not a correct 32bits unsigned integer\n", argv[i]); + } + } + break; + default: + fprintf(stderr, "Unknown option %s\n", argv[i]); + usage(stderr, argv[0]); + return 1; + } + } else { + fprintf(stderr, "Unknown option %s\n", argv[i]); + usage(stderr, argv[0]); + return 1; + } + } + + /* initnatpmp() */ + r = initnatpmp(&natpmp, forcegw, gateway); + printf("initnatpmp() returned %d (%s)\n", r, r?"FAILED":"SUCCESS"); + if(r<0) + return 1; + + printf("using gateway : %s\n", inet_ntoa(*((struct in_addr *)&natpmp.gateway))); + + /* sendpublicaddressrequest() */ + r = sendpublicaddressrequest(&natpmp); + printf("sendpublicaddressrequest returned %d (%s)\n", + r, r==2?"SUCCESS":"FAILED"); + if(r<0) + return 1; + + do { + FD_ZERO(&fds); + FD_SET(natpmp.s, &fds); + getnatpmprequesttimeout(&natpmp, &timeout); + r = select(FD_SETSIZE, &fds, NULL, NULL, &timeout); + if(r<0) { + fprintf(stderr, "select()"); + return 1; + } + r = readnatpmpresponseorretry(&natpmp, &response); + sav_errno = errno; + printf("readnatpmpresponseorretry returned %d (%s)\n", + r, r==0?"OK":(r==NATPMP_TRYAGAIN?"TRY AGAIN":"FAILED")); + if(r<0 && r!=NATPMP_TRYAGAIN) { +#ifdef ENABLE_STRNATPMPERR + fprintf(stderr, "readnatpmpresponseorretry() failed : %s\n", + strnatpmperr(r)); +#endif + fprintf(stderr, " errno=%d '%s'\n", + sav_errno, strerror(sav_errno)); + } + } while(r==NATPMP_TRYAGAIN); + if(r<0) + return 1; + + /* TODO : check that response.type == 0 */ + printf("Public IP address : %s\n", inet_ntoa(response.pnu.publicaddress.addr)); + printf("epoch = %u\n", response.epoch); + + if(command == 'a') { + /* sendnewportmappingrequest() */ + r = sendnewportmappingrequest(&natpmp, protocol, + privateport, publicport, + lifetime); + printf("sendnewportmappingrequest returned %d (%s)\n", + r, r==12?"SUCCESS":"FAILED"); + if(r < 0) + return 1; + + do { + FD_ZERO(&fds); + FD_SET(natpmp.s, &fds); + getnatpmprequesttimeout(&natpmp, &timeout); + select(FD_SETSIZE, &fds, NULL, NULL, &timeout); + r = readnatpmpresponseorretry(&natpmp, &response); + printf("readnatpmpresponseorretry returned %d (%s)\n", + r, r==0?"OK":(r==NATPMP_TRYAGAIN?"TRY AGAIN":"FAILED")); + } while(r==NATPMP_TRYAGAIN); + if(r<0) { +#ifdef ENABLE_STRNATPMPERR + fprintf(stderr, "readnatpmpresponseorretry() failed : %s\n", + strnatpmperr(r)); +#endif + return 1; + } + + printf("Mapped public port %hu protocol %s to local port %hu " + "liftime %u\n", + response.pnu.newportmapping.mappedpublicport, + response.type == NATPMP_RESPTYPE_UDPPORTMAPPING ? "UDP" : + (response.type == NATPMP_RESPTYPE_TCPPORTMAPPING ? "TCP" : + "UNKNOWN"), + response.pnu.newportmapping.privateport, + response.pnu.newportmapping.lifetime); + printf("epoch = %u\n", response.epoch); + } + + r = closenatpmp(&natpmp); + printf("closenatpmp() returned %d (%s)\n", r, r==0?"SUCCESS":"FAILED"); + if(r<0) + return 1; + + return 0; +} + diff --git a/3rdParty/LibNATPMP/src/libnatpmp/wingettimeofday.c b/3rdParty/LibNATPMP/src/libnatpmp/wingettimeofday.c new file mode 100644 index 0000000..5b1b8a6 --- /dev/null +++ b/3rdParty/LibNATPMP/src/libnatpmp/wingettimeofday.c @@ -0,0 +1,50 @@ +/* $Id: wingettimeofday.c,v 1.3 2009/12/19 12:00:00 nanard Exp $ */ +/* libnatpmp + * Copyright (c) 2007-2008, Thomas BERNARD <miniupnp@free.fr> + * http://miniupnp.free.fr/libnatpmp.html + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#ifdef WIN32 +#if defined(_MSC_VER) +struct timeval { + long tv_sec; + long tv_usec; +}; +#else +#include <sys/time.h> +#endif + +typedef struct _FILETIME { + unsigned long dwLowDateTime; + unsigned long dwHighDateTime; +} FILETIME; + +void __stdcall GetSystemTimeAsFileTime(FILETIME*); + +//int gettimeofday(struct timeval* p, void* tz /* IGNORED */); + +int gettimeofday(struct timeval* p, void* tz /* IGNORED */) { + union { + long long ns100; /*time since 1 Jan 1601 in 100ns units */ + FILETIME ft; + } _now; + + if(!p) + return -1; + GetSystemTimeAsFileTime( &(_now.ft) ); + p->tv_usec =(long)((_now.ns100 / 10LL) % 1000000LL ); + p->tv_sec = (long)((_now.ns100-(116444736000000000LL))/10000000LL); + return 0; +} +#endif + diff --git a/3rdParty/LibNATPMP/src/libnatpmp/wingettimeofday.h b/3rdParty/LibNATPMP/src/libnatpmp/wingettimeofday.h new file mode 100644 index 0000000..ed6c599 --- /dev/null +++ b/3rdParty/LibNATPMP/src/libnatpmp/wingettimeofday.h @@ -0,0 +1,27 @@ +/* $Id: wingettimeofday.h,v 1.1 2009/12/19 12:02:42 nanard Exp $ */ +/* libnatpmp + * Copyright (c) 2007-2008, Thomas BERNARD <miniupnp@free.fr> + * http://miniupnp.free.fr/libnatpmp.html + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#ifndef __WINGETTIMEOFDAY_H__ +#define __WINGETTIMEOFDAY_H__ +#ifdef WIN32 +#if defined(_MSC_VER) +#include <time.h> +#else +#include <sys/time.h> +#endif +int gettimeofday(struct timeval* p, void* tz /* IGNORED */); +#endif +#endif diff --git a/BuildTools/SCons/SConscript.boot b/BuildTools/SCons/SConscript.boot index e969def..77b1cf1 100644 --- a/BuildTools/SCons/SConscript.boot +++ b/BuildTools/SCons/SConscript.boot @@ -180,7 +180,7 @@ else : if gccVersion >= ["4", "5", "0"] : env.Append(CXXFLAGS = ["-Wlogical-op"]) if "clang" in env["CC"] : - env.Append(CXXFLAGS = ["-W#warnings", "-W-Wc++0x-compat", "-Wc++0x-compat", "-Waddress-of-temporary", "-Wambiguous-member-template", "-Warray-bounds", "-Watomic-properties", "-Wbind-to-temporary-copy", "-Wbuiltin-macro-redefined", "-Wc++-compat", "-Wc++0x-extensions", "-Wcomments", "-Wconditional-uninitialized", "-Wconstant-logical-operand", "-Wdeclaration-after-statement", "-Wdeprecated", "-Wdeprecated-implementations", "-Wdeprecated-writable-strings", "-Wduplicate-method-arg", "-Wempty-body", "-Wendif-labels", "-Wenum-compare", "-Wformat=2", "-Wfour-char-constants", "-Wgnu", "-Wincomplete-implementation", "-Winvalid-noreturn", "-Winvalid-offsetof", "-Winvalid-token-paste", "-Wlocal-type-template-args", "-Wmethod-signatures", "-Wmicrosoft", "-Wmissing-declarations", "-Wnon-pod-varargs", "-Wnonfragile-abi2", "-Wnull-dereference", "-Wout-of-line-declaration", "-Woverlength-strings", "-Wpacked", "-Wpointer-arith", "-Wpointer-sign", "-Wprotocol", "-Wreadonly-setter-attrs", "-Wselector", "-Wshift-overflow", "-Wshift-sign-overflow", "-Wstrict-selector-match", "-Wsuper-class-method-mismatch", "-Wtautological-compare", "-Wtypedef-redefinition", "-Wundeclared-selector", "-Wunknown-attributes", "-Wunknown-warning-option", "-Wunnamed-type-template-args", "-Wunused-exception-parameter", "-Wunused-member-function", "-Wused-but-marked-unused", "-Wvariadic-macros"]) + env.Append(CXXFLAGS = ["-W#warnings", "-Wc++0x-compat", "-Waddress-of-temporary", "-Wambiguous-member-template", "-Warray-bounds", "-Watomic-properties", "-Wbind-to-temporary-copy", "-Wbuiltin-macro-redefined", "-Wc++-compat", "-Wc++0x-extensions", "-Wcomments", "-Wconditional-uninitialized", "-Wconstant-logical-operand", "-Wdeclaration-after-statement", "-Wdeprecated", "-Wdeprecated-implementations", "-Wdeprecated-writable-strings", "-Wduplicate-method-arg", "-Wempty-body", "-Wendif-labels", "-Wenum-compare", "-Wformat=2", "-Wfour-char-constants", "-Wgnu", "-Wincomplete-implementation", "-Winvalid-noreturn", "-Winvalid-offsetof", "-Winvalid-token-paste", "-Wlocal-type-template-args", "-Wmethod-signatures", "-Wmicrosoft", "-Wmissing-declarations", "-Wnon-pod-varargs", "-Wnonfragile-abi2", "-Wnull-dereference", "-Wout-of-line-declaration", "-Woverlength-strings", "-Wpacked", "-Wpointer-arith", "-Wpointer-sign", "-Wprotocol", "-Wreadonly-setter-attrs", "-Wselector", "-Wshift-overflow", "-Wshift-sign-overflow", "-Wstrict-selector-match", "-Wsuper-class-method-mismatch", "-Wtautological-compare", "-Wtypedef-redefinition", "-Wundeclared-selector", "-Wunknown-warning-option", "-Wunnamed-type-template-args", "-Wunused-exception-parameter", "-Wunused-member-function", "-Wused-but-marked-unused", "-Wvariadic-macros"]) # To enable: # "-Wheader-hygiene" # "-Wnon-gcc", @@ -193,7 +193,7 @@ if env.get("coverage", 0) : env.Append(LINKFLAGS = ["-fprofile-arcs", "-ftest-coverage"]) if env["PLATFORM"] == "win32" : - env.Append(LIBS = ["user32", "crypt32", "dnsapi", "ws2_32", "wsock32", "Advapi32"]) + env.Append(LIBS = ["user32", "crypt32", "dnsapi", "iphlpapi", "ws2_32", "wsock32", "Advapi32"]) env.Append(CCFLAGS = ["/EHsc", "/nologo"]) # FIXME: We should find a decent solution for MSVS 10 if int(env["MSVS_VERSION"].split(".")[0]) < 10 : diff --git a/BuildTools/SCons/SConstruct b/BuildTools/SCons/SConstruct index 88a195a..ba2f4b7 100644 --- a/BuildTools/SCons/SConstruct +++ b/BuildTools/SCons/SConstruct @@ -311,6 +311,27 @@ else : env["LIBIDN_BUNDLED"] = 1 conf.Finish() +# LibMiniUPnPc +libminiupnpc_conf_env = conf_env.Clone() + +conf = Configure(libminiupnpc_conf_env) +if conf.CheckCHeader("miniupnpc.h") and conf.CheckLib(env["libminiupnpc_libname"]) : + print "NOT IMPLEMENTED YET" +else : + env["LIBMINIUPNPC_BUNDLED"] = 1 +conf.Finish() + +# LibNATPMP +libnatpmp_conf_env = conf_env.Clone() + +conf = Configure(libnatpmp_conf_env) +if conf.CheckCHeader("natpmp.h") and conf.CheckLib(env["libnatpmp_libname"]) : + print "NOT IMPLEMENTED YET" +else : + env["LIBNATPMP_BUNDLED"] = 1 +conf.Finish() + + # SQLite #sqlite_conf_env = conf_env.Clone() #sqlite_flags = {} diff --git a/QA/Checker/IO.cpp b/QA/Checker/IO.cpp index d9eadd6..41fe5d3 100644 --- a/QA/Checker/IO.cpp +++ b/QA/Checker/IO.cpp @@ -6,6 +6,7 @@ #include <QA/Checker/IO.h> +#include <algorithm> #include <iostream> std::ostream& operator<<(std::ostream& os, const Swift::ByteArray& s) { @@ -55,3 +56,10 @@ std::ostream& operator<<(std::ostream& os, const std::vector<size_t>& s) { os << std::endl; return os; } + +bool operator==(const Swift::ByteArray& a, const Swift::ByteArray& b) { + if (a.size() != b.size()) { + return false; + } + return std::equal(a.begin(), a.end(), b.begin()); +} diff --git a/QA/Checker/IO.h b/QA/Checker/IO.h index 2545d24..3e40f7e 100644 --- a/QA/Checker/IO.h +++ b/QA/Checker/IO.h @@ -10,6 +10,7 @@ #include <Swiften/Base/SafeByteArray.h> std::ostream& operator<<(std::ostream& os, const Swift::ByteArray& s); +bool operator==(const Swift::ByteArray& a, const Swift::ByteArray& b); std::ostream& operator<<(std::ostream& os, const Swift::SafeByteArray& s); std::ostream& operator<<(std::ostream& os, const std::vector<int>& s); std::ostream& operator<<(std::ostream& os, const std::vector<size_t>& s); diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp index f4aa745..a3d9fb5 100644 --- a/Swift/Controllers/Chat/ChatController.cpp +++ b/Swift/Controllers/Chat/ChatController.cpp @@ -20,8 +20,13 @@ #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> #include <Swiften/Client/NickResolver.h> #include <Swift/Controllers/XMPPEvents/EventController.h> +#include <Swift/Controllers/FileTransfer/FileTransferController.h> #include <Swift/Controllers/StatusUtil.h> #include <Swiften/Disco/EntityCapsProvider.h> +#include <Swiften/Base/foreach.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIEvents/SendFileUIEvent.h> + namespace Swift { @@ -29,7 +34,7 @@ namespace Swift { * The controller does not gain ownership of the stanzaChannel, nor the factory. */ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider) - : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider) { + : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider), eventStream_(eventStream) { isInMUC_ = isInMUC; lastWasPresence_ = false; chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider); @@ -60,6 +65,10 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ chatWindow_->addSystemMessage(startMessage); chatWindow_->onUserTyping.connect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_)); chatWindow_->onUserCancelsTyping.connect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_)); + chatWindow_->onFileTransferStart.connect(boost::bind(&ChatController::handleFileTransferStart, this, _1, _2)); + chatWindow_->onFileTransferAccept.connect(boost::bind(&ChatController::handleFileTransferAccept, this, _1, _2)); + chatWindow_->onFileTransferCancel.connect(boost::bind(&ChatController::handleFileTransferCancel, this, _1)); + chatWindow_->onSendFileRequest.connect(boost::bind(&ChatController::handleSendFileRequest, this, _1)); handleBareJIDCapsChanged(toJID_); } @@ -171,6 +180,45 @@ void ChatController::setOnline(bool online) { ChatControllerBase::setOnline(online); } +void ChatController::handleNewFileTransferController(FileTransferController* ftc) { + std::string nick = senderDisplayNameFromMessage(ftc->getOtherParty()); + std::string ftID = ftc->setChatWindow(chatWindow_, nick); + + ftControllers[ftID] = ftc; +} + +void ChatController::handleFileTransferCancel(std::string id) { + std::cout << "handleFileTransferCancel(" << id << ")" << std::endl; + if (ftControllers.find(id) != ftControllers.end()) { + ftControllers[id]->cancel(); + } else { + std::cerr << "unknown file transfer UI id" << std::endl; + } +} + +void ChatController::handleFileTransferStart(std::string id, std::string description) { + std::cout << "handleFileTransferStart(" << id << ", " << description << ")" << std::endl; + if (ftControllers.find(id) != ftControllers.end()) { + ftControllers[id]->start(description); + } else { + std::cerr << "unknown file transfer UI id" << std::endl; + } +} + +void ChatController::handleFileTransferAccept(std::string id, std::string filename) { + std::cout << "handleFileTransferAccept(" << id << ", " << filename << ")" << std::endl; + if (ftControllers.find(id) != ftControllers.end()) { + ftControllers[id]->accept(filename); + } else { + std::cerr << "unknown file transfer UI id" << std::endl; + } +} + +void ChatController::handleSendFileRequest(std::string filename) { + std::cout << "ChatController::handleSendFileRequest(" << filename << ")" << std::endl; + eventStream_->send(boost::make_shared<SendFileUIEvent>(getToJID(), filename)); +} + std::string ChatController::senderDisplayNameFromMessage(const JID& from) { return nickResolver_->jidToNick(from); } diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h index f6b8763..2531adb 100644 --- a/Swift/Controllers/Chat/ChatController.h +++ b/Swift/Controllers/Chat/ChatController.h @@ -8,12 +8,16 @@ #include "Swift/Controllers/Chat/ChatControllerBase.h" +#include <map> +#include <string> + namespace Swift { class AvatarManager; class ChatStateNotifier; class ChatStateTracker; class NickResolver; class EntityCapsProvider; + class FileTransferController; class ChatController : public ChatControllerBase { public: @@ -21,6 +25,7 @@ namespace Swift { virtual ~ChatController(); virtual void setToJID(const JID& jid); virtual void setOnline(bool online); + virtual void handleNewFileTransferController(FileTransferController* ftc); private: void handlePresenceChange(boost::shared_ptr<Presence> newPresence); @@ -37,6 +42,11 @@ namespace Swift { void handleContactNickChanged(const JID& jid, const std::string& /*oldNick*/); void handleBareJIDCapsChanged(const JID& jid); + void handleFileTransferCancel(std::string /* id */); + void handleFileTransferStart(std::string /* id */, std::string /* description */); + void handleFileTransferAccept(std::string /* id */, std::string /* filename */); + void handleSendFileRequest(std::string filename); + private: NickResolver* nickResolver_; ChatStateNotifier* chatStateNotifier_; @@ -47,6 +57,9 @@ namespace Swift { std::string lastStatusChangeString_; std::map<boost::shared_ptr<Stanza>, std::string> unackedStanzas_; StatusShow::Type lastShownStatus_; + UIEventStream* eventStream_; + + std::map<std::string, FileTransferController*> ftControllers; }; } diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp index d631494..c61479c 100644 --- a/Swift/Controllers/Chat/ChatsManager.cpp +++ b/Swift/Controllers/Chat/ChatsManager.cpp @@ -29,6 +29,8 @@ #include <Swiften/MUC/MUCManager.h> #include <Swiften/Elements/ChatState.h> #include <Swiften/MUC/MUCBookmarkManager.h> +#include <Swift/Controllers/FileTransfer/FileTransferController.h> +#include <Swift/Controllers/FileTransfer/FileTransferOverview.h> #include <Swift/Controllers/ProfileSettingsProvider.h> #include <Swiften/Avatars/AvatarManager.h> @@ -56,13 +58,15 @@ ChatsManager::ChatsManager( EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, - ProfileSettingsProvider* settings) : + ProfileSettingsProvider* settings, + FileTransferOverview* ftOverview) : jid_(jid), joinMUCWindowFactory_(joinMUCWindowFactory), useDelayForLatency_(useDelayForLatency), mucRegistry_(mucRegistry), entityCapsProvider_(entityCapsProvider), - mucManager(mucManager) { + mucManager(mucManager), + ftOverview_(ftOverview) { timerFactory_ = timerFactory; eventController_ = eventController; stanzaChannel_ = stanzaChannel; @@ -86,6 +90,7 @@ ChatsManager::ChatsManager( joinMUCWindow_ = NULL; mucSearchController_ = new MUCSearchController(jid_, mucSearchWindowFactory, iqRouter, settings); mucSearchController_->onMUCSelected.connect(boost::bind(&ChatsManager::handleMUCSelectedAfterSearch, this, _1)); + ftOverview_->onNewFileTransferController.connect(boost::bind(&ChatsManager::handleNewFileTransferController, this, _1)); setupBookmarks(); loadRecents(); } @@ -535,6 +540,12 @@ void ChatsManager::handleMUCBookmarkActivated(const MUCBookmark& mucBookmark) { uiEventStream_->send(boost::make_shared<JoinMUCUIEvent>(mucBookmark.getRoom(), mucBookmark.getNick())); } +void ChatsManager::handleNewFileTransferController(FileTransferController* ftc) { + ChatController* chatController = getChatControllerOrCreate(ftc->getOtherParty()); + chatController->handleNewFileTransferController(ftc); + chatController->activateChatWindow(); +} + void ChatsManager::handleRecentActivated(const ChatListWindow::Chat& chat) { if (chat.isMUC) { uiEventStream_->send(boost::make_shared<JoinMUCUIEvent>(chat.jid, chat.nick)); diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h index b4db523..46c104d 100644 --- a/Swift/Controllers/Chat/ChatsManager.h +++ b/Swift/Controllers/Chat/ChatsManager.h @@ -43,10 +43,12 @@ namespace Swift { class MUCSearchWindowFactory; class ProfileSettingsProvider; class MUCSearchController; - + class FileTransferOverview; + class FileTransferController; + class ChatsManager { public: - ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* settings); + ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* settings, FileTransferOverview* ftOverview); virtual ~ChatsManager(); void setAvatarManager(AvatarManager* avatarManager); void setOnline(bool enabled); @@ -67,6 +69,7 @@ namespace Swift { void handleUserLeftMUC(MUCController* mucController); void handleBookmarksReady(); void handleChatActivity(const JID& jid, const std::string& activity, bool isMUC); + void handleNewFileTransferController(FileTransferController*); void appendRecent(const ChatListWindow::Chat& chat); void prependRecent(const ChatListWindow::Chat& chat); void setupBookmarks(); @@ -110,5 +113,6 @@ namespace Swift { MUCSearchController* mucSearchController_; std::list<ChatListWindow::Chat> recentChats_; ProfileSettingsProvider* profileSettings_; + FileTransferOverview* ftOverview_; }; } diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp index b8cb368..5339703 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp @@ -8,6 +8,8 @@ #include <cppunit/extensions/TestFactoryRegistry.h> #include "3rdParty/hippomocks.h" +#include <boost/bind.hpp> + #include "Swift/Controllers/Chat/ChatsManager.h" #include "Swift/Controllers/Chat/UnitTest/MockChatListWindow.h" @@ -36,11 +38,14 @@ #include "Swiften/Client/DummyStanzaChannel.h" #include "Swiften/Queries/DummyIQChannel.h" #include "Swiften/Presence/PresenceOracle.h" +#include "Swiften/Jingle/JingleSessionManager.h" +#include "Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h" #include "Swift/Controllers/UIEvents/RequestChatUIEvent.h" #include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h" #include "Swift/Controllers/UIEvents/UIEventStream.h" #include <Swift/Controllers/ProfileSettingsProvider.h> - +#include "Swift/Controllers/FileTransfer/FileTransferOverview.h" +#include <Swiften/Base/Algorithm.h> using namespace Swift; @@ -86,19 +91,23 @@ public: settings_ = new DummySettingsProvider(); profileSettings_ = new ProfileSettingsProvider("a", settings_); chatListWindow_ = new MockChatListWindow(); + ftManager_ = new DummyFileTransferManager(); + ftOverview_ = new FileTransferOverview(ftManager_); mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(chatListWindow_); - manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_); + manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_); avatarManager_ = new NullAvatarManager(); manager_->setAvatarManager(avatarManager_); }; void tearDown() { - //delete chatListWindowFactory_; + //delete chatListWindowFactory delete settings_; delete profileSettings_; delete avatarManager_; delete manager_; + delete ftOverview_; + delete ftManager_; delete directedPresenceSender_; delete presenceSender_; delete presenceOracle_; @@ -354,6 +363,8 @@ private: DummySettingsProvider* settings_; ProfileSettingsProvider* profileSettings_; ChatListWindow* chatListWindow_; + FileTransferOverview* ftOverview_; + FileTransferManager* ftManager_; }; CPPUNIT_TEST_SUITE_REGISTRATION(ChatsManagerTest); diff --git a/Swift/Controllers/FileTransfer/FileTransferController.cpp b/Swift/Controllers/FileTransfer/FileTransferController.cpp new file mode 100644 index 0000000..afa907d --- /dev/null +++ b/Swift/Controllers/FileTransfer/FileTransferController.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "FileTransferController.h" +#include "Swiften/FileTransfer/OutgoingJingleFileTransfer.h" +#include "Swiften/FileTransfer/FileTransferManager.h" +#include <Swiften/FileTransfer/FileReadBytestream.h> +#include <Swiften/Base/boost_bsignals.h> +#include <boost/bind.hpp> +#include "Swift/Controllers/UIInterfaces/ChatWindow.h" + +#include <boost/smart_ptr/make_shared.hpp> + +namespace Swift { + +FileTransferController::FileTransferController(const JID& receipient, const std::string& filename, FileTransferManager* fileTransferManager) : + sending(true), otherParty(receipient), filename(filename), ftManager(fileTransferManager), ftProgressInfo(0), chatWindow(0), currentState(FileTransfer::State::WaitingForStart) { + +} + +FileTransferController::FileTransferController(IncomingFileTransfer::ref transfer) : + sending(false), otherParty(transfer->getSender()), filename(transfer->filename), transfer(transfer), ftManager(0), ftProgressInfo(0), chatWindow(0), currentState(FileTransfer::State::WaitingForStart) { + +} + +FileTransferController::~FileTransferController() { + delete ftProgressInfo; +} + +const JID &FileTransferController::getOtherParty() const { + return otherParty; +} + +std::string FileTransferController::setChatWindow(ChatWindow* wnd, std::string nickname) { + chatWindow = wnd; + if (sending) { + uiID = wnd->addFileTransfer("me", true, filename, boost::filesystem::file_size(boost::filesystem::path(filename))); + } else { + uiID = wnd->addFileTransfer(nickname, false, filename, transfer->fileSizeInBytes); + } + return uiID; +} + +void FileTransferController::setReceipient(const JID& receipient) { + this->otherParty = receipient; +} + +bool FileTransferController::isIncoming() const { + return !sending; +} + +FileTransfer::State FileTransferController::getState() const { + return currentState; +} + +int FileTransferController::getProgress() const { + return ftProgressInfo ? ftProgressInfo->getPercentage() : 0; +} + +boost::uintmax_t FileTransferController::getSize() const { + if (transfer) { + return transfer->fileSizeInBytes; + } else { + return 0; + } +} + +void FileTransferController::start(std::string& description) { + std::cout << "FileTransferController::start" << std::endl; + fileReadStream = boost::make_shared<FileReadBytestream>(boost::filesystem::path(filename)); + OutgoingFileTransfer::ref outgoingTransfer = ftManager->createOutgoingFileTransfer(otherParty, boost::filesystem::path(filename), description, fileReadStream); + if (outgoingTransfer) { + ftProgressInfo = new FileTransferProgressInfo(outgoingTransfer->fileSizeInBytes); + ftProgressInfo->onProgressPercentage.connect(boost::bind(&FileTransferController::handleProgressPercentageChange, this, _1)); + outgoingTransfer->onStateChange.connect(boost::bind(&FileTransferController::handleFileTransferStateChange, this, _1)); + outgoingTransfer->onProcessedBytes.connect(boost::bind(&FileTransferProgressInfo::setBytesProcessed, ftProgressInfo, _1)); + outgoingTransfer->start(); + transfer = outgoingTransfer; + } else { + std::cerr << "File transfer not supported!" << std::endl; + } +} + +void FileTransferController::accept(std::string& file) { + std::cout << "FileTransferController::accept" << std::endl; + IncomingFileTransfer::ref incomingTransfer = boost::dynamic_pointer_cast<IncomingFileTransfer>(transfer); + if (incomingTransfer) { + fileWriteStream = boost::make_shared<FileWriteBytestream>(boost::filesystem::path(file)); + + ftProgressInfo = new FileTransferProgressInfo(transfer->fileSizeInBytes); + ftProgressInfo->onProgressPercentage.connect(boost::bind(&FileTransferController::handleProgressPercentageChange, this, _1)); + transfer->onStateChange.connect(boost::bind(&FileTransferController::handleFileTransferStateChange, this, _1)); + transfer->onProcessedBytes.connect(boost::bind(&FileTransferProgressInfo::setBytesProcessed, ftProgressInfo, _1)); + incomingTransfer->accept(fileWriteStream); + } else { + std::cerr << "Expected an incoming transfer in this situation!" << std::endl; + } +} + +void FileTransferController::cancel() { + if (transfer) { + transfer->cancel(); + } else { + chatWindow->setFileTransferStatus(uiID, ChatWindow::Canceled); + } +} + +void FileTransferController::handleFileTransferStateChange(FileTransfer::State state) { + currentState = state; + onStateChage(); + switch(state.state) { + case FileTransfer::State::Negotiating: + chatWindow->setFileTransferStatus(uiID, ChatWindow::Negotiating); + return; + case FileTransfer::State::Transferring: + chatWindow->setFileTransferStatus(uiID, ChatWindow::Transferring); + return; + case FileTransfer::State::Canceled: + chatWindow->setFileTransferStatus(uiID, ChatWindow::Canceled); + return; + case FileTransfer::State::Finished: + chatWindow->setFileTransferStatus(uiID, ChatWindow::Finished); + if (fileWriteStream) { + fileWriteStream->close(); + } + return; + case FileTransfer::State::Failed: + chatWindow->setFileTransferStatus(uiID, ChatWindow::FTFailed); + return; + case FileTransfer::State::WaitingForAccept: + chatWindow->setFileTransferStatus(uiID, ChatWindow::WaitingForAccept); + return; + case FileTransfer::State::WaitingForStart: + return; + } + std::cerr << "Unhandled FileTransfer::State!" << std::endl; +} + +void FileTransferController::handleProgressPercentageChange(int percentage) { + onProgressChange(); + chatWindow->setFileTransferProgress(uiID, percentage); +} + +} diff --git a/Swift/Controllers/FileTransfer/FileTransferController.h b/Swift/Controllers/FileTransfer/FileTransferController.h new file mode 100644 index 0000000..5d98468 --- /dev/null +++ b/Swift/Controllers/FileTransfer/FileTransferController.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <string> + +#include <boost/shared_ptr.hpp> + +#include <Swiften/JID/JID.h> +#include <Swiften/FileTransfer/FileTransfer.h> +#include <Swiften/FileTransfer/IncomingFileTransfer.h> +#include <Swiften/FileTransfer/FileReadBytestream.h> +#include <Swiften/FileTransfer/FileWriteBytestream.h> +#include <Swift/Controllers/FileTransfer/FileTransferProgressInfo.h> + +namespace Swift { + +class FileTransferManager; +class ChatWindow; + +class FileTransferController { +public: + /** + * For outgoing file transfers. It'll create a file transfer via FileTransferManager as soon as the descriptive information is available. + */ + FileTransferController(const JID&, const std::string&, FileTransferManager*); + + /** + * For incoming file transfers. + */ + FileTransferController(IncomingFileTransfer::ref transfer); + ~FileTransferController(); + + std::string setChatWindow(ChatWindow*, std::string nickname); + void setReceipient(const JID& otherParty); + + void start(std::string& description); + void accept(std::string& file); + void cancel(); + + const JID &getOtherParty() const; + bool isIncoming() const; + FileTransfer::State getState() const; + int getProgress() const; + boost::uintmax_t getSize() const; + + boost::signal<void ()> onStateChage; + boost::signal<void ()> onProgressChange; + +private: + void handleFileTransferStateChange(FileTransfer::State); + void handleProgressPercentageChange(int percentage); + +private: + bool sending; + JID otherParty; + std::string filename; + FileTransfer::ref transfer; + boost::shared_ptr<FileReadBytestream> fileReadStream; + boost::shared_ptr<FileWriteBytestream> fileWriteStream; + FileTransferManager* ftManager; + FileTransferProgressInfo* ftProgressInfo; + ChatWindow* chatWindow; + std::string uiID; + FileTransfer::State currentState; +}; + +} diff --git a/Swift/Controllers/FileTransfer/FileTransferOverview.cpp b/Swift/Controllers/FileTransfer/FileTransferOverview.cpp new file mode 100644 index 0000000..c3ffc5c --- /dev/null +++ b/Swift/Controllers/FileTransfer/FileTransferOverview.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "FileTransferOverview.h" + +#include <boost/bind.hpp> +#include <Swiften/Base/boost_bsignals.h> + +#include <Swiften/FileTransfer/FileTransferManager.h> + +namespace Swift { + +FileTransferOverview::FileTransferOverview(FileTransferManager* ftm) : fileTransferManager(ftm) { + fileTransferManager->onIncomingFileTransfer.connect(boost::bind(&FileTransferOverview::handleIncomingFileTransfer, this, _1)); +} + +FileTransferOverview::~FileTransferOverview() { + +} + +void FileTransferOverview::sendFile(const JID& jid, const std::string& filename) { + FileTransferController* controller = new FileTransferController(jid, filename, fileTransferManager); + fileTransfers.push_back(controller); + + onNewFileTransferController(controller); +} + +void FileTransferOverview::handleIncomingFileTransfer(IncomingFileTransfer::ref transfer) { + FileTransferController* controller = new FileTransferController(transfer); + fileTransfers.push_back(controller); + onNewFileTransferController(controller); +} + +const std::vector<FileTransferController*>& FileTransferOverview::getFileTransfers() const { + return fileTransfers; +} + +} diff --git a/Swift/Controllers/FileTransfer/FileTransferOverview.h b/Swift/Controllers/FileTransfer/FileTransferOverview.h new file mode 100644 index 0000000..716666a --- /dev/null +++ b/Swift/Controllers/FileTransfer/FileTransferOverview.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <vector> + +#include "Swift/Controllers/FileTransfer/FileTransferController.h" + +#include <Swiften/Base/boost_bsignals.h> + +namespace Swift { + +class ChatsManager; +class FileTransferManager; + +class FileTransferOverview { +public: + FileTransferOverview(FileTransferManager*); + ~FileTransferOverview(); + + void sendFile(const JID&, const std::string&); + const std::vector<FileTransferController*>& getFileTransfers() const; + + boost::signal<void (FileTransferController*)> onNewFileTransferController; + +private: + void handleIncomingFileTransfer(IncomingFileTransfer::ref transfer); + +private: + std::vector<FileTransferController*> fileTransfers; + FileTransferManager *fileTransferManager; +}; + +} diff --git a/Swift/Controllers/FileTransfer/FileTransferProgressInfo.cpp b/Swift/Controllers/FileTransfer/FileTransferProgressInfo.cpp new file mode 100644 index 0000000..6d19fa1 --- /dev/null +++ b/Swift/Controllers/FileTransfer/FileTransferProgressInfo.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "FileTransferProgressInfo.h" + +#include <Swiften/Base/Log.h> + +namespace Swift { + +FileTransferProgressInfo::FileTransferProgressInfo(boost::uintmax_t completeBytes) : completeBytes(completeBytes), completedBytes(0), percentage(0) { + onProgressPercentage(0); +} + +void FileTransferProgressInfo::setBytesProcessed(int processedBytes) { + int oldPercentage = int(double(completedBytes) / double(completeBytes) * 100.0); + completedBytes += processedBytes; + int newPercentage = int(double(completedBytes) / double(completeBytes) * 100.0); + if (oldPercentage != newPercentage) { + onProgressPercentage(newPercentage); + } + percentage = newPercentage; +} + +int FileTransferProgressInfo::getPercentage() const { + return percentage; +} + +} diff --git a/Swift/Controllers/FileTransfer/FileTransferProgressInfo.h b/Swift/Controllers/FileTransfer/FileTransferProgressInfo.h new file mode 100644 index 0000000..bb3c0fc --- /dev/null +++ b/Swift/Controllers/FileTransfer/FileTransferProgressInfo.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Base/boost_bsignals.h> + +namespace Swift { + +class FileTransferProgressInfo { +public: + FileTransferProgressInfo(boost::uintmax_t completeBytes); + +public: + void setBytesProcessed(int processedBytes); + + int getPercentage() const; + boost::signal<void (int)> onProgressPercentage; + +private: + boost::uintmax_t completeBytes; + boost::uintmax_t completedBytes; + int percentage; +}; + +} diff --git a/Swift/Controllers/FileTransfer/SOCKS5BytestreamProxyFinder.cpp b/Swift/Controllers/FileTransfer/SOCKS5BytestreamProxyFinder.cpp new file mode 100644 index 0000000..da0606c --- /dev/null +++ b/Swift/Controllers/FileTransfer/SOCKS5BytestreamProxyFinder.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "SOCKS5BytestreamProxyFinder.h" + +#include <boost/smart_ptr/make_shared.hpp> +#include <boost/bind.hpp> + +#include <Swiften/Base/Log.h> +#include <Swiften/Elements/S5BProxyRequest.h> +#include <Swiften/Queries/GenericRequest.h> +#include <Swiften/Queries/IQRouter.h> + +namespace Swift { + +SOCKS5BytestreamProxyFinder::SOCKS5BytestreamProxyFinder(const JID& service, IQRouter *iqRouter) : iqRouter(iqRouter) { + serviceWalker = boost::make_shared<DiscoServiceWalker>(service, iqRouter); + serviceWalker->onServiceFound.connect(boost::bind(&SOCKS5BytestreamProxyFinder::handleServiceFound, this, _1, _2)); +} + +void SOCKS5BytestreamProxyFinder::start() { + serviceWalker->beginWalk(); +} + +void SOCKS5BytestreamProxyFinder::sendBytestreamQuery(const JID& jid) { + S5BProxyRequest::ref proxyRequest = boost::make_shared<S5BProxyRequest>(); + boost::shared_ptr<GenericRequest<S5BProxyRequest> > request = boost::make_shared<GenericRequest<S5BProxyRequest> >(IQ::Get, jid, proxyRequest, iqRouter); + request->onResponse.connect(boost::bind(&SOCKS5BytestreamProxyFinder::handleProxyResponse, this, _1, _2)); + request->send(); +} + +void SOCKS5BytestreamProxyFinder::handleServiceFound(const JID& jid, boost::shared_ptr<DiscoInfo> discoInfo) { + if (discoInfo->hasFeature(DiscoInfo::Bytestream)) { + sendBytestreamQuery(jid); + } +} + +void SOCKS5BytestreamProxyFinder::handleProxyResponse(boost::shared_ptr<S5BProxyRequest> request, ErrorPayload::ref error) { + if (error) { + SWIFT_LOG(debug) << "ERROR" << std::endl; + } else { + if (request) { + onProxyFound(request); + } else { + //assert(false); + } + } +} + +} diff --git a/Swift/Controllers/FileTransfer/SOCKS5BytestreamProxyFinder.h b/Swift/Controllers/FileTransfer/SOCKS5BytestreamProxyFinder.h new file mode 100644 index 0000000..1727a63 --- /dev/null +++ b/Swift/Controllers/FileTransfer/SOCKS5BytestreamProxyFinder.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> + +#include <Swift/Controllers/DiscoServiceWalker.h> +#include <Swiften/Network/HostAddressPort.h> +#include <Swiften/Elements/S5BProxyRequest.h> + +namespace Swift { + +class JID; +class IQRouter; + +class SOCKS5BytestreamProxyFinder { +public: + SOCKS5BytestreamProxyFinder(const JID& service, IQRouter *iqRouter); + void start(); + + boost::signal<void(boost::shared_ptr<S5BProxyRequest>)> onProxyFound; + +private: + void sendBytestreamQuery(const JID&); + + void handleServiceFound(const JID&, boost::shared_ptr<DiscoInfo>); + void handleProxyResponse(boost::shared_ptr<S5BProxyRequest>, ErrorPayload::ref); +private: + boost::shared_ptr<DiscoServiceWalker> serviceWalker; + IQRouter* iqRouter; + std::vector<boost::shared_ptr<GenericRequest<S5BProxyRequest> > > requests; +}; + +} diff --git a/Swift/Controllers/FileTransferListController.cpp b/Swift/Controllers/FileTransferListController.cpp new file mode 100644 index 0000000..093a3c4 --- /dev/null +++ b/Swift/Controllers/FileTransferListController.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "Swift/Controllers/FileTransferListController.h" + +#include <boost/bind.hpp> + +#include "Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h" +#include "Swift/Controllers/UIEvents/RequestFileTransferListUIEvent.h" + +namespace Swift { + +FileTransferListController::FileTransferListController(UIEventStream* uiEventStream, FileTransferListWidgetFactory* fileTransferListWidgetFactory) : fileTransferListWidgetFactory(fileTransferListWidgetFactory), fileTransferListWidget(NULL), fileTransferOverview(0) { + uiEventStream->onUIEvent.connect(boost::bind(&FileTransferListController::handleUIEvent, this, _1)); +} + +FileTransferListController::~FileTransferListController() { + delete fileTransferListWidget; +} + +void FileTransferListController::setFileTransferOverview(FileTransferOverview *overview) { + fileTransferOverview = overview; + if (fileTransferListWidget) { + fileTransferListWidget->setFileTransferOverview(fileTransferOverview); + } +} + +void FileTransferListController::handleUIEvent(boost::shared_ptr<UIEvent> rawEvent) { + boost::shared_ptr<RequestFileTransferListUIEvent> event = boost::dynamic_pointer_cast<RequestFileTransferListUIEvent>(rawEvent); + if (event != NULL) { + if (fileTransferListWidget == NULL) { + fileTransferListWidget = fileTransferListWidgetFactory->createFileTransferListWidget(); + if (fileTransferOverview) { + fileTransferListWidget->setFileTransferOverview(fileTransferOverview); + } + } + fileTransferListWidget->show(); + fileTransferListWidget->activate(); + } +} + +} diff --git a/Swift/Controllers/FileTransferListController.h b/Swift/Controllers/FileTransferListController.h new file mode 100644 index 0000000..c5c8893 --- /dev/null +++ b/Swift/Controllers/FileTransferListController.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> + +#include "Swift/Controllers/UIEvents/UIEventStream.h" + +namespace Swift { + +class FileTransferListWidgetFactory; +class FileTransferListWidget; +class FileTransferOverview; + +class FileTransferListController { +public: + FileTransferListController(UIEventStream* uiEventStream, FileTransferListWidgetFactory* fileTransferListWidgetFactory); + ~FileTransferListController(); + + void setFileTransferOverview(FileTransferOverview* overview); + +private: + void handleUIEvent(boost::shared_ptr<UIEvent> event); + +private: + FileTransferListWidgetFactory* fileTransferListWidgetFactory; + FileTransferListWidget* fileTransferListWidget; + FileTransferOverview* fileTransferOverview; +}; + +} diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index 24c5303..b84e7d6 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -37,6 +37,7 @@ #include "Swift/Controllers/SystemTray.h" #include "Swift/Controllers/SystemTrayController.h" #include "Swift/Controllers/XMLConsoleController.h" +#include "Swift/Controllers/FileTransferListController.h" #include "Swift/Controllers/UIEvents/UIEventStream.h" #include "Swift/Controllers/PresenceNotifier.h" #include "Swift/Controllers/EventNotifier.h" @@ -68,6 +69,9 @@ #include <Swift/Controllers/XMPPURIController.h> #include "Swift/Controllers/AdHocManager.h" #include <SwifTools/Idle/IdleDetector.h> +#include <Swift/Controllers/FileTransfer/FileTransferOverview.h> +#include <Swiften/FileTransfer/FileTransferManager.h> +#include <Swiften/Client/ClientXMLTracer.h> namespace Swift { @@ -101,7 +105,8 @@ MainController::MainController( idleDetector_(idleDetector), loginWindow_(NULL) , useDelayForLatency_(useDelayForLatency), - eagleMode_(eagleMode) { + eagleMode_(eagleMode), + ftOverview_(NULL) { storages_ = NULL; certificateStorage_ = NULL; statusTracker_ = NULL; @@ -164,6 +169,8 @@ MainController::MainController( xmlConsoleController_ = new XMLConsoleController(uiEventStream_, uiFactory_); + fileTransferListController_ = new FileTransferListController(uiEventStream_, uiFactory_); + uiEventStream_->onUIEvent.connect(boost::bind(&MainController::handleUIEvent, this, _1)); bool enabled = settings_->getBoolSetting(SHOW_NOTIFICATIONS, true); uiEventStream_->send(boost::shared_ptr<ToggleNotificationsUIEvent>(new ToggleNotificationsUIEvent(enabled))); @@ -184,7 +191,7 @@ MainController::~MainController() { eventController_->disconnectAll(); resetClient(); - + delete fileTransferListController_; delete xmlConsoleController_; delete xmppURIController_; delete soundEventController_; @@ -211,6 +218,10 @@ void MainController::resetClient() { eventWindowController_ = NULL; delete chatsManager_; chatsManager_ = NULL; + delete s5bProxyFinder_; + s5bProxyFinder_ = NULL; + delete ftOverview_; + ftOverview_ = NULL; delete rosterController_; rosterController_ = NULL; delete eventNotifier_; @@ -270,13 +281,22 @@ void MainController::handleConnected() { myStatusLooksOnline_ = true; if (freshLogin) { profileController_ = new ProfileController(client_->getVCardManager(), uiFactory_, uiEventStream_); - rosterController_ = new RosterController(jid_, client_->getRoster(), client_->getAvatarManager(), uiFactory_, client_->getNickManager(), client_->getNickResolver(), client_->getPresenceOracle(), client_->getSubscriptionManager(), eventController_, uiEventStream_, client_->getIQRouter(), settings_); + srand(time(NULL)); + int randomPort = 10000 + rand() % 10000; + client_->getFileTransferManager()->startListeningOnPort(randomPort); + s5bProxyFinder_ = new SOCKS5BytestreamProxyFinder(client_->getJID().getDomain(), client_->getIQRouter()); + s5bProxyFinder_->onProxyFound.connect(boost::bind(&FileTransferManager::addS5BProxy, client_->getFileTransferManager(), _1)); + s5bProxyFinder_->start(); + ftOverview_ = new FileTransferOverview(client_->getFileTransferManager()); + fileTransferListController_->setFileTransferOverview(ftOverview_); + rosterController_ = new RosterController(jid_, client_->getRoster(), client_->getAvatarManager(), uiFactory_, client_->getNickManager(), client_->getNickResolver(), client_->getPresenceOracle(), client_->getSubscriptionManager(), eventController_, uiEventStream_, client_->getIQRouter(), settings_, client_->getEntityCapsProvider(), ftOverview_); rosterController_->onChangeStatusRequest.connect(boost::bind(&MainController::handleChangeStatusRequest, this, _1, _2)); rosterController_->onSignOutRequest.connect(boost::bind(&MainController::signOut, this)); contactEditController_ = new ContactEditController(rosterController_, uiFactory_, uiEventStream_); - chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_); + chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_); + client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1)); chatsManager_->setAvatarManager(client_->getAvatarManager()); @@ -289,6 +309,10 @@ void MainController::handleConnected() { discoInfo.addFeature(DiscoInfo::ChatStatesFeature); discoInfo.addFeature(DiscoInfo::SecurityLabelsFeature); discoInfo.addFeature(DiscoInfo::MessageCorrectionFeature); + discoInfo.addFeature(DiscoInfo::JingleFeature); + discoInfo.addFeature(DiscoInfo::JingleFTFeature); + discoInfo.addFeature(DiscoInfo::JingleTransportsIBBFeature); + discoInfo.addFeature(DiscoInfo::JingleTransportsS5BFeature); client_->getDiscoManager()->setCapsNode(CLIENT_NODE); client_->getDiscoManager()->setDiscoInfo(discoInfo); diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h index 7ac6648..2aaa542 100644 --- a/Swift/Controllers/MainController.h +++ b/Swift/Controllers/MainController.h @@ -23,6 +23,8 @@ #include "Swiften/Elements/CapsInfo.h" #include "Swift/Controllers/XMPPEvents/ErrorEvent.h" #include "Swift/Controllers/UIEvents/UIEvent.h" +#include "Swift/Controllers/FileTransfer/SOCKS5BytestreamProxyFinder.h" +#include "Swiften/Client/ClientXMLTracer.h" namespace Swift { class IdleDetector; @@ -51,6 +53,7 @@ namespace Swift { class SoundEventController; class SoundPlayer; class XMLConsoleController; + class FileTransferListController; class UIEventStream; class EventWindowFactory; class EventWindowController; @@ -65,6 +68,7 @@ namespace Swift { class XMPPURIController; class AdHocManager; class AdHocCommandWindowFactory; + class FileTransferOverview; class MainController { public: @@ -140,6 +144,7 @@ namespace Swift { LoginWindow* loginWindow_; UIEventStream* uiEventStream_; XMLConsoleController* xmlConsoleController_; + FileTransferListController* fileTransferListController_; ChatsManager* chatsManager_; ProfileController* profileController_; ContactEditController* contactEditController_; @@ -162,5 +167,7 @@ namespace Swift { bool quitRequested_; static const int SecondsToWaitBeforeForceQuitting; bool eagleMode_; + FileTransferOverview* ftOverview_; + SOCKS5BytestreamProxyFinder* s5bProxyFinder_; }; } diff --git a/Swift/Controllers/Roster/ContactRosterItem.cpp b/Swift/Controllers/Roster/ContactRosterItem.cpp index bbe81e6..6d707bb 100644 --- a/Swift/Controllers/Roster/ContactRosterItem.cpp +++ b/Swift/Controllers/Roster/ContactRosterItem.cpp @@ -113,6 +113,14 @@ void ContactRosterItem::removeGroup(const std::string& group) { groups_.erase(std::remove(groups_.begin(), groups_.end(), group), groups_.end()); } +void ContactRosterItem::setSupportedFeatures(const std::set<Feature>& features) { + features_ = features; +} + +bool ContactRosterItem::supportsFeature(const Feature feat) const { + return features_.find(feat) != features_.end(); +} + } diff --git a/Swift/Controllers/Roster/ContactRosterItem.h b/Swift/Controllers/Roster/ContactRosterItem.h index 7aa948c..67ffaa9 100644 --- a/Swift/Controllers/Roster/ContactRosterItem.h +++ b/Swift/Controllers/Roster/ContactRosterItem.h @@ -13,6 +13,7 @@ #include "Swiften/Elements/Presence.h" #include <map> +#include <set> #include <boost/bind.hpp> #include "Swiften/Base/boost_bsignals.h" #include <boost/shared_ptr.hpp> @@ -22,6 +23,11 @@ namespace Swift { class GroupRosterItem; class ContactRosterItem : public RosterItem { public: + enum Feature { + FileTransferFeature, + }; + + public: ContactRosterItem(const JID& jid, const JID& displayJID, const std::string& name, GroupRosterItem* parent); virtual ~ContactRosterItem(); @@ -40,6 +46,9 @@ class ContactRosterItem : public RosterItem { /** Only used so a contact can know about the groups it's in*/ void addGroup(const std::string& group); void removeGroup(const std::string& group); + + void setSupportedFeatures(const std::set<Feature>& features); + bool supportsFeature(Feature feat) const; private: JID jid_; JID displayJID_; @@ -48,6 +57,7 @@ class ContactRosterItem : public RosterItem { boost::shared_ptr<Presence> offlinePresence_; boost::shared_ptr<Presence> shownPresence_; std::vector<std::string> groups_; + std::set<Feature> features_; }; } diff --git a/Swift/Controllers/Roster/Roster.cpp b/Swift/Controllers/Roster/Roster.cpp index a62a18a..f3d058e 100644 --- a/Swift/Controllers/Roster/Roster.cpp +++ b/Swift/Controllers/Roster/Roster.cpp @@ -17,6 +17,7 @@ #include <boost/bind.hpp> #include <iostream> +#include <set> #include <deque> namespace Swift { @@ -60,6 +61,13 @@ GroupRosterItem* Roster::getGroup(const std::string& groupName) { return group; } +void Roster::setAvailableFeatures(const JID& jid, const std::set<ContactRosterItem::Feature>& features) { + if (itemMap_[fullJIDMapping_ ? jid : jid.toBare()].empty()) return; + foreach(ContactRosterItem* item, itemMap_[fullJIDMapping_ ? jid : jid.toBare()]) { + item->setSupportedFeatures(features); + } +} + void Roster::removeGroup(const std::string& group) { root_->removeGroupChild(group); } @@ -74,7 +82,7 @@ void Roster::handleChildrenChanged(GroupRosterItem* item) { void Roster::addContact(const JID& jid, const JID& displayJID, const std::string& name, const std::string& groupName, const std::string& avatarPath) { GroupRosterItem* group(getGroup(groupName)); - ContactRosterItem *item = new ContactRosterItem(jid, displayJID, name, group); + ContactRosterItem *item = new ContactRosterItem(jid, displayJID, name, group); item->setAvatarPath(avatarPath); group->addChild(item); if (itemMap_[fullJIDMapping_ ? jid : jid.toBare()].size() > 0) { diff --git a/Swift/Controllers/Roster/Roster.h b/Swift/Controllers/Roster/Roster.h index 53161a8..2b4dd27 100644 --- a/Swift/Controllers/Roster/Roster.h +++ b/Swift/Controllers/Roster/Roster.h @@ -10,6 +10,7 @@ #include "Swiften/JID/JID.h" #include "Swift/Controllers/Roster/RosterItemOperation.h" #include "Swift/Controllers/Roster/RosterFilter.h" +#include <Swift/Controllers/Roster/ContactRosterItem.h> #include <vector> #include <map> @@ -43,6 +44,8 @@ class Roster { boost::signal<void (GroupRosterItem*)> onGroupAdded; boost::signal<void (RosterItem*)> onDataChanged; GroupRosterItem* getGroup(const std::string& groupName); + void setAvailableFeatures(const JID& jid, const std::set<ContactRosterItem::Feature>& features); + private: void handleDataChanged(RosterItem* item); void handleChildrenChanged(GroupRosterItem* item); diff --git a/Swift/Controllers/Roster/RosterController.cpp b/Swift/Controllers/Roster/RosterController.cpp index 5b61abf..66948c1 100644 --- a/Swift/Controllers/Roster/RosterController.cpp +++ b/Swift/Controllers/Roster/RosterController.cpp @@ -36,9 +36,14 @@ #include "Swift/Controllers/UIEvents/RenameRosterItemUIEvent.h" #include "Swift/Controllers/UIEvents/RenameGroupUIEvent.h" #include "Swift/Controllers/UIEvents/ToggleShowOfflineUIEvent.h" +#include "Swift/Controllers/UIEvents/SendFileUIEvent.h" +#include <Swiften/FileTransfer/FileTransferManager.h> #include <Swiften/Client/NickManager.h> #include <Swift/Controllers/Intl.h> #include <Swiften/Base/format.h> +#include <Swiften/Elements/DiscoInfo.h> +#include <Swiften/Disco/EntityCapsManager.h> +#include <Swiften/Jingle/JingleSessionManager.h> namespace Swift { @@ -47,8 +52,9 @@ static const std::string SHOW_OFFLINE = "showOffline"; /** * The controller does not gain ownership of these parameters. */ -RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter, SettingsProvider* settings) - : myJID_(jid), xmppRoster_(xmppRoster), mainWindowFactory_(mainWindowFactory), mainWindow_(mainWindowFactory_->createMainWindow(uiEventStream)), roster_(new Roster()), offlineFilter_(new OfflineRosterFilter()), nickManager_(nickManager), nickResolver_(nickResolver), uiEventStream_(uiEventStream) { +RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter, SettingsProvider* settings, EntityCapsProvider* entityCapsManager, FileTransferOverview* fileTransferOverview) + : myJID_(jid), xmppRoster_(xmppRoster), mainWindowFactory_(mainWindowFactory), mainWindow_(mainWindowFactory_->createMainWindow(uiEventStream)), roster_(new Roster()), offlineFilter_(new OfflineRosterFilter()), nickManager_(nickManager), nickResolver_(nickResolver), uiEventStream_(uiEventStream), entityCapsManager_(entityCapsManager), ftOverview_(fileTransferOverview) { + assert(fileTransferOverview); iqRouter_ = iqRouter; presenceOracle_ = presenceOracle; subscriptionManager_ = subscriptionManager; @@ -74,15 +80,17 @@ RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, Avata nickManager_->onOwnNickChanged.connect(boost::bind(&MainWindow::setMyNick, mainWindow_, _1)); mainWindow_->setMyJID(jid); mainWindow_->setMyNick(nickManager_->getOwnNick()); + + entityCapsManager_->onCapsChanged.connect(boost::bind(&RosterController::handleOnCapsChanged, this, _1)); if (settings->getBoolSetting(SHOW_OFFLINE, false)) { uiEventStream->onUIEvent(boost::shared_ptr<UIEvent>(new ToggleShowOfflineUIEvent(true))); } } -RosterController::~RosterController() { +RosterController::~RosterController() { nickManager_->onOwnNickChanged.disconnect(boost::bind(&MainWindow::setMyNick, mainWindow_, _1)); - + delete offlineFilter_; delete expandiness_; @@ -91,6 +99,7 @@ RosterController::~RosterController() { delete mainWindow_; } delete roster_; + } void RosterController::setEnabled(bool enabled) { @@ -226,6 +235,10 @@ void RosterController::handleUIEvent(boost::shared_ptr<UIEvent> event) { } } } + else if (boost::shared_ptr<SendFileUIEvent> sendFileEvent = boost::dynamic_pointer_cast<SendFileUIEvent>(event)) { + //TODO add send file dialog to ChatView of receipient jid + ftOverview_->sendFile(sendFileEvent->getJID(), sendFileEvent->getFilename()); + } } void RosterController::setContactGroups(const JID& jid, const std::vector<std::string>& groups) { @@ -302,4 +315,15 @@ std::set<std::string> RosterController::getGroups() const { return xmppRoster_->getGroups(); } +void RosterController::handleOnCapsChanged(const JID& jid) { + DiscoInfo::ref info = entityCapsManager_->getCaps(jid); + if (info) { + std::set<ContactRosterItem::Feature> features; + if (info->hasFeature(DiscoInfo::JingleFeature) && info->hasFeature(DiscoInfo::JingleFTFeature) && info->hasFeature(DiscoInfo::JingleTransportsIBBFeature)) { + features.insert(ContactRosterItem::FileTransferFeature); + } + roster_->setAvailableFeatures(jid, features); + } +} + } diff --git a/Swift/Controllers/Roster/RosterController.h b/Swift/Controllers/Roster/RosterController.h index 0a2b818..66748ca 100644 --- a/Swift/Controllers/Roster/RosterController.h +++ b/Swift/Controllers/Roster/RosterController.h @@ -8,12 +8,14 @@ #include "Swiften/JID/JID.h" #include <string> +#include <set> #include "Swiften/Elements/Presence.h" #include "Swiften/Elements/ErrorPayload.h" #include "Swiften/Elements/RosterPayload.h" #include "Swiften/Avatars/AvatarManager.h" #include "Swift/Controllers/UIEvents/UIEvent.h" #include "RosterGroupExpandinessPersister.h" +#include "Swift/Controllers/FileTransfer/FileTransferOverview.h" #include "Swiften/Base/boost_bsignals.h" #include <boost/shared_ptr.hpp> @@ -35,10 +37,12 @@ namespace Swift { class IQRouter; class SettingsProvider; class NickManager; - + class EntityCapsProvider; + class FileTransferManager; + class RosterController { public: - RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter_, SettingsProvider* settings); + RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter_, SettingsProvider* settings, EntityCapsProvider* entityCapsProvider, FileTransferOverview* fileTransferOverview); ~RosterController(); void showRosterWindow(); MainWindow* getWindow() {return mainWindow_;}; @@ -69,6 +73,7 @@ namespace Swift { void handleRosterSetError(ErrorPayload::ref error, boost::shared_ptr<RosterPayload> rosterPayload); void applyAllPresenceTo(const JID& jid); void handleEditProfileRequest(); + void handleOnCapsChanged(const JID& jid); JID myJID_; XMPPRoster* xmppRoster_; @@ -86,6 +91,9 @@ namespace Swift { IQRouter* iqRouter_; SettingsProvider* settings_; UIEventStream* uiEventStream_; + EntityCapsProvider* entityCapsManager_; + FileTransferOverview* ftOverview_; + boost::bsignals::scoped_connection changeStatusConnection_; boost::bsignals::scoped_connection signOutConnection_; boost::bsignals::scoped_connection uiEventConnection_; diff --git a/Swift/Controllers/Roster/UnitTest/LeastCommonSubsequenceTest.cpp b/Swift/Controllers/Roster/UnitTest/LeastCommonSubsequenceTest.cpp index 3acab12..963c5cd 100644 --- a/Swift/Controllers/Roster/UnitTest/LeastCommonSubsequenceTest.cpp +++ b/Swift/Controllers/Roster/UnitTest/LeastCommonSubsequenceTest.cpp @@ -4,12 +4,12 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include <cppunit/extensions/HelperMacros.h> -#include <cppunit/extensions/TestFactoryRegistry.h> #include <boost/assign/list_of.hpp> #include <functional> #include <QA/Checker/IO.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> #include <Swift/Controllers/Roster/LeastCommonSubsequence.h> using namespace Swift; diff --git a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp index ca74dbb..fbee894 100644 --- a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp +++ b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp @@ -31,11 +31,21 @@ #include "Swift/Controllers/UIEvents/RenameRosterItemUIEvent.h" #include "Swiften/MUC/MUCRegistry.h" #include <Swiften/Client/DummyNickManager.h> +#include <Swiften/Disco/EntityCapsManager.h> +#include <Swiften/Disco/CapsProvider.h> +#include <Swiften/Jingle/JingleSessionManager.h> +#include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h> +#include <Swiften/Base/Algorithm.h> +#include <Swiften/EventLoop/DummyEventLoop.h> using namespace Swift; #define CHILDREN mainWindow_->roster->getRoot()->getChildren() +class DummyCapsProvider : public CapsProvider { + DiscoInfo::ref getCaps(const std::string&) const {return DiscoInfo::ref(new DiscoInfo());} +}; + class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(RosterControllerTest); CPPUNIT_TEST(testAdd); @@ -66,12 +76,21 @@ class RosterControllerTest : public CppUnit::TestFixture { uiEventStream_ = new UIEventStream(); settings_ = new DummySettingsProvider(); nickManager_ = new DummyNickManager(); - rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickManager_, nickResolver_, presenceOracle_, subscriptionManager_, eventController_, uiEventStream_, router_, settings_); + capsProvider_ = new DummyCapsProvider(); + entityCapsManager_ = new EntityCapsManager(capsProvider_, stanzaChannel_); + jingleSessionManager_ = new JingleSessionManager(router_); + + ftManager_ = new DummyFileTransferManager(); + ftOverview_ = new FileTransferOverview(ftManager_); + rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickManager_, nickResolver_, presenceOracle_, subscriptionManager_, eventController_, uiEventStream_, router_, settings_, entityCapsManager_, ftOverview_); mainWindow_ = mainWindowFactory_->last; }; void tearDown() { delete rosterController_; + delete ftManager_; + delete jingleSessionManager_; + delete nickManager_; delete nickResolver_; delete mucRegistry_; @@ -313,6 +332,11 @@ class RosterControllerTest : public CppUnit::TestFixture { UIEventStream* uiEventStream_; MockMainWindow* mainWindow_; DummySettingsProvider* settings_; + DummyCapsProvider* capsProvider_; + EntityCapsManager* entityCapsManager_; + JingleSessionManager* jingleSessionManager_; + FileTransferManager* ftManager_; + FileTransferOverview* ftOverview_; }; CPPUNIT_TEST_SUITE_REGISTRATION(RosterControllerTest); diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript index 031e93a..289f055 100644 --- a/Swift/Controllers/SConscript +++ b/Swift/Controllers/SConscript @@ -31,6 +31,10 @@ if env["SCONS_STAGE"] == "build" : "MainController.cpp", "ProfileController.cpp", "ContactEditController.cpp", + "FileTransfer/FileTransferController.cpp", + "FileTransfer/FileTransferOverview.cpp", + "FileTransfer/FileTransferProgressInfo.cpp", + "FileTransfer/SOCKS5BytestreamProxyFinder.cpp", "Roster/RosterController.cpp", "Roster/RosterGroupExpandinessPersister.cpp", "Roster/ContactRosterItem.cpp", @@ -42,6 +46,7 @@ if env["SCONS_STAGE"] == "build" : "SoundEventController.cpp", "SystemTrayController.cpp", "XMLConsoleController.cpp", + "FileTransferListController.cpp", "StatusTracker.cpp", "PresenceNotifier.cpp", "EventNotifier.cpp", diff --git a/Swift/Controllers/UIEvents/RequestFileTransferListUIEvent.h b/Swift/Controllers/UIEvents/RequestFileTransferListUIEvent.h new file mode 100644 index 0000000..aff6909 --- /dev/null +++ b/Swift/Controllers/UIEvents/RequestFileTransferListUIEvent.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/UIEvents/UIEvent.h> + +namespace Swift { + +class RequestFileTransferListUIEvent : public UIEvent { +}; + +} diff --git a/Swift/Controllers/UIEvents/SendFileUIEvent.h b/Swift/Controllers/UIEvents/SendFileUIEvent.h new file mode 100644 index 0000000..3bfa69d --- /dev/null +++ b/Swift/Controllers/UIEvents/SendFileUIEvent.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <string> + +#include <Swiften/JID/JID.h> +#include <Swift/Controllers/UIEvents/UIEvent.h> + +namespace Swift { + class SendFileUIEvent : public UIEvent { + public: + typedef boost::shared_ptr<SendFileUIEvent> ref; + + SendFileUIEvent(const JID& jid, const std::string& filename) : jid(jid), filename(filename) { + } + + const JID& getJID() const { + return jid; + } + + const std::string& getFilename() const { + return filename; + } + + private: + JID jid; + std::string filename; + }; +} diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h index faef5c8..b90efd9 100644 --- a/Swift/Controllers/UIInterfaces/ChatWindow.h +++ b/Swift/Controllers/UIInterfaces/ChatWindow.h @@ -24,12 +24,14 @@ namespace Swift { class TabComplete; class RosterItem; class ContactRosterItem; + class FileTransferController; class ChatWindow { public: enum AckState {Pending, Received, Failed}; enum Tristate {Yes, No, Maybe}; enum OccupantAction {Kick}; + enum FileTransferState {WaitingForAccept, Negotiating, Transferring, Canceled, Finished, FTFailed}; ChatWindow() {} virtual ~ChatWindow() {}; @@ -45,6 +47,11 @@ namespace Swift { virtual void addPresenceMessage(const std::string& message) = 0; virtual void addErrorMessage(const std::string& message) = 0; virtual void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) = 0; + + // File transfer related stuff + virtual std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) = 0; + virtual void setFileTransferProgress(std::string, const int percentageDone) = 0; + virtual void setFileTransferStatus(std::string, const FileTransferState state, const std::string& msg = "") = 0; virtual void setContactChatState(ChatState::ChatStateType state) = 0; virtual void setName(const std::string& name) = 0; @@ -88,6 +95,12 @@ namespace Swift { boost::signal<void ()> onAlertButtonClicked; boost::signal<void (ContactRosterItem*)> onOccupantSelectionChanged; boost::signal<void (ChatWindow::OccupantAction, ContactRosterItem*)> onOccupantActionSelected; + + // File transfer related + boost::signal<void (std::string /* id */)> onFileTransferCancel; + boost::signal<void (std::string /* id */, std::string /* description */)> onFileTransferStart; + boost::signal<void (std::string /* id */, std::string /* path */)> onFileTransferAccept; + boost::signal<void (std::string /* path */)> onSendFileRequest; }; } #endif diff --git a/Swift/Controllers/UIInterfaces/FileTransferListWidget.h b/Swift/Controllers/UIInterfaces/FileTransferListWidget.h new file mode 100644 index 0000000..01dcfd3 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/FileTransferListWidget.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +namespace Swift { + +class FileTransferOverview; + +class FileTransferListWidget { +public: + virtual ~FileTransferListWidget() {} + + virtual void show() = 0; + virtual void activate() = 0; + + virtual void setFileTransferOverview(FileTransferOverview*) = 0; +}; + +} diff --git a/Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h b/Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h new file mode 100644 index 0000000..0b08fb3 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include "Swift/Controllers/UIInterfaces/FileTransferListWidget.h" + +namespace Swift { + +class FileTransferListWidgetFactory { +public: + virtual ~FileTransferListWidgetFactory() {} + + virtual FileTransferListWidget* createFileTransferListWidget() = 0; +}; + +} diff --git a/Swift/Controllers/UIInterfaces/UIFactory.h b/Swift/Controllers/UIInterfaces/UIFactory.h index 57f55d0..cf89dab 100644 --- a/Swift/Controllers/UIInterfaces/UIFactory.h +++ b/Swift/Controllers/UIInterfaces/UIFactory.h @@ -18,6 +18,7 @@ #include <Swift/Controllers/UIInterfaces/ProfileWindowFactory.h> #include <Swift/Controllers/UIInterfaces/ContactEditWindowFactory.h> #include <Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h> namespace Swift { class UIFactory : @@ -27,12 +28,13 @@ namespace Swift { public LoginWindowFactory, public MainWindowFactory, public MUCSearchWindowFactory, - public XMLConsoleWidgetFactory, + public XMLConsoleWidgetFactory, public UserSearchWindowFactory, public JoinMUCWindowFactory, public ProfileWindowFactory, public ContactEditWindowFactory, - public AdHocCommandWindowFactory { + public AdHocCommandWindowFactory, + public FileTransferListWidgetFactory { public: virtual ~UIFactory() {} }; diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h index 574248f..b410c69 100644 --- a/Swift/Controllers/UnitTest/MockChatWindow.h +++ b/Swift/Controllers/UnitTest/MockChatWindow.h @@ -20,6 +20,11 @@ namespace Swift { virtual void addErrorMessage(const std::string& /*message*/) {}; virtual void addPresenceMessage(const std::string& /*message*/) {}; + // File transfer related stuff + virtual std::string addFileTransfer(const std::string& /*senderName*/, bool /*senderIsSelf*/,const std::string& /*filename*/, const boost::uintmax_t /*sizeInBytes*/) { return 0; }; + virtual void setFileTransferProgress(std::string /*id*/, const int /*alreadyTransferedBytes*/) { }; + virtual void setFileTransferStatus(std::string /*id*/, const FileTransferState /*state*/, const std::string& /*msg*/) { }; + virtual void setContactChatState(ChatState::ChatStateType /*state*/) {}; virtual void setName(const std::string& name) {name_ = name;}; virtual void show() {}; @@ -40,7 +45,7 @@ namespace Swift { virtual void setAlert(const std::string& /*alertText*/, const std::string& /*buttonText*/) {}; virtual void cancelAlert() {}; virtual void setCorrectionEnabled(Tristate /*enabled*/) {} - void setAvailableOccupantActions(const std::vector<OccupantAction>& actions) {} + void setAvailableOccupantActions(const std::vector<OccupantAction>&/* actions*/) {} boost::signal<void ()> onClosed; boost::signal<void ()> onAllMessagesRead; diff --git a/Swift/QtUI/QtChatView.cpp b/Swift/QtUI/QtChatView.cpp index 4c6a729..d51f74c 100644 --- a/Swift/QtUI/QtChatView.cpp +++ b/Swift/QtUI/QtChatView.cpp @@ -18,6 +18,8 @@ #include <QMessageBox> #include <QApplication> +#include <Swiften/Base/Log.h> + #include "QtWebView.h" #include "QtChatTheme.h" @@ -47,9 +49,11 @@ QtChatView::QtChatView(QtChatTheme* theme, QWidget* parent) : QWidget(parent), f #else mainLayout->addWidget(webView_); #endif + setAcceptDrops(true); webPage_ = new QWebPage(this); webPage_->setLinkDelegationPolicy(QWebPage::DelegateAllLinks); + webPage_->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true); webView_->setPage(webPage_); connect(webPage_, SIGNAL(selectionChanged()), SLOT(copySelectionToClipboard())); @@ -151,6 +155,10 @@ QString QtChatView::getLastSentMessage() { return lastElement_.toPlainText(); } +void QtChatView::addToJSEnvironment(const QString& name, QObject* obj) { + webView_->page()->currentFrame()->addToJavaScriptWindowObject(name, obj); +} + void QtChatView::replaceMessage(const QString& newMessage, const QString& id, const QDateTime& editTime) { rememberScrolledToBottom(); QWebElement message = document_.findFirst("#" + id); @@ -263,4 +271,69 @@ void QtChatView::resetView() { connect(webPage_->mainFrame(), SIGNAL(contentsSizeChanged(const QSize&)), this, SLOT(handleFrameSizeChanged()), Qt::UniqueConnection); } +QWebElement findDivElementWithID(QWebElement document, QString id) { + QWebElementCollection divs = document.findAll("div"); + foreach(QWebElement div, divs) { + if (div.attribute("id") == id) { + return div; + } + } + return QWebElement(); +} + +void QtChatView::setFileTransferProgress(QString id, const int percentageDone) { + QWebElement ftElement = findDivElementWithID(document_, id); + if (ftElement.isNull()) { + SWIFT_LOG(debug) << "Tried to access FT UI via invalid id!" << std::endl; + return; + } + QWebElement progressBar = ftElement.findFirst("div.progressbar"); + progressBar.setStyleProperty("width", QString::number(percentageDone) + "%"); + + QWebElement progressBarValue = ftElement.findFirst("div.progressbar-value"); + progressBarValue.setInnerXml(QString::number(percentageDone) + " %"); +} + +void QtChatView::setFileTransferStatus(QString id, const ChatWindow::FileTransferState state, const QString& /* msg */) { + QWebElement ftElement = findDivElementWithID(document_, id); + if (ftElement.isNull()) { + SWIFT_LOG(debug) << "Tried to access FT UI via invalid id! id = " << id.toStdString() << std::endl; + return; + } + + QString newInnerHTML = ""; + if (state == ChatWindow::WaitingForAccept) { + newInnerHTML = "Waiting for other side to accept the transfer.<br/>" + "<input id=\"discard\" type=\"submit\" value=\"Cancel\" onclick=\"filetransfer.cancel(\'" + id + "\');\">"; + } + if (state == ChatWindow::Negotiating) { + // replace with text "Negotiaging" + Cancel button + newInnerHTML = "Negotiating...<br/>" + "<input id=\"discard\" type=\"submit\" value=\"Cancel\" onclick=\"filetransfer.cancel(\'" + id + "\');\">"; + } + else if (state == ChatWindow::Transferring) { + // progress bar + Cancel Button + newInnerHTML = "<div style=\"position: relative; width: 90%; height: 20px; border: 2px solid grey; -webkit-border-radius: 10px;\">" + "<div class=\"progressbar\" style=\"width: 0%; height: 100%; background: #AAA; -webkit-border-radius: 6px;\">" + "<div class=\"progressbar-value\" style=\"position: absolute; top: 0px; left: 0px; width: 100%; text-align: center; padding-top: 2px;\">" + "0%" + "</div>" + "</div>" + "</div>" + "<input id=\"discard\" type=\"submit\" value=\"Cancel\" onclick=\"filetransfer.cancel(\'" + id + "\');\">"; + } + else if (state == ChatWindow::Canceled) { + newInnerHTML = "Transfer has been canceled!"; + } + else if (state == ChatWindow::Finished) { + // text "Successful transfer" + newInnerHTML = "Transfer completed successfully."; + } + else if (state == ChatWindow::FTFailed) { + newInnerHTML = "Transfer failed."; + } + + ftElement.setInnerXml(newInnerHTML); +} + } diff --git a/Swift/QtUI/QtChatView.h b/Swift/QtUI/QtChatView.h index eda7e42..cf47029 100644 --- a/Swift/QtUI/QtChatView.h +++ b/Swift/QtUI/QtChatView.h @@ -16,6 +16,8 @@ #include "ChatSnippet.h" +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> + class QWebPage; class QUrl; @@ -34,6 +36,9 @@ namespace Swift { void rememberScrolledToBottom(); void setAckXML(const QString& id, const QString& xml); QString getLastSentMessage(); + void addToJSEnvironment(const QString&, QObject*); + void setFileTransferProgress(QString id, const int percentageDone); + void setFileTransferStatus(QString id, const ChatWindow::FileTransferState state, const QString& msg); signals: void gotFocus(); diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp index a36bc32..238100a 100644 --- a/Swift/QtUI/QtChatWindow.cpp +++ b/Swift/QtUI/QtChatWindow.cpp @@ -19,8 +19,15 @@ #include "QtScaledAvatarCache.h" #include "SwifTools/TabComplete.h" +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIEvents/SendFileUIEvent.h> +#include "QtFileTransferJSBridge.h" + +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> #include <QLabel> +#include <QInputDialog> #include <QApplication> #include <QBoxLayout> #include <QCloseEvent> @@ -33,9 +40,12 @@ #include <QTime> #include <QUrl> #include <QPushButton> +#include <QFileDialog> + +#include <QDebug> namespace Swift { -QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream) : QtTabbable(), contact_(contact), previousMessageWasSelf_(false), previousMessageWasSystem_(false), previousMessageWasPresence_(false), eventStream_(eventStream) { +QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream) : QtTabbable(), contact_(contact), previousMessageWasSelf_(false), previousMessageWasSystem_(false), previousMessageWasPresence_(false), previousMessageWasFileTransfer_(false), eventStream_(eventStream) { unreadCount_ = 0; inputEnabled_ = true; completer_ = NULL; @@ -45,6 +55,8 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt updateTitleWithUnreadCount(); QtSettingsProvider settings; + setAcceptDrops(true); + alertStyleSheet_ = "background: rgb(255, 255, 153); color: black"; QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, this); @@ -117,15 +129,23 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt connect(messageLog_, SIGNAL(gotFocus()), input_, SLOT(setFocus())); resize(400,300); connect(messageLog_, SIGNAL(fontResized(int)), this, SIGNAL(fontResized(int))); + treeWidget_->onSomethingSelectedChanged.connect(boost::bind(&QtChatWindow::handleOccupantSelectionChanged, this, _1)); treeWidget_->onOccupantActionSelected.connect(boost::bind(boost::ref(onOccupantActionSelected), _1, _2)); + fileTransferJS = new QtFileTransferJSBridge(); + messageLog_->addToJSEnvironment("filetransfer", fileTransferJS); + connect(fileTransferJS, SIGNAL(setDescription(QString)), this, SLOT(handleFileTransferSetDescription(QString))); + connect(fileTransferJS, SIGNAL(sendRequest(QString)), this, SLOT(handleFileTransferStart(QString))); + connect(fileTransferJS, SIGNAL(acceptRequest(QString, QString)), this, SLOT(handleFileTransferAccept(QString, QString))); + connect(fileTransferJS, SIGNAL(cancel(QString)), this, SLOT(handleFileTransferCancel(QString))); } QtChatWindow::~QtChatWindow() { - + delete fileTransferJS; } + void QtChatWindow::handleOccupantSelectionChanged(RosterItem* item) { onOccupantSelectionChanged(dynamic_cast<ContactRosterItem*>(item)); } @@ -306,6 +326,7 @@ void QtChatWindow::closeEvent(QCloseEvent* event) { } void QtChatWindow::convertToMUC() { + setAcceptDrops(false); treeWidget_->show(); } @@ -391,7 +412,7 @@ std::string QtChatWindow::addMessage(const std::string &message, const std::stri QString styleSpanEnd = style == "" ? "" : "</span>"; htmlString += styleSpanStart + messageHTML + styleSpanEnd; - bool appendToPrevious = !previousMessageWasSystem_ && !previousMessageWasPresence_ && ((senderIsSelf && previousMessageWasSelf_) || (!senderIsSelf && !previousMessageWasSelf_ && previousSenderName_ == P2QSTRING(senderName))); + bool appendToPrevious = !previousMessageWasFileTransfer_ && !previousMessageWasSystem_ && !previousMessageWasPresence_ && ((senderIsSelf && previousMessageWasSelf_) || (!senderIsSelf && !previousMessageWasSelf_ && previousSenderName_ == P2QSTRING(senderName))); if (lastLineTracker_.getShouldMoveLastLine()) { /* should this be queued? */ messageLog_->addLastSeenLine(); @@ -406,6 +427,7 @@ std::string QtChatWindow::addMessage(const std::string &message, const std::stri previousSenderName_ = P2QSTRING(senderName); previousMessageWasSystem_ = false; previousMessageWasPresence_ = false; + previousMessageWasFileTransfer_ = false; return id; } @@ -431,6 +453,94 @@ std::string QtChatWindow::addAction(const std::string &message, const std::strin return addMessage(" *" + message + "*", senderName, senderIsSelf, label, avatarPath, "font-style:italic ", time); } +std::string formatSize(const uintmax_t bytes) { + static const char *siPrefix[] = {"k", "M", "G", "T", "P", "E", "Z", "Y", NULL}; + int power = 0; + double engBytes = bytes; + while (engBytes >= 1000) { + ++power; + engBytes = engBytes / 1000.0; + } + return str( boost::format("%.1lf %sB") % engBytes % (power > 0 ? siPrefix[power-1] : "") ); +} + +std::string QtChatWindow::addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) { + qDebug() << "addFileTransfer"; + std::string ft_id = id_.generateID(); + + std::string htmlString; + if (senderIsSelf) { + // outgoing + htmlString = "Send file: " + filename + " ( " + formatSize(sizeInBytes) + ") </br>" + + "<div id='" + ft_id + "'>" + + "<input id='discard' type='submit' value='Cancel' onclick='filetransfer.cancel(\"" + ft_id + "\");' />" + + "<input id='description' type='submit' value='Set Description' onclick='filetransfer.setDescription(\"" + ft_id + "\");' />" + + "<input id='send' type='submit' value='Send' onclick='filetransfer.sendRequest(\"" + ft_id + "\");' />" + + "</div>"; + } else { + // incoming + htmlString = "Receiving file: " + filename + " ( " + formatSize(sizeInBytes) + ") </br>" + + "<div id='" + ft_id + "'>" + + "<input id='discard' type='submit' value='Cancel' onclick='filetransfer.cancel(\"" + ft_id + "\");' />" + + "<input id='accept' type='submit' value='Accept' onclick='filetransfer.acceptRequest(\"" + ft_id + "\", \"" + filename + "\");' />" + + "</div>"; + } + + //addMessage(message, senderName, senderIsSelf, boost::shared_ptr<SecurityLabel>(), "", boost::posix_time::second_clock::local_time()); + + bool appendToPrevious = !previousMessageWasFileTransfer_ && !previousMessageWasSystem_ && !previousMessageWasPresence_ && ((senderIsSelf && previousMessageWasSelf_) || (!senderIsSelf && !previousMessageWasSelf_ && previousSenderName_ == P2QSTRING(senderName))); + if (lastLineTracker_.getShouldMoveLastLine()) { + /* should this be queued? */ + messageLog_->addLastSeenLine(); + /* if the line is added we should break the snippet */ + appendToPrevious = false; + } + QString qAvatarPath = "qrc:/icons/avatar.png"; + std::string id = id_.generateID(); + messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new MessageSnippet(QString::fromStdString(htmlString), Qt::escape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id)))); + + + return ft_id; +} + +void QtChatWindow::setFileTransferProgress(std::string id, const int percentageDone) { + messageLog_->setFileTransferProgress(QString::fromStdString(id), percentageDone); +} + +void QtChatWindow::setFileTransferStatus(std::string id, const FileTransferState state, const std::string& msg) { + messageLog_->setFileTransferStatus(QString::fromStdString(id), state, QString::fromStdString(msg)); +} + +void QtChatWindow::handleFileTransferCancel(QString id) { + qDebug() << "QtChatWindow::handleFileTransferCancel(" << id << ")"; + onFileTransferCancel(id.toStdString()); +} + +void QtChatWindow::handleFileTransferSetDescription(QString id) { + bool ok = false; + QString text = QInputDialog::getText(this, tr("File transfer description"), + tr("Description:"), QLineEdit::Normal, "", &ok); + if (ok) { + descriptions[id] = text; + } +} + +void QtChatWindow::handleFileTransferStart(QString id) { + qDebug() << "QtChatWindow::handleFileTransferStart(" << id << ")"; + + QString text = descriptions.find(id) == descriptions.end() ? QString() : descriptions[id]; + onFileTransferStart(id.toStdString(), text.toStdString()); +} + +void QtChatWindow::handleFileTransferAccept(QString id, QString filename) { + qDebug() << "QtChatWindow::handleFileTransferAccept(" << id << ", " << filename << ")"; + + QString path = QFileDialog::getSaveFileName(this, tr("Save File"), filename); + if (!path.isEmpty()) { + onFileTransferAccept(id.toStdString(), path.toStdString()); + } +} + void QtChatWindow::addErrorMessage(const std::string& errorMessage) { if (isWidgetSelected()) { onAllMessagesRead(); @@ -443,6 +553,7 @@ void QtChatWindow::addErrorMessage(const std::string& errorMessage) { previousMessageWasSelf_ = false; previousMessageWasSystem_ = true; previousMessageWasPresence_ = false; + previousMessageWasFileTransfer_ = false; } void QtChatWindow::addSystemMessage(const std::string& message) { @@ -458,6 +569,7 @@ void QtChatWindow::addSystemMessage(const std::string& message) { previousMessageWasSelf_ = false; previousMessageWasSystem_ = true; previousMessageWasPresence_ = false; + previousMessageWasFileTransfer_ = false; } void QtChatWindow::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) { @@ -532,6 +644,21 @@ void QtChatWindow::moveEvent(QMoveEvent*) { emit geometryChanged(); } +void QtChatWindow::dragEnterEvent(QDragEnterEvent *event) { + if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() == 1) { + // TODO: check whether contact actually supports file transfer + event->acceptProposedAction(); + } +} + +void QtChatWindow::dropEvent(QDropEvent *event) { + if (event->mimeData()->urls().size() == 1) { + onSendFileRequest(event->mimeData()->urls().at(0).toLocalFile().toStdString()); + } else { + addSystemMessage("Sending of multiple files at once isn't supported at this time."); + } +} + void QtChatWindow::replaceLastMessage(const std::string& message) { messageLog_->replaceLastMessage(P2QSTRING(Linkify::linkify(message))); } diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h index d38e9c6..f0c078c 100644 --- a/Swift/QtUI/QtChatWindow.h +++ b/Swift/QtUI/QtChatWindow.h @@ -14,6 +14,8 @@ #include "Swiften/Base/IDGenerator.h" +#include <map> + class QTextEdit; class QLineEdit; class QComboBox; @@ -28,6 +30,7 @@ namespace Swift { class TreeWidget; class QtTextEdit; class UIEventStream; + class QtFileTransferJSBridge; class QtChatWindow : public QtTabbable, public ChatWindow { Q_OBJECT public: @@ -39,6 +42,11 @@ namespace Swift { void addPresenceMessage(const std::string& message); void addErrorMessage(const std::string& errorMessage); void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time); + // File transfer related stuff + std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes); + void setFileTransferProgress(std::string id, const int percentageDone); + void setFileTransferStatus(std::string id, const FileTransferState state, const std::string& msg); + void show(); void activate(); void setUnreadMessageCount(int count); @@ -79,6 +87,9 @@ namespace Swift { void resizeEvent(QResizeEvent* event); void moveEvent(QMoveEvent* event); + void dragEnterEvent(QDragEnterEvent *event); + void dropEvent(QDropEvent *event); + protected: void showEvent(QShowEvent* event); @@ -88,6 +99,13 @@ namespace Swift { void handleKeyPressEvent(QKeyEvent* event); void handleSplitterMoved(int pos, int index); void handleAlertButtonClicked(); + + + void handleFileTransferCancel(QString id); + void handleFileTransferSetDescription(QString id); + void handleFileTransferStart(QString id); + void handleFileTransferAccept(QString id, QString filename); + private: void updateTitleWithUnreadCount(); void tabComplete(); @@ -116,6 +134,7 @@ namespace Swift { bool previousMessageWasSelf_; bool previousMessageWasSystem_; bool previousMessageWasPresence_; + bool previousMessageWasFileTransfer_; QString previousSenderName_; bool inputClearing_; UIEventStream* eventStream_; @@ -124,5 +143,8 @@ namespace Swift { QSplitter *logRosterSplitter_; Tristate correctionEnabled_; QString alertStyleSheet_; + + std::map<QString, QString> descriptions; + QtFileTransferJSBridge* fileTransferJS; }; } diff --git a/Swift/QtUI/QtFileTransferJSBridge.cpp b/Swift/QtUI/QtFileTransferJSBridge.cpp new file mode 100644 index 0000000..76c1509 --- /dev/null +++ b/Swift/QtUI/QtFileTransferJSBridge.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtFileTransferJSBridge.h" + +namespace Swift { + +QtFileTransferJSBridge::QtFileTransferJSBridge() { + +} + +QtFileTransferJSBridge::~QtFileTransferJSBridge() { + +} + +}
\ No newline at end of file diff --git a/Swift/QtUI/QtFileTransferJSBridge.h b/Swift/QtUI/QtFileTransferJSBridge.h new file mode 100644 index 0000000..bd884e5 --- /dev/null +++ b/Swift/QtUI/QtFileTransferJSBridge.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QObject> + +#include <map> + +namespace Swift { + +class FileTransferController; + +class QtFileTransferJSBridge : public QObject { + Q_OBJECT +public: + QtFileTransferJSBridge(); + virtual ~QtFileTransferJSBridge(); +signals: + void discard(QString id); + void sendRequest(QString id); + void setDescription(QString id); + void acceptRequest(QString id, QString filename); + void cancel(QString id); +}; + +} diff --git a/Swift/QtUI/QtFileTransferListItemModel.cpp b/Swift/QtUI/QtFileTransferListItemModel.cpp new file mode 100644 index 0000000..1b8ec51 --- /dev/null +++ b/Swift/QtUI/QtFileTransferListItemModel.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtFileTransferListItemModel.h" + +#include <boost/bind.hpp> + +#include <Swiften/Base/boost_bsignals.h> +#include <Swift/Controllers/FileTransfer/FileTransferController.h> +#include <Swift/Controllers/FileTransfer/FileTransferOverview.h> + +namespace Swift { + +extern std::string formatSize(const uintmax_t bytes); + +QtFileTransferListItemModel::QtFileTransferListItemModel(QObject *parent) : QAbstractItemModel(parent), fileTransferOverview(0) { +} + +void QtFileTransferListItemModel::setFileTransferOverview(FileTransferOverview *overview) { + fileTransferOverview = overview; + fileTransferOverview->onNewFileTransferController.connect(boost::bind(&QtFileTransferListItemModel::handleNewFileTransferController, this, _1)); +} + +void QtFileTransferListItemModel::handleNewFileTransferController(FileTransferController* newController) { + emit layoutAboutToBeChanged(); + emit layoutChanged(); + dataChanged(createIndex(0,0), createIndex(fileTransferOverview->getFileTransfers().size(),4)); + newController->onStateChage.connect(boost::bind(&QtFileTransferListItemModel::handleStateChange, this, fileTransferOverview->getFileTransfers().size() - 1)); + newController->onProgressChange.connect(boost::bind(&QtFileTransferListItemModel::handleProgressChange, this, fileTransferOverview->getFileTransfers().size() - 1)); +} + +void QtFileTransferListItemModel::handleStateChange(int index) { + emit dataChanged(createIndex(index, 2), createIndex(index, 2)); +} + +void QtFileTransferListItemModel::handleProgressChange(int index) { + emit dataChanged(createIndex(index, 3), createIndex(index, 3)); +} + +QVariant QtFileTransferListItemModel::headerData(int section, Qt::Orientation /* orientation */, int role) const { + if (role != Qt::DisplayRole) return QVariant(); + if (section == Direction) return QVariant("Direction"); + if (section == OtherParty) return QVariant("Other Party"); + if (section == State) return QVariant("State"); + if (section == Progress) return QVariant("Progress"); + if (section == OverallSize) return QVariant("Size"); + return QVariant(); +} + +int QtFileTransferListItemModel::columnCount(const QModelIndex& /* parent */) const { + return NoOfColumns; +} + +QVariant QtFileTransferListItemModel::data(const QModelIndex &index, int role) const { + if (role != Qt::DisplayRole || !index.isValid() || + !fileTransferOverview || static_cast<size_t>(index.row()) >= fileTransferOverview->getFileTransfers().size()) { + return QVariant(); + } + FileTransferController* controller = fileTransferOverview->getFileTransfers().at(index.row()); + if (index.column() == Direction) { + return controller->isIncoming() ? QVariant("Incoming") : QVariant("Outgoing"); + } + if (index.column() == OtherParty) { + return QVariant(QString::fromStdString(controller->getOtherParty().toString())); + } + if (index.column() == State) { + FileTransfer::State state = controller->getState(); + switch(state.state) { + case FileTransfer::State::WaitingForStart: + return QVariant("Waiting for start"); + case FileTransfer::State::WaitingForAccept: + return QVariant("Waiting for other side to accept"); + case FileTransfer::State::Negotiating: + return QVariant("Negotiating"); + case FileTransfer::State::Transferring: + return QVariant("Transferring"); + case FileTransfer::State::Finished: + return QVariant("Finished"); + case FileTransfer::State::Failed: + return QVariant("Failed"); + case FileTransfer::State::Canceled: + return QVariant("Canceled"); + } + } + + if (index.column() == Progress) { + return QVariant(QString::number(controller->getProgress())); + } + if (index.column() == OverallSize) { + return QVariant(QString::fromStdString(formatSize((controller->getSize())))); + } + return QVariant(); +} + +QModelIndex QtFileTransferListItemModel::parent(const QModelIndex& /* child */) const { + return createIndex(0,0); +} + +int QtFileTransferListItemModel::rowCount(const QModelIndex& /* parent */) const { + return fileTransferOverview ? fileTransferOverview->getFileTransfers().size() : 0; +} + +QModelIndex QtFileTransferListItemModel::index(int row, int column, const QModelIndex& /* parent */) const { + return createIndex(row, column, 0); +} + +} diff --git a/Swift/QtUI/QtFileTransferListItemModel.h b/Swift/QtUI/QtFileTransferListItemModel.h new file mode 100644 index 0000000..1d892a5 --- /dev/null +++ b/Swift/QtUI/QtFileTransferListItemModel.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QAbstractItemModel> + +namespace Swift { + +class FileTransferController; +class FileTransferOverview; + +class QtFileTransferListItemModel : public QAbstractItemModel { + Q_OBJECT +public: + explicit QtFileTransferListItemModel(QObject *parent = 0); + + void setFileTransferOverview(FileTransferOverview*); + + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + int columnCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + QModelIndex parent(const QModelIndex &child) const; + int rowCount(const QModelIndex &parent) const; + QModelIndex index(int row, int column, const QModelIndex &parent) const; + +private: + enum Columns { + Direction = 0, + OtherParty, + State, + Progress, + OverallSize, + NoOfColumns, + }; + +private: + void handleNewFileTransferController(FileTransferController*); + void handleStateChange(int index); + void handleProgressChange(int index); + +signals: + +public slots: + +private: + FileTransferOverview *fileTransferOverview; + +}; + +} diff --git a/Swift/QtUI/QtFileTransferListWidget.cpp b/Swift/QtUI/QtFileTransferListWidget.cpp new file mode 100644 index 0000000..01c632f --- /dev/null +++ b/Swift/QtUI/QtFileTransferListWidget.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtFileTransferListWidget.h" + +#include <Swift/QtUI/QtFileTransferListItemModel.h> + +#include <QVBoxLayout> +#include <QHBoxLayout> +#include <QWidget> +#include <QPushButton> + +namespace Swift { + +QtFileTransferListWidget::QtFileTransferListWidget() : fileTransferOverview(0) { + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setSpacing(0); + layout->setContentsMargins(0,0,0,0); + + treeView = new QTreeView(this); + treeView->setRootIsDecorated(false); + treeView->setItemsExpandable(false); + layout->addWidget(treeView); + + itemModel = new QtFileTransferListItemModel(); + treeView->setModel(itemModel); + + QWidget* bottom = new QWidget(this); + layout->addWidget(bottom); + bottom->setAutoFillBackground(true); + + QHBoxLayout* buttonLayout = new QHBoxLayout(bottom); + buttonLayout->setContentsMargins(10,0,20,0); + buttonLayout->setSpacing(0); + + QPushButton* clearFinished = new QPushButton(tr("Clear Finished Transfers"), bottom); + clearFinished->setEnabled(false); + //connect(clearButton, SIGNAL(clicked()), textEdit, SLOT(clear())); + buttonLayout->addWidget(clearFinished); + + setWindowTitle(tr("File Transfer List")); + emit titleUpdated(); +} + +QtFileTransferListWidget::~QtFileTransferListWidget() { + delete itemModel; +} + +void QtFileTransferListWidget::showEvent(QShowEvent* event) { + emit windowOpening(); + emit titleUpdated(); /* This just needs to be somewhere after construction */ + QWidget::showEvent(event); +} + +void QtFileTransferListWidget::show() { + QWidget::show(); + emit windowOpening(); +} + +void QtFileTransferListWidget::activate() { + emit wantsToActivate(); +} + +void QtFileTransferListWidget::setFileTransferOverview(FileTransferOverview *overview) { + fileTransferOverview = overview; + itemModel->setFileTransferOverview(overview); +} + +void QtFileTransferListWidget::closeEvent(QCloseEvent* event) { + emit windowClosing(); + event->accept(); +} + +} diff --git a/Swift/QtUI/QtFileTransferListWidget.h b/Swift/QtUI/QtFileTransferListWidget.h new file mode 100644 index 0000000..c828d4e --- /dev/null +++ b/Swift/QtUI/QtFileTransferListWidget.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include "Swift/Controllers/UIInterfaces/FileTransferListWidget.h" + +#include "QtTabbable.h" + +#include <QCloseEvent> +#include <QShowEvent> +#include <QTreeView> + +namespace Swift { + +class FileTransferOverview; +class QtFileTransferListItemModel; + +class QtFileTransferListWidget : public QtTabbable, public FileTransferListWidget { + Q_OBJECT + +public: + QtFileTransferListWidget(); + virtual ~QtFileTransferListWidget(); + + void show(); + void activate(); + + void setFileTransferOverview(FileTransferOverview *); + +private: + virtual void closeEvent(QCloseEvent* event); + virtual void showEvent(QShowEvent* event); + +private: + QTreeView* treeView; + + QtFileTransferListItemModel* itemModel; + FileTransferOverview* fileTransferOverview; +}; + +} diff --git a/Swift/QtUI/QtLoginWindow.cpp b/Swift/QtUI/QtLoginWindow.cpp index 475863c..543af65 100644 --- a/Swift/QtUI/QtLoginWindow.cpp +++ b/Swift/QtUI/QtLoginWindow.cpp @@ -7,6 +7,7 @@ #include "QtLoginWindow.h" #include <boost/bind.hpp> +#include <boost/smart_ptr/make_shared.hpp> #include <algorithm> #include <cassert> @@ -28,6 +29,7 @@ #include "Swift/Controllers/UIEvents/UIEventStream.h" #include "Swift/Controllers/UIEvents/RequestXMLConsoleUIEvent.h" +#include "Swift/Controllers/UIEvents/RequestFileTransferListUIEvent.h" #include "Swift/Controllers/UIEvents/ToggleSoundsUIEvent.h" #include "Swift/Controllers/UIEvents/ToggleNotificationsUIEvent.h" #include "Swiften/Base/Platform.h" @@ -166,6 +168,10 @@ QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream) : QMainWindow(), forg connect(xmlConsoleAction_, SIGNAL(triggered()), SLOT(handleShowXMLConsole())); generalMenu_->addAction(xmlConsoleAction_); + fileTransferOverviewAction_ = new QAction(tr("Show &File Transfer Overview"), this); + connect(fileTransferOverviewAction_, SIGNAL(triggered()), SLOT(handleShowFileTransferOverview())); + generalMenu_->addAction(fileTransferOverviewAction_); + toggleSoundsAction_ = new QAction(tr("&Play Sounds"), this); toggleSoundsAction_->setCheckable(true); toggleSoundsAction_->setChecked(true); @@ -356,6 +362,10 @@ void QtLoginWindow::handleShowXMLConsole() { uiEventStream_->send(boost::shared_ptr<RequestXMLConsoleUIEvent>(new RequestXMLConsoleUIEvent())); } +void QtLoginWindow::handleShowFileTransferOverview() { + uiEventStream_->send(boost::make_shared<RequestFileTransferListUIEvent>()); +} + void QtLoginWindow::handleToggleSounds(bool enabled) { uiEventStream_->send(boost::shared_ptr<ToggleSoundsUIEvent>(new ToggleSoundsUIEvent(enabled))); } diff --git a/Swift/QtUI/QtLoginWindow.h b/Swift/QtUI/QtLoginWindow.h index 4628af7..414bb20 100644 --- a/Swift/QtUI/QtLoginWindow.h +++ b/Swift/QtUI/QtLoginWindow.h @@ -51,6 +51,7 @@ namespace Swift { void handleCertficateChecked(bool); void handleQuit(); void handleShowXMLConsole(); + void handleShowFileTransferOverview(); void handleToggleSounds(bool enabled); void handleToggleNotifications(bool enabled); void handleAbout(); @@ -88,5 +89,6 @@ namespace Swift { QPointer<QtAboutWidget> aboutDialog_; bool forgetful_; QAction* xmlConsoleAction_; + QAction* fileTransferOverviewAction_; }; } diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp index bd936d4..9de700c 100644 --- a/Swift/QtUI/QtUIFactory.cpp +++ b/Swift/QtUI/QtUIFactory.cpp @@ -24,6 +24,7 @@ #include "QtProfileWindow.h" #include "QtContactEditWindow.h" #include "QtAdHocCommandWindow.h" +#include "QtFileTransferListWidget.h" #define CHATWINDOW_FONT_SIZE "chatWindowFontSize" @@ -43,6 +44,15 @@ XMLConsoleWidget* QtUIFactory::createXMLConsoleWidget() { return widget; } +FileTransferListWidget* QtUIFactory::createFileTransferListWidget() { + QtFileTransferListWidget* widget = new QtFileTransferListWidget(); + tabs->addTab(widget); + if (!tabs->isVisible()) { + tabs->show(); + } + widget->show(); + return widget; +} MainWindow* QtUIFactory::createMainWindow(UIEventStream* eventStream) { lastMainWindow = new QtMainWindow(settings, eventStream); diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h index e7ea6c6..8fc5395 100644 --- a/Swift/QtUI/QtUIFactory.h +++ b/Swift/QtUI/QtUIFactory.h @@ -39,6 +39,7 @@ namespace Swift { virtual JoinMUCWindow* createJoinMUCWindow(UIEventStream* uiEventStream); virtual ProfileWindow* createProfileWindow(); virtual ContactEditWindow* createContactEditWindow(); + virtual FileTransferListWidget* createFileTransferListWidget(); virtual void createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command); private slots: diff --git a/Swift/QtUI/QtWebView.cpp b/Swift/QtUI/QtWebView.cpp index 5f9539e..d849ce7 100644 --- a/Swift/QtUI/QtWebView.cpp +++ b/Swift/QtUI/QtWebView.cpp @@ -45,7 +45,9 @@ void QtWebView::setFontSizeIsMinimal(bool minimum) { void QtWebView::contextMenuEvent(QContextMenuEvent* ev) { // Filter out the relevant actions from the standard actions + QMenu* menu = page()->createStandardContextMenu(); + /* QList<QAction*> actions(menu->actions()); for (int i = 0; i < actions.size(); ++i) { QAction* action = actions.at(i); @@ -59,7 +61,7 @@ void QtWebView::contextMenuEvent(QContextMenuEvent* ev) { if (removeAction) { menu->removeAction(action); } - } + }*/ // Add our own custom actions menu->addAction(tr("Clear"), this, SIGNAL(clearRequested())); diff --git a/Swift/QtUI/QtXMLConsoleWidget.cpp b/Swift/QtUI/QtXMLConsoleWidget.cpp index b0c0385..0c88ce6 100644 --- a/Swift/QtUI/QtXMLConsoleWidget.cpp +++ b/Swift/QtUI/QtXMLConsoleWidget.cpp @@ -6,6 +6,8 @@ #include "QtXMLConsoleWidget.h" +#include <Swiften/Client/XMLBeautifier.h> + #include <QCloseEvent> #include <QTextEdit> #include <QVBoxLayout> @@ -49,6 +51,11 @@ QtXMLConsoleWidget::QtXMLConsoleWidget() { setWindowTitle(tr("Debug Console")); emit titleUpdated(); + beautifier = new XMLBeautifier(true, false); +} + +QtXMLConsoleWidget::~QtXMLConsoleWidget() { + delete beautifier; } void QtXMLConsoleWidget::showEvent(QShowEvent* event) { @@ -72,11 +79,11 @@ void QtXMLConsoleWidget::closeEvent(QCloseEvent* event) { } void QtXMLConsoleWidget::handleDataRead(const SafeByteArray& data) { - appendTextIfEnabled(std::string(tr("<!-- IN -->").toUtf8()) + "\n" + safeByteArrayToString(data) + "\n", QColor(33,98,33)); + appendTextIfEnabled(std::string(tr("<!-- IN -->").toUtf8()) + "\n" + beautifier->beautify(safeByteArrayToString(data)) + "\n", QColor(33,98,33)); } void QtXMLConsoleWidget::handleDataWritten(const SafeByteArray& data) { - appendTextIfEnabled(std::string(tr("<!-- OUT -->").toUtf8()) + "\n" + safeByteArrayToString(data) + "\n", QColor(155,1,0)); + appendTextIfEnabled(std::string(tr("<!-- OUT -->").toUtf8()) + "\n" + beautifier->beautify(safeByteArrayToString(data)) + "\n", QColor(155,1,0)); } void QtXMLConsoleWidget::appendTextIfEnabled(const std::string& data, const QColor& color) { diff --git a/Swift/QtUI/QtXMLConsoleWidget.h b/Swift/QtUI/QtXMLConsoleWidget.h index 73a3bad..c22251f 100644 --- a/Swift/QtUI/QtXMLConsoleWidget.h +++ b/Swift/QtUI/QtXMLConsoleWidget.h @@ -14,11 +14,14 @@ class QCheckBox; class QColor; namespace Swift { + class XMLBeautifier; + class QtXMLConsoleWidget : public QtTabbable, public XMLConsoleWidget { Q_OBJECT public: QtXMLConsoleWidget(); + ~QtXMLConsoleWidget(); void show(); void activate(); @@ -35,5 +38,6 @@ namespace Swift { private: QTextEdit* textEdit; QCheckBox* enabled; + XMLBeautifier* beautifier; }; } diff --git a/Swift/QtUI/Roster/QtRosterWidget.cpp b/Swift/QtUI/Roster/QtRosterWidget.cpp index 923f977..f3b0441 100644 --- a/Swift/QtUI/Roster/QtRosterWidget.cpp +++ b/Swift/QtUI/Roster/QtRosterWidget.cpp @@ -9,10 +9,12 @@ #include <QContextMenuEvent> #include <QMenu> #include <QInputDialog> +#include <QFileDialog> #include "Swift/Controllers/UIEvents/RequestContactEditorUIEvent.h" #include "Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h" #include "Swift/Controllers/UIEvents/RenameGroupUIEvent.h" +#include "Swift/Controllers/UIEvents/SendFileUIEvent.h" #include "QtContactEditWindow.h" #include "Swift/Controllers/Roster/ContactRosterItem.h" #include "Swift/Controllers/Roster/GroupRosterItem.h" @@ -54,6 +56,10 @@ void QtRosterWidget::contextMenuEvent(QContextMenuEvent* event) { if (ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item)) { QAction* editContact = contextMenu.addAction(tr("Edit")); QAction* removeContact = contextMenu.addAction(tr("Remove")); + QAction* sendFile = NULL; + if (contact->supportsFeature(ContactRosterItem::FileTransferFeature)) { + sendFile = contextMenu.addAction(tr("Send File")); + } QAction* result = contextMenu.exec(event->globalPos()); if (result == editContact) { eventStream_->send(boost::make_shared<RequestContactEditorUIEvent>(contact->getJID())); @@ -63,6 +69,12 @@ void QtRosterWidget::contextMenuEvent(QContextMenuEvent* event) { eventStream_->send(boost::make_shared<RemoveRosterItemUIEvent>(contact->getJID())); } } + else if (result == sendFile) { + QString fileName = QFileDialog::getOpenFileName(this, tr("Send File"), "", tr("All Files (*);;")); + if (!fileName.isEmpty()) { + eventStream_->send(boost::make_shared<SendFileUIEvent>(contact->getJID(), fileName.toStdString())); + } + } } else if (GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(item)) { QAction* renameGroupAction = contextMenu.addAction(tr("Rename")); diff --git a/Swift/QtUI/Roster/QtTreeWidget.cpp b/Swift/QtUI/Roster/QtTreeWidget.cpp index 96a078b..79dd6a2 100644 --- a/Swift/QtUI/Roster/QtTreeWidget.cpp +++ b/Swift/QtUI/Roster/QtTreeWidget.cpp @@ -8,11 +8,14 @@ #include <boost/smart_ptr/make_shared.hpp> +#include <QUrl> + #include "Swiften/Base/Platform.h" #include "Swift/Controllers/Roster/ContactRosterItem.h" #include "Swift/Controllers/Roster/GroupRosterItem.h" #include "Swift/Controllers/UIEvents/UIEventStream.h" #include "Swift/Controllers/UIEvents/RequestChatUIEvent.h" +#include "Swift/Controllers/UIEvents/SendFileUIEvent.h" #include "QtSwiftUtil.h" namespace Swift { @@ -31,6 +34,7 @@ QtTreeWidget::QtTreeWidget(UIEventStream* eventStream, QWidget* parent) : QTreeV expandAll(); setAnimated(true); setIndentation(0); + setAcceptDrops(true); setRootIsDecorated(true); connect(this, SIGNAL(activated(const QModelIndex&)), this, SLOT(handleItemActivated(const QModelIndex&))); connect(model_, SIGNAL(itemExpanded(const QModelIndex&, bool)), this, SLOT(handleModelItemExpanded(const QModelIndex&, bool))); @@ -104,6 +108,41 @@ void QtTreeWidget::handleItemActivated(const QModelIndex& index) { } } +void QtTreeWidget::dragEnterEvent(QDragEnterEvent *event) { + if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() == 1) { + event->acceptProposedAction(); + } +} + +void QtTreeWidget::dropEvent(QDropEvent *event) { + QModelIndex index = indexAt(event->pos()); + if (index.isValid()) { + RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); + if (ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item)) { + if (contact->supportsFeature(ContactRosterItem::FileTransferFeature)) { + QString filename = event->mimeData()->urls().at(0).toLocalFile(); + if (!filename.isEmpty()) { + eventStream_->send(boost::make_shared<SendFileUIEvent>(contact->getJID(), filename.toStdString())); + } + } + } + } +} + +void QtTreeWidget::dragMoveEvent(QDragMoveEvent* event) { + QModelIndex index = indexAt(event->pos()); + if (index.isValid()) { + RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); + if (ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item)) { + if (contact->supportsFeature(ContactRosterItem::FileTransferFeature)) { + event->accept(); + return; + } + } + } + event->ignore(); +} + void QtTreeWidget::handleExpanded(const QModelIndex& index) { GroupRosterItem* item = dynamic_cast<GroupRosterItem*>(static_cast<RosterItem*>(index.internalPointer())); if (item) { diff --git a/Swift/QtUI/Roster/QtTreeWidget.h b/Swift/QtUI/Roster/QtTreeWidget.h index 1ad56d6..271fbd5 100644 --- a/Swift/QtUI/Roster/QtTreeWidget.h +++ b/Swift/QtUI/Roster/QtTreeWidget.h @@ -8,6 +8,9 @@ #include <QTreeView> #include <QModelIndex> +#include <QDragEnterEvent> +#include <QDropEvent> +#include <QDragMoveEvent> #include "Swift/QtUI/Roster/RosterModel.h" #include "Swift/QtUI/Roster/RosterDelegate.h" @@ -31,6 +34,10 @@ class QtTreeWidget : public QTreeView{ void handleExpanded(const QModelIndex&); void handleCollapsed(const QModelIndex&); void handleClicked(const QModelIndex&); + protected: + void dragEnterEvent(QDragEnterEvent* event); + void dropEvent(QDropEvent* event); + void dragMoveEvent(QDragMoveEvent* event); protected: QModelIndexList getSelectedIndexes() const; diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript index 33450ed..1d7ab78 100644 --- a/Swift/QtUI/SConscript +++ b/Swift/QtUI/SConscript @@ -84,6 +84,8 @@ sources = [ "QtTabWidget.cpp", "QtTextEdit.cpp", "QtXMLConsoleWidget.cpp", + "QtFileTransferListWidget.cpp", + "QtFileTransferListItemModel.cpp", "QtAdHocCommandWindow.cpp", "QtUtilities.cpp", "QtBookmarkDetailWindow.cpp", @@ -133,6 +135,7 @@ sources = [ "QtWebView.cpp", "qrc_DefaultTheme.cc", "qrc_Swift.cc", + "QtFileTransferJSBridge.cpp", ] myenv["SWIFT_VERSION"] = Version.getBuildVersion(env.Dir("#").abspath, "swift") diff --git a/Swiften/Client/Client.cpp b/Swiften/Client/Client.cpp index c53bcaf..e95e704 100644 --- a/Swiften/Client/Client.cpp +++ b/Swiften/Client/Client.cpp @@ -26,6 +26,9 @@ #include <Swiften/TLS/BlindCertificateTrustChecker.h> #include <Swiften/Client/NickManagerImpl.h> #include <Swiften/Client/ClientSession.h> +#include <Swiften/Jingle/JingleSessionManager.h> +#include <Swiften/Network/NetworkFactories.h> +#include <Swiften/FileTransfer/FileTransferManagerImpl.h> namespace Swift { @@ -59,9 +62,15 @@ Client::Client(const JID& jid, const SafeString& password, NetworkFactories* net nickResolver = new NickResolver(jid.toBare(), roster, vcardManager, mucRegistry); blindCertificateTrustChecker = new BlindCertificateTrustChecker(); + + jingleSessionManager = new JingleSessionManager(getIQRouter()); + fileTransferManager = NULL; } Client::~Client() { + delete fileTransferManager; + delete jingleSessionManager; + delete blindCertificateTrustChecker; delete nickResolver; @@ -98,6 +107,10 @@ void Client::setSoftwareVersion(const std::string& name, const std::string& vers softwareVersionResponder->setVersion(name, version, os); } +void Client::handleConnected() { + fileTransferManager = new FileTransferManagerImpl(getJID(), jingleSessionManager, getIQRouter(), getEntityCapsProvider(), presenceOracle, getNetworkFactories()->getConnectionFactory(), getNetworkFactories()->getConnectionServerFactory(), getNetworkFactories()->getTimerFactory(), getNetworkFactories()->getPlatformNATTraversalWorker()); +} + void Client::requestRoster() { // FIXME: We should set this once when the session is finished, but there // is currently no callback for this @@ -139,4 +152,8 @@ NickManager* Client::getNickManager() const { return nickManager; } +FileTransferManager* Client::getFileTransferManager() const { + return fileTransferManager; +} + } diff --git a/Swiften/Client/Client.h b/Swiften/Client/Client.h index 08289a5..7269f10 100644 --- a/Swiften/Client/Client.h +++ b/Swiften/Client/Client.h @@ -33,6 +33,9 @@ namespace Swift { class SubscriptionManager; class ClientDiscoManager; class NickManager; + class FileTransferManager; + class JingleSessionManager; + class FileTransferManagerImpl; /** * Provides the core functionality for writing XMPP client software. @@ -131,6 +134,11 @@ namespace Swift { ClientDiscoManager* getDiscoManager() const { return discoManager; } + + /** + * Returns a FileTransferManager for the client. This is only available after the onConnected signal has been fired. + */ + FileTransferManager* getFileTransferManager() const; /** * Configures the client to always trust a non-validating @@ -149,6 +157,9 @@ namespace Swift { private: Storages* getStorages() const; + protected: + void handleConnected(); + private: Storages* storages; MemoryStorages* memoryStorages; @@ -168,6 +179,8 @@ namespace Swift { SubscriptionManager* subscriptionManager; MUCManager* mucManager; ClientDiscoManager* discoManager; + JingleSessionManager* jingleSessionManager; + FileTransferManagerImpl* fileTransferManager; BlindCertificateTrustChecker* blindCertificateTrustChecker; }; } diff --git a/Swiften/Client/ClientXMLTracer.cpp b/Swiften/Client/ClientXMLTracer.cpp index c1e398d..c1093eb 100644 --- a/Swiften/Client/ClientXMLTracer.cpp +++ b/Swiften/Client/ClientXMLTracer.cpp @@ -12,13 +12,18 @@ namespace Swift { ClientXMLTracer::ClientXMLTracer(CoreClient* client) { - client->onDataRead.connect(boost::bind(&ClientXMLTracer::printData, '<', _1)); - client->onDataWritten.connect(boost::bind(&ClientXMLTracer::printData, '>', _1)); + beautifier = new XMLBeautifier(true, true); + client->onDataRead.connect(boost::bind(&ClientXMLTracer::printData, this, '<', _1)); + client->onDataWritten.connect(boost::bind(&ClientXMLTracer::printData, this, '>', _1)); +} + +ClientXMLTracer::~ClientXMLTracer() { + delete beautifier; } void ClientXMLTracer::printData(char direction, const SafeByteArray& data) { printLine(direction); - std::cerr << byteArrayToString(ByteArray(data.begin(), data.end())) << std::endl; + std::cerr << beautifier->beautify(byteArrayToString(ByteArray(data.begin(), data.end()))) << std::endl; } void ClientXMLTracer::printLine(char c) { diff --git a/Swiften/Client/ClientXMLTracer.h b/Swiften/Client/ClientXMLTracer.h index dd94e0e..0752faa 100644 --- a/Swiften/Client/ClientXMLTracer.h +++ b/Swiften/Client/ClientXMLTracer.h @@ -7,15 +7,19 @@ #pragma once #include <Swiften/Client/CoreClient.h> +#include <Swiften/Client/XMLBeautifier.h> #include <Swiften/Base/SafeByteArray.h> namespace Swift { class ClientXMLTracer { public: ClientXMLTracer(CoreClient* client); + ~ClientXMLTracer(); + private: + void printData(char direction, const SafeByteArray& data); + void printLine(char c); private: - static void printData(char direction, const SafeByteArray& data); - static void printLine(char c); + XMLBeautifier *beautifier; }; } diff --git a/Swiften/Client/CoreClient.cpp b/Swiften/Client/CoreClient.cpp index cceec74..37055e4 100644 --- a/Swiften/Client/CoreClient.cpp +++ b/Swiften/Client/CoreClient.cpp @@ -276,6 +276,7 @@ void CoreClient::handleDataWritten(const SafeByteArray& data) { void CoreClient::handleStanzaChannelAvailableChanged(bool available) { if (available) { + handleConnected(); onConnected(); } } diff --git a/Swiften/Client/CoreClient.h b/Swiften/Client/CoreClient.h index 16813de..3472e76 100644 --- a/Swiften/Client/CoreClient.h +++ b/Swiften/Client/CoreClient.h @@ -188,6 +188,15 @@ namespace Swift { return session_; } + NetworkFactories* getNetworkFactories() const { + return networkFactories; + } + + /** + * Called before onConnected signal is emmitted. + */ + virtual void handleConnected() {}; + private: void handleConnectorFinished(boost::shared_ptr<Connection>); void handleStanzaChannelAvailableChanged(bool available); diff --git a/Swiften/Client/XMLBeautifier.cpp b/Swiften/Client/XMLBeautifier.cpp new file mode 100644 index 0000000..b70fc48 --- /dev/null +++ b/Swiften/Client/XMLBeautifier.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <sstream> +#include <stack> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/Log.h> +#include <Swiften/Client/XMLBeautifier.h> +#include <Swiften/Parser/PlatformXMLParserFactory.h> + +namespace Swift { + +XMLBeautifier::XMLBeautifier(bool indention, bool coloring) : doIndention(indention), doColoring(coloring), intLevel(0), parser(NULL), lastWasStepDown(false) { + factory = new PlatformXMLParserFactory(); +} + +XMLBeautifier::~XMLBeautifier() { + delete factory; +} + +std::string XMLBeautifier::beautify(const std::string &text) { + parser = factory->createXMLParser(this); + intLevel = 0; + buffer.str(std::string()); + parser->parse(text); + delete parser; + return buffer.str(); +} + +void XMLBeautifier::indent() { + for (int i = 0; i < intLevel; ++i) { + buffer << " "; + } +} + +// all bold but reset +const char colorBlue[] = "\x1b[01;34m"; +const char colorCyan[] = "\x1b[01;36m"; +const char colorGreen[] = "\x1b[01;32m"; +const char colorMagenta[] = "\x1b[01;35m"; +const char colorRed[] = "\x1b[01;31m"; +const char colorReset[] = "\x1b[0m"; +const char colorYellow[] = "\x1b[01;33m"; + + + +std::string XMLBeautifier::styleTag(const std::string& text) const { + std::string result; + result += colorYellow; + result += text; + result += colorReset; + return result; +} + +std::string XMLBeautifier::styleNamespace(const std::string& text) const { + std::string result; + result += colorRed; + result += text; + result += colorReset; + return result; +} + +std::string XMLBeautifier::styleAttribute(const std::string& text) const { + std::string result; + result += colorGreen; + result += text; + result += colorReset; + return result; +} +std::string XMLBeautifier::styleValue(const std::string& text) const { + std::string result; + result += colorCyan; + result += text; + result += colorReset; + return result; +} + +void XMLBeautifier::handleStartElement(const std::string& element, const std::string& ns, const AttributeMap& attributes) { + if (doIndention) { + if (intLevel) buffer << std::endl; + } + indent(); + buffer << "<" << (doColoring ? styleTag(element) : element); + if (!ns.empty() && (!parentNSs.empty() && parentNSs.top() != ns)) { + buffer << " "; + buffer << (doColoring ? styleAttribute("xmlns") : "xmlns"); + buffer << "="; + buffer << "\"" << (doColoring ? styleNamespace(ns) : ns) << "\""; + } + if (!attributes.getEntries().empty()) { + foreach(AttributeMap::Entry entry, attributes.getEntries()) { + buffer << " "; + buffer << (doColoring ? styleAttribute(entry.getAttribute().getName()) : entry.getAttribute().getName()); + buffer << "="; + buffer << "\"" << (doColoring ? styleValue(entry.getValue()) : entry.getValue()) << "\""; + } + } + buffer << ">"; + ++intLevel; + lastWasStepDown = false; + parentNSs.push(ns); +} + +void XMLBeautifier::handleEndElement(const std::string& element, const std::string& /* ns */) { + --intLevel; + parentNSs.pop(); + if (/*hadCDATA.top() ||*/ lastWasStepDown) { + if (doIndention) { + buffer << std::endl; + } + indent(); + } + buffer << "</" << (doColoring ? styleTag(element) : element) << ">"; + lastWasStepDown = true; +} + +void XMLBeautifier::handleCharacterData(const std::string& data) { + buffer << data; + lastWasStepDown = false; +} + +} diff --git a/Swiften/Client/XMLBeautifier.h b/Swiften/Client/XMLBeautifier.h new file mode 100644 index 0000000..44dfd20 --- /dev/null +++ b/Swiften/Client/XMLBeautifier.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <string> +#include <stack> + +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/Parser/XMLParserFactory.h> +#include <Swiften/Parser/XMLParserClient.h> +#include <Swiften/Parser/XMLParser.h> + +namespace Swift { + +class XMLBeautifier : public XMLParserClient { +public: + XMLBeautifier(bool indention, bool coloring); + virtual ~XMLBeautifier(); + + std::string beautify(const std::string&); + +private: + void handleStartElement(const std::string& element, const std::string& ns, const AttributeMap& attributes); + void handleEndElement(const std::string& element, const std::string& ns); + void handleCharacterData(const std::string& data); + +private: + void indent(); + +private: + std::string styleTag(const std::string& text) const; + std::string styleNamespace(const std::string& text) const; + std::string styleAttribute(const std::string& text) const; + std::string styleValue(const std::string& text) const; + +private: + bool doIndention; + bool doColoring; + + int intLevel; + std::string inputBuffer; + std::stringstream buffer; + XMLParserFactory* factory; + XMLParser* parser; + + bool lastWasStepDown; + std::stack<std::string> parentNSs; +}; +} diff --git a/Swiften/Elements/DiscoInfo.cpp b/Swiften/Elements/DiscoInfo.cpp index f54b6bf..35d4d04 100644 --- a/Swiften/Elements/DiscoInfo.cpp +++ b/Swiften/Elements/DiscoInfo.cpp @@ -16,7 +16,11 @@ const std::string DiscoInfo::SecurityLabelsCatalogFeature = std::string("urn:xmp const std::string DiscoInfo::JabberSearchFeature = std::string("jabber:iq:search"); const std::string DiscoInfo::CommandsFeature = std::string("http://jabber.org/protocol/commands"); const std::string DiscoInfo::MessageCorrectionFeature = std::string("urn:xmpp:message-correct:0"); - +const std::string DiscoInfo::JingleFeature = std::string("urn:xmpp:jingle:1"); +const std::string DiscoInfo::JingleFTFeature = std::string("urn:xmpp:jingle:apps:file-transfer:3"); +const std::string DiscoInfo::JingleTransportsIBBFeature = std::string("urn:xmpp:jingle:transports:ibb:1"); +const std::string DiscoInfo::JingleTransportsS5BFeature = std::string("urn:xmpp:jingle:transports:s5b:1"); +const std::string DiscoInfo::Bytestream = std::string("http://jabber.org/protocol/bytestreams"); bool DiscoInfo::Identity::operator<(const Identity& other) const { diff --git a/Swiften/Elements/DiscoInfo.h b/Swiften/Elements/DiscoInfo.h index c5c9e1c..6d6e722 100644 --- a/Swiften/Elements/DiscoInfo.h +++ b/Swiften/Elements/DiscoInfo.h @@ -23,6 +23,11 @@ namespace Swift { static const std::string JabberSearchFeature; static const std::string CommandsFeature; static const std::string MessageCorrectionFeature; + static const std::string JingleFeature; + static const std::string JingleFTFeature; + static const std::string JingleTransportsIBBFeature; + static const std::string JingleTransportsS5BFeature; + static const std::string Bytestream; class Identity { public: diff --git a/Swiften/Elements/JingleContentPayload.h b/Swiften/Elements/JingleContentPayload.h index c44a806..183b8eb 100644 --- a/Swiften/Elements/JingleContentPayload.h +++ b/Swiften/Elements/JingleContentPayload.h @@ -21,6 +21,7 @@ namespace Swift { typedef boost::shared_ptr<JingleContentPayload> ref; enum Creator { + UnknownCreator, InitiatorCreator, ResponderCreator, }; diff --git a/Swiften/Elements/JingleFileTransferDescription.h b/Swiften/Elements/JingleFileTransferDescription.h index 19644bd..04f3f1f 100644 --- a/Swiften/Elements/JingleFileTransferDescription.h +++ b/Swiften/Elements/JingleFileTransferDescription.h @@ -7,7 +7,7 @@ #pragma once #include <boost/shared_ptr.hpp> -#include <boost/optional.hpp> +#include <vector> #include <Swiften/Elements/JingleDescription.h> #include <Swiften/Elements/StreamInitiationFileInfo.h> @@ -17,15 +17,25 @@ namespace Swift { public: typedef boost::shared_ptr<JingleFileTransferDescription> ref; - void setOffer(const StreamInitiationFileInfo& offer) { - this->offer = offer; + void addOffer(const StreamInitiationFileInfo& offer) { + offers.push_back(offer); } + - const boost::optional<StreamInitiationFileInfo>& getOffer() const { - return offer; + const std::vector<StreamInitiationFileInfo>& getOffers() const { + return offers; + } + + void addRequest(const StreamInitiationFileInfo& request) { + reqeusts.push_back(request); + } + + const std::vector<StreamInitiationFileInfo>& getRequests() const { + return reqeusts; } private: - boost::optional<StreamInitiationFileInfo> offer; + std::vector<StreamInitiationFileInfo> offers; + std::vector<StreamInitiationFileInfo> reqeusts; }; } diff --git a/Swiften/Elements/JingleFileTransferHash.h b/Swiften/Elements/JingleFileTransferHash.h new file mode 100644 index 0000000..5603531 --- /dev/null +++ b/Swiften/Elements/JingleFileTransferHash.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <map> +#include <string> + +#include <Swiften/Elements/JingleDescription.h> + +namespace Swift { + +class JingleFileTransferHash : public Payload { +public: + typedef std::map<std::string, std::string> HashesMap; +public: + typedef boost::shared_ptr<JingleFileTransferHash> ref; + + void setHash(const std::string& algo, const std::string& hash) { + hashes[algo] = hash; + } + + const HashesMap& getHashes() const { + return hashes; + } + +private: + HashesMap hashes; +}; + +} diff --git a/Swiften/Elements/JingleFileTransferReceived.h b/Swiften/Elements/JingleFileTransferReceived.h new file mode 100644 index 0000000..75c95d9 --- /dev/null +++ b/Swiften/Elements/JingleFileTransferReceived.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <vector> + +#include <Swiften/Elements/StreamInitiationFileInfo.h> +#include <Swiften/Elements/Payload.h> + +namespace Swift { + +class JingleFileTransferReceived : public Payload { + public: + typedef boost::shared_ptr<JingleFileTransferReceived> ref; + + void setFileInfo(const StreamInitiationFileInfo& fileInfo) { + this->fileInfo = fileInfo; + } + + const StreamInitiationFileInfo& getFileInfo() const { + return this->fileInfo; + } + private: + StreamInitiationFileInfo fileInfo; + +}; + +} diff --git a/Swiften/Elements/JingleIBBTransportPayload.h b/Swiften/Elements/JingleIBBTransportPayload.h index 67aab09..8c174f0 100644 --- a/Swiften/Elements/JingleIBBTransportPayload.h +++ b/Swiften/Elements/JingleIBBTransportPayload.h @@ -29,14 +29,6 @@ namespace Swift { return stanzaType; } - void setSessionID(const std::string& id) { - sessionID = id; - } - - const std::string& getSessionID() const { - return sessionID; - } - int getBlockSize() const { return blockSize; } @@ -46,7 +38,6 @@ namespace Swift { } private: - std::string sessionID; int blockSize; StanzaType stanzaType; }; diff --git a/Swiften/Elements/JinglePayload.h b/Swiften/Elements/JinglePayload.h index 5c766b8..31d4448 100644 --- a/Swiften/Elements/JinglePayload.h +++ b/Swiften/Elements/JinglePayload.h @@ -7,6 +7,7 @@ #pragma once #include <vector> +#include <boost/shared_ptr.hpp> #include <boost/optional.hpp> #include <string> @@ -14,13 +15,15 @@ #include <Swiften/Elements/Payload.h> #include <Swiften/Elements/JingleContentPayload.h> +#include <Swiften/Base/Log.h> namespace Swift { class JinglePayload : public Payload { public: typedef boost::shared_ptr<JinglePayload> ref; - struct Reason { + struct Reason : public Payload { enum Type { + UnknownType, AlternativeSession, Busy, Cancel, @@ -39,13 +42,15 @@ namespace Swift { UnsupportedApplications, UnsupportedTransports }; - + Reason() : type(UnknownType), text("") {} Reason(Type type, const std::string& text = "") : type(type), text(text) {} + ~Reason() {} Type type; std::string text; }; enum Action { + UnknownAction, ContentAccept, ContentAdd, ContentModify, @@ -62,8 +67,11 @@ namespace Swift { TransportReject, TransportReplace }; - + JinglePayload() : action(SessionTerminate), sessionID("") { + } + JinglePayload(Action action, const std::string& sessionID) : action(action), sessionID(sessionID) { + } void setAction(Action action) { @@ -99,11 +107,46 @@ namespace Swift { } void addContent(JingleContentPayload::ref content) { - this->contents.push_back(content); + this->payloads.push_back(content); + } + + void addPayload(boost::shared_ptr<Payload> payload) { + this->payloads.push_back(payload); + } + + const std::vector<JingleContentPayload::ref> getContents() const { + return getPayloads<JingleContentPayload>(); + } + + const std::vector<boost::shared_ptr<Payload> > getPayloads() const { + return payloads; + } + + template<typename T> + const std::vector<boost::shared_ptr<T> > getPayloads() const { + std::vector<boost::shared_ptr<T> > matched_payloads; + for (std::vector<boost::shared_ptr<Payload> >::const_iterator i = payloads.begin(); i != payloads.end(); ++i) { + boost::shared_ptr<T> result = boost::dynamic_pointer_cast<T>(*i); + if (result) { + matched_payloads.push_back(result); + } + } + + return matched_payloads; + } - const std::vector<JingleContentPayload::ref>& getContents() const { - return contents; + template<typename T> + const boost::shared_ptr<T> getPayload() const { + boost::shared_ptr<T> result; + for (std::vector<boost::shared_ptr<Payload> >::const_iterator i = payloads.begin(); i != payloads.end(); ++i) { + result = boost::dynamic_pointer_cast<T>(*i); + if (result) { + return result; + } + } + + return result; } void setReason(const Reason& reason) { @@ -119,7 +162,7 @@ namespace Swift { JID initiator; JID responder; std::string sessionID; - std::vector<JingleContentPayload::ref> contents; + std::vector<boost::shared_ptr<Payload> > payloads; boost::optional<Reason> reason; }; } diff --git a/Swiften/Elements/JingleS5BTransportPayload.h b/Swiften/Elements/JingleS5BTransportPayload.h index 7b3089f..980af27 100644 --- a/Swiften/Elements/JingleS5BTransportPayload.h +++ b/Swiften/Elements/JingleS5BTransportPayload.h @@ -6,23 +6,107 @@ #pragma once +#include <vector> + +#include <boost/shared_ptr.hpp> + #include <Swiften/Elements/JingleTransportPayload.h> #include <Swiften/Elements/Bytestreams.h> +#include <Swiften/Network/HostAddressPort.h> -// FIXME: Remove Bytestreams, and replace by our own candidate namespace Swift { class JingleS5BTransportPayload : public JingleTransportPayload { public: - const Bytestreams& getInfo() const { - return info; + enum Mode { + TCPMode, // default case + UDPMode, + }; + + struct Candidate { + enum Type { + DirectType, // default case + AssistedType, + TunnelType, + ProxyType, + }; + + Candidate() : priority(0), type(DirectType) {} + + std::string cid; + JID jid; + HostAddressPort hostPort; + int priority; + Type type; + }; + + struct CompareCandidate { + bool operator() (const JingleS5BTransportPayload::Candidate& c1, const JingleS5BTransportPayload::Candidate& c2) const { + if (c1.priority < c2.priority) return true; + return false; + } + }; + + public: + JingleS5BTransportPayload() : mode(TCPMode), candidateError(false), proxyError(false) {} + + Mode getMode() const { + return mode; + } + + void setMode(Mode mode) { + this->mode = mode; + } + + const std::vector<Candidate>& getCandidates() { + return candidates; + } + + void addCandidate(const Candidate& candidate) { + candidates.push_back(candidate); + } + + void setCandidateUsed(const std::string& cid) { + candidateUsedCID = cid; + } + + const std::string& getCandidateUsed() const { + return candidateUsedCID; + } + + void setActivated(const std::string& cid) { + activatedCID = cid; } - void setInfo(const Bytestreams& info) { - this->info = info; + const std::string& getActivated() const { + return activatedCID; } + void setCandidateError(bool hasError) { + candidateError = hasError; + } + + bool hasCandidateError() const { + return candidateError; + } + + void setProxyError(bool hasError) { + proxyError = hasError; + } + + bool hasProxyError() const { + return proxyError; + } + public: + typedef boost::shared_ptr<JingleS5BTransportPayload> ref; + private: - Bytestreams info; + Mode mode; + std::vector<Candidate> candidates; + + std::string candidateUsedCID; + std::string activatedCID; + bool candidateError; + bool proxyError; }; } diff --git a/Swiften/Elements/JingleTransportPayload.h b/Swiften/Elements/JingleTransportPayload.h index 7a9ea29..b870be9 100644 --- a/Swiften/Elements/JingleTransportPayload.h +++ b/Swiften/Elements/JingleTransportPayload.h @@ -13,6 +13,18 @@ namespace Swift { class JingleTransportPayload : public Payload { public: + void setSessionID(const std::string& id) { + sessionID = id; + } + + const std::string& getSessionID() const { + return sessionID; + } + + public: typedef boost::shared_ptr<JingleTransportPayload> ref; + + private: + std::string sessionID; }; } diff --git a/Swiften/Elements/Payload.h b/Swiften/Elements/Payload.h index 8b6d44a..f994ebc 100644 --- a/Swiften/Elements/Payload.h +++ b/Swiften/Elements/Payload.h @@ -6,9 +6,13 @@ #pragma once +#include <boost/shared_ptr.hpp> + namespace Swift { class Payload { public: + typedef boost::shared_ptr<Payload> ref; + public: virtual ~Payload(); }; } diff --git a/Swiften/Elements/S5BProxyRequest.h b/Swiften/Elements/S5BProxyRequest.h new file mode 100644 index 0000000..fcd0cb2 --- /dev/null +++ b/Swiften/Elements/S5BProxyRequest.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <string> + +#include <boost/optional.hpp> + +#include <Swiften/Elements/Payload.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Network/HostAddressPort.h> + +namespace Swift { + +class S5BProxyRequest : public Payload { +public: + typedef boost::shared_ptr<S5BProxyRequest> ref; + +public: + struct StreamHost { + HostAddressPort addressPort; + JID jid; + }; + +public: + const boost::optional<StreamHost>& getStreamHost() const { + return streamHost; + } + + void setStreamHost(const StreamHost& streamHost) { + this->streamHost = boost::optional<StreamHost>(streamHost); + } + + const std::string& getSID() const { + return sid; + } + + void setSID(const std::string& sid) { + this->sid = sid; + } + + const boost::optional<JID>& getActivate() const { + return activate; + } + + void setActivate(const JID& activate) { + this->activate = activate; + } + +private: + boost::optional<StreamHost> streamHost; + + std::string sid; + boost::optional<JID> activate; +}; + +} diff --git a/Swiften/Elements/StreamInitiationFileInfo.h b/Swiften/Elements/StreamInitiationFileInfo.h index 92b9824..9484bc0 100644 --- a/Swiften/Elements/StreamInitiationFileInfo.h +++ b/Swiften/Elements/StreamInitiationFileInfo.h @@ -6,14 +6,97 @@ #pragma once +#include <Swiften/Elements/Payload.h> +#include <boost/shared_ptr.hpp> +#include <boost/date_time/posix_time/posix_time_types.hpp> + #include <string> namespace Swift { - struct StreamInitiationFileInfo { - StreamInitiationFileInfo(const std::string& name = "", const std::string& description = "", int size = -1) : name(name), description(description), size(size) {} - std::string name; - std::string description; - int size; - }; +class StreamInitiationFileInfo : public Payload { +public: + typedef boost::shared_ptr<StreamInitiationFileInfo> ref; + +public: + StreamInitiationFileInfo(const std::string& name = "", const std::string& description = "", int size = 0, + const std::string& hash = "", const boost::posix_time::ptime &date = boost::posix_time::ptime(), const std::string& algo="md5") : + name(name), description(description), size(size), hash(hash), date(date), algo(algo), supportsRangeRequests(false), rangeOffset(0) {} + + void setName(const std::string& name) { + this->name = name;; + } + + const std::string& getName() const { + return this->name; + } + + void setDescription(const std::string& description) { + this->description = description; + } + + const std::string& getDescription() const { + return this->description; + } + + void setSize(const boost::uintmax_t size) { + this->size = size; + } + + boost::uintmax_t getSize() const { + return this->size; + } + + void setHash(const std::string& hash) { + this->hash = hash; + } + + const std::string& getHash() const { + return this->hash; + } + + void setDate(const boost::posix_time::ptime& date) { + this->date = date; + } + + const boost::posix_time::ptime& getDate() const { + return this->date; + } + + void setAlgo(const std::string& algo) { + this->algo = algo; + } + + const std::string& getAlgo() const { + return this->algo; + } + + void setSupportsRangeRequests(const bool supportsIt) { + supportsRangeRequests = supportsIt; + } + + bool getSupportsRangeRequests() const { + return supportsRangeRequests; + } + + void setRangeOffset(const int offset) { + supportsRangeRequests = offset >= 0 ? true : false; + rangeOffset = offset; + } + + int getRangeOffset() const { + return rangeOffset; + } + +private: + std::string name; + std::string description; + boost::uintmax_t size; + std::string hash; + boost::posix_time::ptime date; + std::string algo; + bool supportsRangeRequests; + boost::uintmax_t rangeOffset; +}; + } diff --git a/Swiften/Examples/SendFile/ReceiveFile.cpp b/Swiften/Examples/SendFile/ReceiveFile.cpp index effa1b7..f80f03a 100644 --- a/Swiften/Examples/SendFile/ReceiveFile.cpp +++ b/Swiften/Examples/SendFile/ReceiveFile.cpp @@ -12,12 +12,18 @@ #include <Swiften/Elements/Presence.h> #include <Swiften/Base/foreach.h> #include <Swiften/Client/Client.h> +#include <Swiften/Elements/DiscoInfo.h> #include <Swiften/Network/BoostNetworkFactories.h> #include <Swiften/EventLoop/SimpleEventLoop.h> #include <Swiften/Client/ClientXMLTracer.h> +#include <Swiften/Disco/ClientDiscoManager.h> #include <Swiften/FileTransfer/IncomingFileTransferManager.h> #include <Swiften/FileTransfer/FileWriteBytestream.h> #include <Swiften/Jingle/JingleSessionManager.h> +#include <Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGeneratorFactory.h> +#include <Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelectorFactory.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> +#include <Swiften/FileTransfer/FileTransferManager.h> using namespace Swift; @@ -26,19 +32,20 @@ BoostNetworkFactories networkFactories(&eventLoop); int exitCode = 2; +static const std::string CLIENT_NAME = "Swiften FT Test"; +static const std::string CLIENT_NODE = "http://swift.im"; + class FileReceiver { public: FileReceiver(const JID& jid, const std::string& password) : jid(jid), password(password), jingleSessionManager(NULL), incomingFileTransferManager(NULL) { client = new Swift::Client(jid, password, &networkFactories); client->onConnected.connect(boost::bind(&FileReceiver::handleConnected, this)); client->onDisconnected.connect(boost::bind(&FileReceiver::handleDisconnected, this, _1)); - //tracer = new ClientXMLTracer(client); + tracer = new ClientXMLTracer(client); } ~FileReceiver() { - delete incomingFileTransferManager; - delete jingleSessionManager; - //delete tracer; + delete tracer; client->onDisconnected.disconnect(boost::bind(&FileReceiver::handleDisconnected, this, _1)); client->onConnected.disconnect(boost::bind(&FileReceiver::handleConnected, this)); delete client; @@ -57,13 +64,24 @@ class FileReceiver { private: void handleConnected() { - client->sendPresence(Presence::create()); - jingleSessionManager = new JingleSessionManager(client->getIQRouter()); - incomingFileTransferManager = new IncomingFileTransferManager(jingleSessionManager, client->getIQRouter()); - incomingFileTransferManager->onIncomingFileTransfer.connect(boost::bind(&FileReceiver::handleIncomingFileTransfer, this, _1)); + Swift::logging = true; + client->getFileTransferManager()->startListeningOnPort(9999); + client->getFileTransferManager()->onIncomingFileTransfer.connect(boost::bind(&FileReceiver::handleIncomingFileTransfer, this, _1)); + + DiscoInfo discoInfo; + discoInfo.addIdentity(DiscoInfo::Identity(CLIENT_NAME, "client", "pc")); + discoInfo.addFeature(DiscoInfo::JingleFeature); + discoInfo.addFeature(DiscoInfo::JingleFTFeature); + discoInfo.addFeature(DiscoInfo::Bytestream); + discoInfo.addFeature(DiscoInfo::JingleTransportsIBBFeature); + discoInfo.addFeature(DiscoInfo::JingleTransportsS5BFeature); + client->getDiscoManager()->setCapsNode(CLIENT_NODE); + client->getDiscoManager()->setDiscoInfo(discoInfo); + client->getPresenceSender()->sendPresence(Presence::create()); } void handleIncomingFileTransfer(IncomingFileTransfer::ref transfer) { + SWIFT_LOG(debug) << "foo" << std::endl; incomingFileTransfers.push_back(transfer); transfer->accept(boost::make_shared<FileWriteBytestream>("out")); //transfer->onFinished.connect(boost::bind(&FileReceiver::handleFileTransferFinished, this, _1)); @@ -100,6 +118,9 @@ class FileReceiver { JingleSessionManager* jingleSessionManager; IncomingFileTransferManager* incomingFileTransferManager; std::vector<IncomingFileTransfer::ref> incomingFileTransfers; + DefaultLocalJingleTransportCandidateGeneratorFactory *localFactory; + DefaultRemoteJingleTransportCandidateSelectorFactory *remoteFactory; + SOCKS5BytestreamRegistry* bytestreamRegistry; }; diff --git a/Swiften/Examples/SendFile/SendFile.cpp b/Swiften/Examples/SendFile/SendFile.cpp index 205b442..9b2105b 100644 --- a/Swiften/Examples/SendFile/SendFile.cpp +++ b/Swiften/Examples/SendFile/SendFile.cpp @@ -4,6 +4,7 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ +#include <boost/date_time/posix_time/posix_time.hpp> #include <boost/bind.hpp> #include <boost/filesystem.hpp> #include <iostream> @@ -20,6 +21,17 @@ #include <Swiften/FileTransfer/FileReadBytestream.h> #include <Swiften/FileTransfer/SOCKS5BytestreamServer.h> #include <Swiften/Network/BoostConnectionServer.h> +#include <Swiften/FileTransfer/OutgoingFileTransferManager.h> +#include <Swiften/FileTransfer/OutgoingFileTransfer.h> +#include <Swiften/Jingle/JingleSessionManager.h> +#include <Swiften/Disco/EntityCapsManager.h> +#include <Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGeneratorFactory.h> +#include <Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelectorFactory.h> +#include <Swiften/Base/ByteArray.h> +#include <Swiften/StringCodecs/MD5.h> +#include <Swiften/StringCodecs/SHA1.h> +#include <Swiften/StringCodecs/Hexify.h> +#include <Swiften/FileTransfer/FileTransferManager.h> using namespace Swift; @@ -30,45 +42,65 @@ int exitCode = 2; class FileSender { public: - FileSender(const JID& jid, const std::string& password, const JID& recipient, const boost::filesystem::path& file, int port) : jid(jid), password(password), recipient(recipient), file(file), transfer(NULL) { - connectionServer = BoostConnectionServer::create(port, networkFactories.getIOServiceThread()->getIOService(), &eventLoop); - socksBytestreamServer = new SOCKS5BytestreamServer(connectionServer); + FileSender(const JID& jid, const std::string& password, const JID& recipient, const boost::filesystem::path& file) : jid(jid), password(password), recipient(recipient), file(file) { client = new Swift::Client(jid, password, &networkFactories); client->onConnected.connect(boost::bind(&FileSender::handleConnected, this)); client->onDisconnected.connect(boost::bind(&FileSender::handleDisconnected, this, _1)); - //tracer = new ClientXMLTracer(client); + tracer = new ClientXMLTracer(client); + client->getEntityCapsProvider()->onCapsChanged.connect(boost::bind(&FileSender::handleCapsChanged, this, _1)); } ~FileSender() { - //delete tracer; + delete tracer; client->onDisconnected.disconnect(boost::bind(&FileSender::handleDisconnected, this, _1)); client->onConnected.disconnect(boost::bind(&FileSender::handleConnected, this)); delete client; - delete socksBytestreamServer; } - + void start() { - connectionServer->start(); - socksBytestreamServer->start(); client->connect(); } - void stop() { - if (transfer) { - transfer->stop(); - } - client->disconnect(); - socksBytestreamServer->stop(); - connectionServer->stop(); - } - private: void handleConnected() { client->sendPresence(Presence::create()); + + client->getFileTransferManager()->startListeningOnPort(19999); + //ByteArray fileData; + //readByteArrayFromFile(fileData, file.string()); + + // gather file information + /*StreamInitiationFileInfo fileInfo; + + fileInfo.setName(file.filename()); + fileInfo.setSize(boost::filesystem::file_size(file)); + fileInfo.setDescription("Some file!"); + fileInfo.setDate(boost::posix_time::from_time_t(boost::filesystem::last_write_time(file)));*/ + //fileInfo.setHash(Hexify::hexify(MD5::getHash(fileData))); + /* transfer = new OutgoingSIFileTransfer("myid", client->getJID(), recipient, file.filename(), boost::filesystem::file_size(file), "A file", boost::shared_ptr<FileReadBytestream>(new FileReadBytestream(file)), client->getIQRouter(), socksBytestreamServer); transfer->onFinished.connect(boost::bind(&FileSender::handleFileTransferFinished, this, _1)); transfer->start(); + */ + } + + void handleCapsChanged(JID jid) { + if (jid.toBare() == recipient) { + // create ReadBytestream from file + boost::shared_ptr<FileReadBytestream> fileStream = boost::make_shared<FileReadBytestream>(file); + + outgoingFileTransfer = client->getFileTransferManager()->createOutgoingFileTransfer(recipient, file, "Some File!", fileStream); + + if (outgoingFileTransfer) { + std::cout << "started FT" << std::endl; + outgoingFileTransfer->start(); + // TODO: getting notified about FT status and end + } else { + std::cout << "[ ERROR ] " << recipient << " doesn't support any kind of file transfer!" << std::endl; + //client->disconnect(); + } + } } void handleDisconnected(const boost::optional<ClientError>&) { @@ -85,23 +117,23 @@ class FileSender { exit(0); } } - + void exit(int code) { exitCode = code; - stop(); eventLoop.stop(); } private: BoostConnectionServer::ref connectionServer; SOCKS5BytestreamServer* socksBytestreamServer; + SOCKS5BytestreamRegistry* registry; + OutgoingFileTransfer::ref outgoingFileTransfer; JID jid; std::string password; JID recipient; boost::filesystem::path file; Client* client; ClientXMLTracer* tracer; - OutgoingSIFileTransfer* transfer; }; @@ -113,9 +145,9 @@ int main(int argc, char* argv[]) { JID sender(argv[1]); JID recipient(argv[3]); - FileSender fileSender(sender, std::string(argv[2]), recipient, boost::filesystem::path(argv[4]), 8888); + Swift::logging = true; + FileSender fileSender(sender, std::string(argv[2]), recipient, boost::filesystem::path(argv[4])); fileSender.start(); - { /*BoostTimer::ref timer(BoostTimer::create(30000, &MainBoostIOServiceThread::getInstance().getIOService())); timer->onTick.connect(boost::bind(&SimpleEventLoop::stop, &eventLoop)); diff --git a/Swiften/FileTransfer/ByteArrayReadBytestream.h b/Swiften/FileTransfer/ByteArrayReadBytestream.h index 4704db6..a6945c3 100644 --- a/Swiften/FileTransfer/ByteArrayReadBytestream.h +++ b/Swiften/FileTransfer/ByteArrayReadBytestream.h @@ -22,6 +22,8 @@ namespace Swift { readSize = data.size() - position; } std::vector<unsigned char> result(data.begin() + position, data.begin() + position + readSize); + + onRead(result); position += readSize; return result; } diff --git a/Swiften/FileTransfer/ByteArrayWriteBytestream.h b/Swiften/FileTransfer/ByteArrayWriteBytestream.h index 6c360e6..ef97ed9 100644 --- a/Swiften/FileTransfer/ByteArrayWriteBytestream.h +++ b/Swiften/FileTransfer/ByteArrayWriteBytestream.h @@ -16,6 +16,7 @@ namespace Swift { virtual void write(const std::vector<unsigned char>& bytes) { data.insert(data.end(), bytes.begin(), bytes.end()); + onWrite(bytes); } const std::vector<unsigned char>& getData() const { diff --git a/Swiften/FileTransfer/ConnectivityManager.cpp b/Swiften/FileTransfer/ConnectivityManager.cpp new file mode 100644 index 0000000..174d6ab --- /dev/null +++ b/Swiften/FileTransfer/ConnectivityManager.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "ConnectivityManager.h" + +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/Log.h> +#include <Swiften/Network/NetworkInterface.h> +#include <Swiften/Network/PlatformNATTraversalGetPublicIPRequest.h> +#include <Swiften/Network/PlatformNATTraversalRemovePortForwardingRequest.h> +#include <Swiften/Network/PlatformNATTraversalWorker.h> +#include <Swiften/Network/PlatformNetworkEnvironment.h> + +namespace Swift { + +ConnectivityManager::ConnectivityManager(PlatformNATTraversalWorker* worker) : natTraversalWorker(worker) { + +} + +ConnectivityManager::~ConnectivityManager() { + std::set<int> leftOpenPorts = ports; + foreach(int port, leftOpenPorts) { + removeListeningPort(port); + } +} + +void ConnectivityManager::addListeningPort(int port) { + ports.insert(port); + boost::shared_ptr<PlatformNATTraversalGetPublicIPRequest> getIPRequest = natTraversalWorker->createGetPublicIPRequest(); + if (getIPRequest) { + getIPRequest->onResult.connect(boost::bind(&ConnectivityManager::natTraversalGetPublicIPResult, this, _1)); + getIPRequest->run(); + } + + boost::shared_ptr<PlatformNATTraversalForwardPortRequest> forwardPortRequest = natTraversalWorker->createForwardPortRequest(port, port); + if (forwardPortRequest) { + forwardPortRequest->onResult.connect(boost::bind(&ConnectivityManager::natTraversalForwardPortResult, this, _1)); + forwardPortRequest->run(); + } +} + +void ConnectivityManager::removeListeningPort(int port) { + SWIFT_LOG(debug) << "remove listening port " << port << std::endl; + ports.erase(port); + boost::shared_ptr<PlatformNATTraversalRemovePortForwardingRequest> removePortForwardingRequest = natTraversalWorker->createRemovePortForwardingRequest(port, port); + if (removePortForwardingRequest) { + removePortForwardingRequest->run(); + } +} + +std::vector<HostAddressPort> ConnectivityManager::getHostAddressPorts() const { + PlatformNetworkEnvironment env; + std::vector<HostAddressPort> results; + + std::vector<HostAddress> addresses; + + foreach (NetworkInterface::ref iface, env.getNetworkInterfaces()) { + foreach (HostAddress address, iface->getAddresses()) { + foreach (int port, ports) { + results.push_back(HostAddressPort(address, port)); + } + } + } + + return results; +} + +std::vector<HostAddressPort> ConnectivityManager::getAssistedHostAddressPorts() const { + std::vector<HostAddressPort> results; + + if (publicAddress) { + foreach (int port, ports) { + results.push_back(HostAddressPort(publicAddress.get(), port)); + } + } + + return results; +} + +void ConnectivityManager::natTraversalGetPublicIPResult(boost::optional<HostAddress> address) { + if (address) { + publicAddress = address; + SWIFT_LOG(debug) << "Public IP discovered as " << publicAddress.get().toString() << "." << std::endl; + } else { + SWIFT_LOG(debug) << "No public IP discoverable." << std::endl; + } +} + +void ConnectivityManager::natTraversalForwardPortResult(boost::optional<PlatformNATTraversalForwardPortRequest::PortMapping> mapping) { + if (mapping) { + SWIFT_LOG(debug) << "Mapping port was successful." << std::endl; + } else { + SWIFT_LOG(debug) << "Mapping port has failed." << std::endl; + } +} + + +} diff --git a/Swiften/FileTransfer/ConnectivityManager.h b/Swiften/FileTransfer/ConnectivityManager.h new file mode 100644 index 0000000..87041b2 --- /dev/null +++ b/Swiften/FileTransfer/ConnectivityManager.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <vector> +#include <set> + +#include <boost/optional.hpp> + +#include <Swiften/Network/HostAddressPort.h> +#include <Swiften/Network/PlatformNATTraversalForwardPortRequest.h> + +namespace Swift { + +class PlatformNATTraversalWorker; + +class ConnectivityManager { +public: + ConnectivityManager(PlatformNATTraversalWorker*); + ~ConnectivityManager(); +public: + void addListeningPort(int port); + void removeListeningPort(int port); + + std::vector<HostAddressPort> getHostAddressPorts() const; + std::vector<HostAddressPort> getAssistedHostAddressPorts() const; + +private: + void natTraversalGetPublicIPResult(boost::optional<HostAddress> address); + void natTraversalForwardPortResult(boost::optional<PlatformNATTraversalForwardPortRequest::PortMapping> mapping); + +private: + PlatformNATTraversalWorker* natTraversalWorker; + + std::set<int> ports; + boost::optional<HostAddress> publicAddress; +}; + +} diff --git a/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGenerator.cpp b/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGenerator.cpp new file mode 100644 index 0000000..5b6da4c --- /dev/null +++ b/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGenerator.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "DefaultLocalJingleTransportCandidateGenerator.h" + +#include <vector> + +#include <boost/shared_ptr.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/Log.h> +#include <Swiften/Elements/JingleIBBTransportPayload.h> +#include <Swiften/Elements/JingleS5BTransportPayload.h> +#include <Swiften/FileTransfer/ConnectivityManager.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamProxy.h> + +namespace Swift { + +DefaultLocalJingleTransportCandidateGenerator::DefaultLocalJingleTransportCandidateGenerator(ConnectivityManager* connectivityManager, SOCKS5BytestreamRegistry* s5bRegistry, SOCKS5BytestreamProxy* s5bProxy, JID& ownJID) : connectivityManager(connectivityManager), s5bRegistry(s5bRegistry), s5bProxy(s5bProxy), ownJID(ownJID) { +} + +DefaultLocalJingleTransportCandidateGenerator::~DefaultLocalJingleTransportCandidateGenerator() { +} + +void DefaultLocalJingleTransportCandidateGenerator::generateLocalTransportCandidates(JingleTransportPayload::ref transportPayload) { + if (boost::dynamic_pointer_cast<JingleIBBTransportPayload>(transportPayload)) { + JingleTransportPayload::ref payL = boost::make_shared<JingleTransportPayload>(); + payL->setSessionID(transportPayload->getSessionID()); + onLocalTransportCandidatesGenerated(payL); + } + if (boost::dynamic_pointer_cast<JingleS5BTransportPayload>(transportPayload)) { + JingleS5BTransportPayload::ref payL = boost::make_shared<JingleS5BTransportPayload>(); + payL->setSessionID(transportPayload->getSessionID()); + payL->setMode(JingleS5BTransportPayload::TCPMode); + + const unsigned long localPreference = 0; + + // get direct candidates + std::vector<HostAddressPort> directCandidates = connectivityManager->getHostAddressPorts(); + foreach(HostAddressPort addressPort, directCandidates) { + JingleS5BTransportPayload::Candidate candidate; + candidate.type = JingleS5BTransportPayload::Candidate::DirectType; + candidate.jid = ownJID; + candidate.hostPort = addressPort; + candidate.priority = 65536 * 126 + localPreference; + candidate.cid = idGenerator.generateID(); + payL->addCandidate(candidate); + } + + // get assissted candidates + std::vector<HostAddressPort> assisstedCandidates = connectivityManager->getAssistedHostAddressPorts(); + foreach(HostAddressPort addressPort, assisstedCandidates) { + JingleS5BTransportPayload::Candidate candidate; + candidate.type = JingleS5BTransportPayload::Candidate::AssistedType; + candidate.jid = ownJID; + candidate.hostPort = addressPort; + candidate.priority = 65536 * 120 + localPreference; + candidate.cid = idGenerator.generateID(); + payL->addCandidate(candidate); + } + + // get proxy candidates + std::vector<S5BProxyRequest::ref> proxyCandidates = s5bProxy->getS5BProxies(); + foreach(S5BProxyRequest::ref proxy, proxyCandidates) { + JingleS5BTransportPayload::Candidate candidate; + candidate.type = JingleS5BTransportPayload::Candidate::ProxyType; + candidate.jid = proxy->getStreamHost().get().jid; + candidate.hostPort = proxy->getStreamHost().get().addressPort; + candidate.priority = 65536 * 10 + localPreference; + candidate.cid = idGenerator.generateID(); + payL->addCandidate(candidate); + } + + onLocalTransportCandidatesGenerated(payL); + } + +} + +bool DefaultLocalJingleTransportCandidateGenerator::isActualCandidate(JingleTransportPayload::ref transportPayload) { + if (!transportPayload.get()) return false; + return false; +} + +int DefaultLocalJingleTransportCandidateGenerator::getPriority(JingleTransportPayload::ref /* transportPayload */) { + return 0; +} + +JingleTransport::ref DefaultLocalJingleTransportCandidateGenerator::selectTransport(JingleTransportPayload::ref /* transportPayload */) { + return JingleTransport::ref(); +} + +} diff --git a/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGenerator.h b/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGenerator.h new file mode 100644 index 0000000..7d45491 --- /dev/null +++ b/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGenerator.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/FileTransfer/LocalJingleTransportCandidateGenerator.h> + +#include <Swiften/Base/IDGenerator.h> +#include <Swiften/JID/JID.h> + +namespace Swift { + +class SOCKS5BytestreamRegistry; +class SOCKS5BytestreamProxy; +class ConnectivityManager; + +class DefaultLocalJingleTransportCandidateGenerator : public LocalJingleTransportCandidateGenerator { +public: + DefaultLocalJingleTransportCandidateGenerator(ConnectivityManager* connectivityManager, SOCKS5BytestreamRegistry* s5bRegistry, SOCKS5BytestreamProxy* s5bProxy, JID& ownJID); + virtual ~DefaultLocalJingleTransportCandidateGenerator(); + + virtual void generateLocalTransportCandidates(JingleTransportPayload::ref); + + virtual bool isActualCandidate(JingleTransportPayload::ref); + virtual int getPriority(JingleTransportPayload::ref); + virtual JingleTransport::ref selectTransport(JingleTransportPayload::ref); + +private: + IDGenerator idGenerator; + ConnectivityManager* connectivityManager; + SOCKS5BytestreamRegistry* s5bRegistry; + SOCKS5BytestreamProxy* s5bProxy; + JID ownJID; +}; + +} diff --git a/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGeneratorFactory.cpp b/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGeneratorFactory.cpp new file mode 100644 index 0000000..ed0386e --- /dev/null +++ b/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGeneratorFactory.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "DefaultLocalJingleTransportCandidateGeneratorFactory.h" + +#include <Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGenerator.h> +#include <Swiften/Base/Log.h> + +namespace Swift { + +DefaultLocalJingleTransportCandidateGeneratorFactory::DefaultLocalJingleTransportCandidateGeneratorFactory(ConnectivityManager* connectivityManager, SOCKS5BytestreamRegistry* s5bRegistry, SOCKS5BytestreamProxy* s5bProxy, const JID& ownJID) : connectivityManager(connectivityManager), s5bRegistry(s5bRegistry), s5bProxy(s5bProxy), ownJID(ownJID) { +} + +DefaultLocalJingleTransportCandidateGeneratorFactory::~DefaultLocalJingleTransportCandidateGeneratorFactory() { +} + +LocalJingleTransportCandidateGenerator* DefaultLocalJingleTransportCandidateGeneratorFactory::createCandidateGenerator() { + return new DefaultLocalJingleTransportCandidateGenerator(connectivityManager, s5bRegistry, s5bProxy, ownJID); +} + + +} diff --git a/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGeneratorFactory.h b/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGeneratorFactory.h new file mode 100644 index 0000000..511d0a1 --- /dev/null +++ b/Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGeneratorFactory.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/FileTransfer/LocalJingleTransportCandidateGeneratorFactory.h> + +#include <Swiften/JID/JID.h> + +namespace Swift { + +class ConnectivityManager; +class SOCKS5BytestreamRegistry; +class SOCKS5BytestreamProxy; + +class DefaultLocalJingleTransportCandidateGeneratorFactory : public LocalJingleTransportCandidateGeneratorFactory{ +public: + DefaultLocalJingleTransportCandidateGeneratorFactory(ConnectivityManager* connectivityManager, SOCKS5BytestreamRegistry* s5bRegistry, SOCKS5BytestreamProxy* s5bProxy, const JID& ownJID); + virtual ~DefaultLocalJingleTransportCandidateGeneratorFactory(); + + LocalJingleTransportCandidateGenerator* createCandidateGenerator(); + +private: + ConnectivityManager* connectivityManager; + SOCKS5BytestreamRegistry* s5bRegistry; + SOCKS5BytestreamProxy* s5bProxy; + JID ownJID; +}; + +} diff --git a/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelector.cpp b/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelector.cpp new file mode 100644 index 0000000..32b4df8 --- /dev/null +++ b/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelector.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "DefaultRemoteJingleTransportCandidateSelector.h" + +#include <boost/smart_ptr/make_shared.hpp> +#include <boost/bind.hpp> + +#include <Swiften/Base/Log.h> +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/Base/foreach.h> +#include <Swiften/Elements/JingleS5BTransportPayload.h> +#include <Swiften/Network/ConnectionFactory.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> + +namespace Swift { + +DefaultRemoteJingleTransportCandidateSelector::DefaultRemoteJingleTransportCandidateSelector(ConnectionFactory* connectionFactory, TimerFactory* timerFactory) : connectionFactory(connectionFactory), timerFactory(timerFactory) { +} + +DefaultRemoteJingleTransportCandidateSelector::~DefaultRemoteJingleTransportCandidateSelector() { +} + +void DefaultRemoteJingleTransportCandidateSelector::addRemoteTransportCandidates(JingleTransportPayload::ref transportPayload) { + JingleS5BTransportPayload::ref s5bPayload; + transportSID = transportPayload->getSessionID(); + if ((s5bPayload = boost::dynamic_pointer_cast<JingleS5BTransportPayload>(transportPayload))) { + foreach(JingleS5BTransportPayload::Candidate c, s5bPayload->getCandidates()) { + candidates.push(c); + } + } +} + +void DefaultRemoteJingleTransportCandidateSelector::selectCandidate() { + tryNextCandidate(true); +} + +void DefaultRemoteJingleTransportCandidateSelector::tryNextCandidate(bool error) { + if (error) { + if (s5bSession) { + SWIFT_LOG(debug) << "failed to connect" << std::endl; + } + if (candidates.empty()) { + // failed to connect to any of the candidates + // issue an error + SWIFT_LOG(debug) << "out of candidates )=" << std::endl; + JingleS5BTransportPayload::ref failed = boost::make_shared<JingleS5BTransportPayload>(); + failed->setCandidateError(true); + failed->setSessionID(transportSID); + onRemoteTransportCandidateSelectFinished(failed); + } else { + lastCandidate = candidates.top(); + // only try direct or assisted for now + if (lastCandidate.type == JingleS5BTransportPayload::Candidate::DirectType || + lastCandidate.type == JingleS5BTransportPayload::Candidate::AssistedType || lastCandidate.type == JingleS5BTransportPayload::Candidate::ProxyType ) { + // create connection + connection = connectionFactory->createConnection(); + s5bSession = boost::make_shared<SOCKS5BytestreamClientSession>(connection, lastCandidate.hostPort, SOCKS5BytestreamRegistry::getHostname(transportSID, requester, target), timerFactory); + + // bind onReady to this method + s5bSession->onSessionReady.connect(boost::bind(&DefaultRemoteJingleTransportCandidateSelector::tryNextCandidate, this, _1)); + + std::string candidateType; + if (lastCandidate.type == JingleS5BTransportPayload::Candidate::DirectType) { + candidateType = "direct"; + } else if (lastCandidate.type == JingleS5BTransportPayload::Candidate::AssistedType) { + candidateType = "assisted"; + } else if (lastCandidate.type == JingleS5BTransportPayload::Candidate::ProxyType) { + candidateType = "proxy"; + } + + // initiate connect + SWIFT_LOG(debug) << "try to connect to candidate of type " << candidateType << " : " << lastCandidate.hostPort.toString() << std::endl; + s5bSession->start(); + + // that's it. we're gonna be called back + candidates.pop(); + } else { + s5bSession.reset(); + candidates.pop(); + tryNextCandidate(true); + } + } + } else { + // we have a working connection, hooray + JingleS5BTransportPayload::ref success = boost::make_shared<JingleS5BTransportPayload>(); + success->setCandidateUsed(lastCandidate.cid); + success->setSessionID(transportSID); + onRemoteTransportCandidateSelectFinished(success); + } +} + +void DefaultRemoteJingleTransportCandidateSelector::setMinimumPriority(int priority) { + SWIFT_LOG(debug) << "priority: " << priority << std::endl; +} + +void DefaultRemoteJingleTransportCandidateSelector::setRequesterTargtet(const JID& requester, const JID& target) { + this->requester = requester; + this->target = target; +} + +SOCKS5BytestreamClientSession::ref DefaultRemoteJingleTransportCandidateSelector::getS5BSession() { + return s5bSession; +} + +bool DefaultRemoteJingleTransportCandidateSelector::isActualCandidate(JingleTransportPayload::ref /* transportPayload */) { + return false; +} + +int DefaultRemoteJingleTransportCandidateSelector::getPriority(JingleTransportPayload::ref /* transportPayload */) { + return 0; +} + +JingleTransport::ref DefaultRemoteJingleTransportCandidateSelector::selectTransport(JingleTransportPayload::ref /* transportPayload */) { + return JingleTransport::ref(); +} + +} diff --git a/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelector.h b/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelector.h new file mode 100644 index 0000000..255acd9 --- /dev/null +++ b/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelector.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <queue> +#include <vector> + +#include <boost/shared_ptr.hpp> + +#include <Swiften/JID/JID.h> +#include <Swiften/Network/Connection.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamClientSession.h> +#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.h> +#include <Swiften/Elements/JingleS5BTransportPayload.h> + + +namespace Swift { + +class ConnectionFactory; +class TimerFactory; + +class DefaultRemoteJingleTransportCandidateSelector : public RemoteJingleTransportCandidateSelector { +public: + DefaultRemoteJingleTransportCandidateSelector(ConnectionFactory*, TimerFactory*); + virtual ~DefaultRemoteJingleTransportCandidateSelector(); + + virtual void addRemoteTransportCandidates(JingleTransportPayload::ref); + virtual void selectCandidate(); + virtual void setMinimumPriority(int); + void setRequesterTargtet(const JID& requester, const JID& target); + virtual SOCKS5BytestreamClientSession::ref getS5BSession(); + + virtual bool isActualCandidate(JingleTransportPayload::ref); + virtual int getPriority(JingleTransportPayload::ref); + virtual JingleTransport::ref selectTransport(JingleTransportPayload::ref); + +private: + void tryNextCandidate(bool error); + +private: + ConnectionFactory* connectionFactory; + TimerFactory* timerFactory; + + std::priority_queue<JingleS5BTransportPayload::Candidate, std::vector<JingleS5BTransportPayload::Candidate>, JingleS5BTransportPayload::CompareCandidate> candidates; + + std::string transportSID; + boost::shared_ptr<Connection> connection; + boost::shared_ptr<SOCKS5BytestreamClientSession> s5bSession; + JingleS5BTransportPayload::Candidate lastCandidate; + JID requester; + JID target; +}; + +} diff --git a/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelectorFactory.cpp b/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelectorFactory.cpp new file mode 100644 index 0000000..8ebbf46 --- /dev/null +++ b/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelectorFactory.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "DefaultRemoteJingleTransportCandidateSelectorFactory.h" + +#include <Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelector.h> + +#include <Swiften/Base/Log.h> + +namespace Swift { + +DefaultRemoteJingleTransportCandidateSelectorFactory::DefaultRemoteJingleTransportCandidateSelectorFactory(ConnectionFactory* connectionFactory, TimerFactory* timerFactory) : connectionFactory(connectionFactory), timerFactory(timerFactory) { +} + +DefaultRemoteJingleTransportCandidateSelectorFactory::~DefaultRemoteJingleTransportCandidateSelectorFactory() { +} + +RemoteJingleTransportCandidateSelector* DefaultRemoteJingleTransportCandidateSelectorFactory::createCandidateSelector() { + return new DefaultRemoteJingleTransportCandidateSelector(connectionFactory, timerFactory); +} + +} diff --git a/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelectorFactory.h b/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelectorFactory.h new file mode 100644 index 0000000..ca29e1f --- /dev/null +++ b/Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelectorFactory.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelectorFactory.h> + +namespace Swift { + +class ConnectionFactory; +class TimerFactory; + +class DefaultRemoteJingleTransportCandidateSelectorFactory : public RemoteJingleTransportCandidateSelectorFactory { +public: + DefaultRemoteJingleTransportCandidateSelectorFactory(ConnectionFactory*, TimerFactory*); + virtual ~DefaultRemoteJingleTransportCandidateSelectorFactory(); + + RemoteJingleTransportCandidateSelector* createCandidateSelector(); + +private: + ConnectionFactory* connectionFactory; + TimerFactory* timerFactory; +}; + +} diff --git a/Swiften/FileTransfer/FileReadBytestream.cpp b/Swiften/FileTransfer/FileReadBytestream.cpp index 84d6490..f0139b8 100644 --- a/Swiften/FileTransfer/FileReadBytestream.cpp +++ b/Swiften/FileTransfer/FileReadBytestream.cpp @@ -30,6 +30,7 @@ std::vector<unsigned char> FileReadBytestream::read(size_t size) { assert(stream->good()); stream->read(reinterpret_cast<char*>(&result[0]), size); result.resize(stream->gcount()); + onRead(result); return result; } diff --git a/Swiften/FileTransfer/FileTransfer.h b/Swiften/FileTransfer/FileTransfer.h new file mode 100644 index 0000000..6c37d8d --- /dev/null +++ b/Swiften/FileTransfer/FileTransfer.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/optional.hpp> +#include <boost/shared_ptr.hpp> + +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/FileTransfer/FileTransferError.h> + +namespace Swift { + +class FileTransfer { +public: + struct State { + enum FTState { + Canceled, + Failed, + Finished, + Negotiating, + Transferring, + WaitingForStart, + WaitingForAccept, + }; + + FTState state; + std::string message; + + State(FTState state) : state(state), message("") {} + State(FTState state, std::string message) : state(state), message(message) {} + }; + +public: + typedef boost::shared_ptr<FileTransfer> ref; + +public: + uintmax_t fileSizeInBytes; + std::string filename; + std::string algo; + std::string hash; + +public: + virtual void cancel() = 0; + +public: + boost::signal<void (int /* proccessedBytes */)> onProcessedBytes; + boost::signal<void (State)> onStateChange; + boost::signal<void (boost::optional<FileTransferError>)> onFinished; + +public: + virtual ~FileTransfer() {} +}; + +} diff --git a/Swiften/FileTransfer/FileTransferManager.cpp b/Swiften/FileTransfer/FileTransferManager.cpp new file mode 100644 index 0000000..69be852 --- /dev/null +++ b/Swiften/FileTransfer/FileTransferManager.cpp @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swiften/FileTransfer/FileTransferManager.h> + +namespace Swift { + +FileTransferManager::~FileTransferManager() { +} + +} diff --git a/Swiften/FileTransfer/FileTransferManager.h b/Swiften/FileTransfer/FileTransferManager.h new file mode 100644 index 0000000..3a1628f --- /dev/null +++ b/Swiften/FileTransfer/FileTransferManager.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <string> +#include <boost/filesystem.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> + +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/JID/JID.h> +#include <Swiften/FileTransfer/OutgoingFileTransfer.h> +#include <Swiften/FileTransfer/IncomingFileTransfer.h> + +namespace Swift { + class ReadBytestream; + class S5BProxyRequest; + + class FileTransferManager { + public: + virtual ~FileTransferManager(); + + virtual void startListeningOnPort(int port) = 0; + virtual void addS5BProxy(boost::shared_ptr<S5BProxyRequest> proxy) = 0; + + virtual OutgoingFileTransfer::ref createOutgoingFileTransfer(const JID& to, const boost::filesystem::path& filepath, const std::string& description, boost::shared_ptr<ReadBytestream> bytestream) = 0; + virtual OutgoingFileTransfer::ref createOutgoingFileTransfer(const JID& to, const std::string& filename, const std::string& description, const boost::uintmax_t sizeInBytes, const boost::posix_time::ptime& lastModified, boost::shared_ptr<ReadBytestream> bytestream) = 0; + + boost::signal<void (IncomingFileTransfer::ref)> onIncomingFileTransfer; + }; +} diff --git a/Swiften/FileTransfer/FileTransferManagerImpl.cpp b/Swiften/FileTransfer/FileTransferManagerImpl.cpp new file mode 100644 index 0000000..f89a3e9 --- /dev/null +++ b/Swiften/FileTransfer/FileTransferManagerImpl.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swiften/FileTransfer/FileTransferManagerImpl.h> + +#include <boost/bind.hpp> + +#include <Swiften/Base/foreach.h> +#include "Swiften/Disco/EntityCapsProvider.h" +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/StreamInitiationFileInfo.h> +#include <Swiften/FileTransfer/ConnectivityManager.h> +#include <Swiften/FileTransfer/OutgoingFileTransferManager.h> +#include <Swiften/FileTransfer/IncomingFileTransferManager.h> +#include <Swiften/FileTransfer/DefaultLocalJingleTransportCandidateGeneratorFactory.h> +#include <Swiften/FileTransfer/DefaultRemoteJingleTransportCandidateSelectorFactory.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamServer.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamProxy.h> +#include <Swiften/Presence/PresenceOracle.h> +#include <Swiften/Elements/Presence.h> +#include <Swiften/Network/ConnectionFactory.h> +#include <Swiften/Network/ConnectionServerFactory.h> +#include <Swiften/Network/HostAddress.h> +#include <Swiften/Network/PlatformNATTraversalWorker.h> + +namespace Swift { + +FileTransferManagerImpl::FileTransferManagerImpl(const JID& ownFullJID, JingleSessionManager* jingleSessionManager, IQRouter* router, EntityCapsProvider* capsProvider, PresenceOracle* presOracle, ConnectionFactory* connectionFactory, ConnectionServerFactory* connectionServerFactory, TimerFactory* timerFactory, PlatformNATTraversalWorker* natTraversalWorker) : ownJID(ownFullJID), jingleSM(jingleSessionManager), iqRouter(router), capsProvider(capsProvider), presenceOracle(presOracle), timerFactory(timerFactory), connectionFactory(connectionFactory), connectionServerFactory(connectionServerFactory), natTraversalWorker(natTraversalWorker), bytestreamServer(NULL) { + assert(!ownFullJID.isBare()); + + connectivityManager = new ConnectivityManager(natTraversalWorker); + bytestreamRegistry = new SOCKS5BytestreamRegistry(); + bytestreamProxy = new SOCKS5BytestreamProxy(connectionFactory, timerFactory); + + localCandidateGeneratorFactory = new DefaultLocalJingleTransportCandidateGeneratorFactory(connectivityManager, bytestreamRegistry, bytestreamProxy, ownFullJID); + remoteCandidateSelectorFactory = new DefaultRemoteJingleTransportCandidateSelectorFactory(connectionFactory, timerFactory); + outgoingFTManager = new OutgoingFileTransferManager(ownJID, jingleSM, iqRouter, capsProvider, remoteCandidateSelectorFactory, localCandidateGeneratorFactory, bytestreamRegistry, bytestreamProxy); + incomingFTManager = new IncomingFileTransferManager(ownJID, jingleSM, iqRouter, remoteCandidateSelectorFactory, localCandidateGeneratorFactory, bytestreamRegistry, bytestreamProxy, timerFactory); + incomingFTManager->onIncomingFileTransfer.connect(onIncomingFileTransfer); +} + +FileTransferManagerImpl::~FileTransferManagerImpl() { + if (bytestreamServer) { + bytestreamServer->stop(); + } + delete incomingFTManager; + delete outgoingFTManager; + delete remoteCandidateSelectorFactory; + delete localCandidateGeneratorFactory; + delete connectivityManager; +} + +void FileTransferManagerImpl::startListeningOnPort(int port) { + // TODO: create a server for each interface we're on + SWIFT_LOG(debug) << "Start listening on port " << port << " and hope it's not in use." << std::endl; + boost::shared_ptr<ConnectionServer> server = connectionServerFactory->createConnectionServer(HostAddress("0.0.0.0"), port); + server->start(); + bytestreamServer = new SOCKS5BytestreamServer(server, bytestreamRegistry); + bytestreamServer->start(); + connectivityManager->addListeningPort(port); +} + +void FileTransferManagerImpl::addS5BProxy(S5BProxyRequest::ref proxy) { + bytestreamProxy->addS5BProxy(proxy); +} + +boost::optional<JID> FileTransferManagerImpl::highestPriorityJIDSupportingFileTransfer(const JID& bareJID) { + JID fullReceipientJID; + int priority = INT_MIN; + + //getAllPresence(bareJID) gives you all presences for the bare JID (i.e. all resources) Remko Tronçon @ 11:11 + std::vector<Presence::ref> presences = presenceOracle->getAllPresence(bareJID); + + //iterate over them + foreach(Presence::ref pres, presences) { + if (pres->getPriority() > priority) { + // look up caps from the jid + DiscoInfo::ref info = capsProvider->getCaps(pres->getFrom()); + if (info && info->hasFeature(DiscoInfo::JingleFeature) && info->hasFeature(DiscoInfo::JingleFTFeature) && + info->hasFeature(DiscoInfo::JingleTransportsIBBFeature)) { + + priority = pres->getPriority(); + fullReceipientJID = pres->getFrom(); + } + } + } + + return fullReceipientJID.isValid() ? boost::optional<JID>(fullReceipientJID) : boost::optional<JID>(); +} + +OutgoingFileTransfer::ref FileTransferManagerImpl::createOutgoingFileTransfer(const JID& to, const boost::filesystem::path& filepath, const std::string& description, boost::shared_ptr<ReadBytestream> bytestream) { + std::string filename = filepath.filename(); + uintmax_t sizeInBytes = boost::filesystem::file_size(filepath); + boost::posix_time::ptime lastModified = boost::posix_time::from_time_t(boost::filesystem::last_write_time(filepath)); + return createOutgoingFileTransfer(to, filename, description, sizeInBytes, lastModified, bytestream); +} + +OutgoingFileTransfer::ref FileTransferManagerImpl::createOutgoingFileTransfer(const JID& to, const std::string& filename, const std::string& description, const uintmax_t sizeInBytes, const boost::posix_time::ptime& lastModified, boost::shared_ptr<ReadBytestream> bytestream) { + StreamInitiationFileInfo fileInfo; + fileInfo.setDate(lastModified); + fileInfo.setSize(sizeInBytes); + fileInfo.setName(filename); + fileInfo.setDescription(description); + + JID receipient = to; + + if(receipient.isBare()) { + boost::optional<JID> fullJID = highestPriorityJIDSupportingFileTransfer(receipient); + if (fullJID.is_initialized()) { + receipient = fullJID.get(); + } else { + return OutgoingFileTransfer::ref(); + } + } + + return outgoingFTManager->createOutgoingFileTransfer(receipient, bytestream, fileInfo); +} + +} diff --git a/Swiften/FileTransfer/FileTransferManagerImpl.h b/Swiften/FileTransfer/FileTransferManagerImpl.h new file mode 100644 index 0000000..b38eaea --- /dev/null +++ b/Swiften/FileTransfer/FileTransferManagerImpl.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <vector> +#include <string> + +#include <boost/filesystem.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/optional.hpp> + +#include <Swiften/FileTransfer/FileTransferManager.h> +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/JID/JID.h> +#include <Swiften/FileTransfer/OutgoingFileTransfer.h> +#include <Swiften/FileTransfer/IncomingFileTransfer.h> +#include <Swiften/Elements/S5BProxyRequest.h> + +namespace Swift { + +class Client; +class ConnectionFactory; +class ConnectionServerFactory; +class ConnectivityManager; +class EntityCapsProvider; +class IQRouter; +class IncomingFileTransferManager; +class JingleSessionManager; +class LocalJingleTransportCandidateGeneratorFactory; +class OutgoingFileTransferManager; +class PlatformNATTraversalWorker; +class PresenceOracle; +class ReadBytestream; +class RemoteJingleTransportCandidateSelectorFactory; +class SOCKS5BytestreamRegistry; +class SOCKS5BytestreamServer; +class SOCKS5BytestreamProxy; +class TimerFactory; + +class FileTransferManagerImpl : public FileTransferManager { +public: + FileTransferManagerImpl(const JID& ownFullJID, JingleSessionManager* jingleSessionManager, IQRouter* router, EntityCapsProvider* capsProvider, PresenceOracle* presOracle, ConnectionFactory* connectionFactory, ConnectionServerFactory* connectionServerFactory, TimerFactory* timerFactory, PlatformNATTraversalWorker* natTraversalWorker); + ~FileTransferManagerImpl(); + + void startListeningOnPort(int port); + void addS5BProxy(S5BProxyRequest::ref proxy); + + OutgoingFileTransfer::ref createOutgoingFileTransfer(const JID& to, const boost::filesystem::path& filepath, const std::string& description, boost::shared_ptr<ReadBytestream> bytestream); + OutgoingFileTransfer::ref createOutgoingFileTransfer(const JID& to, const std::string& filename, const std::string& description, const boost::uintmax_t sizeInBytes, const boost::posix_time::ptime& lastModified, boost::shared_ptr<ReadBytestream> bytestream); + + private: + boost::optional<JID> highestPriorityJIDSupportingFileTransfer(const JID& bareJID); + +private: + JID ownJID; + + OutgoingFileTransferManager* outgoingFTManager; + IncomingFileTransferManager* incomingFTManager; + RemoteJingleTransportCandidateSelectorFactory* remoteCandidateSelectorFactory; + LocalJingleTransportCandidateGeneratorFactory* localCandidateGeneratorFactory; + JingleSessionManager* jingleSM; + IQRouter* iqRouter; + EntityCapsProvider* capsProvider; + PresenceOracle* presenceOracle; + + TimerFactory* timerFactory; + ConnectionFactory* connectionFactory; + ConnectionServerFactory* connectionServerFactory; + PlatformNATTraversalWorker* natTraversalWorker; + SOCKS5BytestreamRegistry* bytestreamRegistry; + SOCKS5BytestreamServer* bytestreamServer; + SOCKS5BytestreamProxy* bytestreamProxy; + ConnectivityManager* connectivityManager; +}; + +} diff --git a/Swiften/FileTransfer/FileWriteBytestream.cpp b/Swiften/FileTransfer/FileWriteBytestream.cpp index c38338a..6a22c6a 100644 --- a/Swiften/FileTransfer/FileWriteBytestream.cpp +++ b/Swiften/FileTransfer/FileWriteBytestream.cpp @@ -27,6 +27,14 @@ void FileWriteBytestream::write(const std::vector<unsigned char>& data) { } assert(stream->good()); stream->write(reinterpret_cast<const char*>(&data[0]), data.size()); + onWrite(data); +} + +void FileWriteBytestream::close() { + if (stream) { + stream->close(); + stream = NULL; + } } } diff --git a/Swiften/FileTransfer/FileWriteBytestream.h b/Swiften/FileTransfer/FileWriteBytestream.h index 8cfa718..82c4a65 100644 --- a/Swiften/FileTransfer/FileWriteBytestream.h +++ b/Swiften/FileTransfer/FileWriteBytestream.h @@ -18,6 +18,7 @@ namespace Swift { ~FileWriteBytestream(); virtual void write(const std::vector<unsigned char>&); + void close(); private: boost::filesystem::path file; diff --git a/Swiften/FileTransfer/IBBReceiveSession.cpp b/Swiften/FileTransfer/IBBReceiveSession.cpp index 566dcca..43c26be 100644 --- a/Swiften/FileTransfer/IBBReceiveSession.cpp +++ b/Swiften/FileTransfer/IBBReceiveSession.cpp @@ -14,6 +14,8 @@ #include <Swiften/FileTransfer/BytestreamException.h> #include <Swiften/Queries/SetResponder.h> +#include <cassert> + namespace Swift { class IBBReceiveSession::IBBResponder : public SetResponder<IBB> { @@ -43,14 +45,17 @@ class IBBReceiveSession::IBBResponder : public SetResponder<IBB> { } } else if (ibb->getAction() == IBB::Open) { + SWIFT_LOG(debug) << "IBB open received" << std::endl; sendResponse(from, id, IBB::ref()); } else if (ibb->getAction() == IBB::Close) { + SWIFT_LOG(debug) << "IBB close received" << std::endl; sendResponse(from, id, IBB::ref()); session->finish(FileTransferError(FileTransferError::ClosedError)); } return true; } + SWIFT_LOG(debug) << "wrong from/sessionID: " << from << " == " << session->from << " / " <<ibb->getStreamID() << " == " << session->id << std::endl; return false; } @@ -71,6 +76,8 @@ IBBReceiveSession::IBBReceiveSession( size(size), router(router), active(false) { + assert(!id.empty()); + assert(from.isValid()); responder = new IBBResponder(this, router); } @@ -82,11 +89,13 @@ IBBReceiveSession::~IBBReceiveSession() { } void IBBReceiveSession::start() { + SWIFT_LOG(debug) << "receive session started" << std::endl; active = true; responder->start(); } void IBBReceiveSession::stop() { + SWIFT_LOG(debug) << "receive session stopped" << std::endl; responder->stop(); if (active) { if (router->isAvailable()) { diff --git a/Swiften/FileTransfer/IBBSendSession.cpp b/Swiften/FileTransfer/IBBSendSession.cpp index 5376276..a434cfb 100644 --- a/Swiften/FileTransfer/IBBSendSession.cpp +++ b/Swiften/FileTransfer/IBBSendSession.cpp @@ -35,7 +35,7 @@ void IBBSendSession::stop() { } void IBBSendSession::handleIBBResponse(IBB::ref, ErrorPayload::ref error) { - if (!error) { + if (!error && active) { if (!bytestream->isFinished()) { try { std::vector<unsigned char> data = bytestream->read(blockSize); @@ -43,6 +43,7 @@ void IBBSendSession::handleIBBResponse(IBB::ref, ErrorPayload::ref error) { sequenceNumber++; request->onResponse.connect(boost::bind(&IBBSendSession::handleIBBResponse, this, _1, _2)); request->send(); + onBytesSent(data.size()); } catch (const BytestreamException&) { finish(FileTransferError(FileTransferError::ReadError)); diff --git a/Swiften/FileTransfer/IBBSendSession.h b/Swiften/FileTransfer/IBBSendSession.h index 6c741cf..325f66c 100644 --- a/Swiften/FileTransfer/IBBSendSession.h +++ b/Swiften/FileTransfer/IBBSendSession.h @@ -32,7 +32,7 @@ namespace Swift { } boost::signal<void (boost::optional<FileTransferError>)> onFinished; - + boost::signal<void (int)> onBytesSent; private: void handleIBBResponse(IBB::ref, ErrorPayload::ref); void finish(boost::optional<FileTransferError>); diff --git a/Swiften/FileTransfer/IncomingFileTransfer.h b/Swiften/FileTransfer/IncomingFileTransfer.h index 1ccd76c..a6cf05e 100644 --- a/Swiften/FileTransfer/IncomingFileTransfer.h +++ b/Swiften/FileTransfer/IncomingFileTransfer.h @@ -9,15 +9,18 @@ #include <boost/shared_ptr.hpp> #include <Swiften/Base/boost_bsignals.h> +#include <Swiften/JID/JID.h> +#include <Swiften/FileTransfer/FileTransfer.h> #include <Swiften/FileTransfer/WriteBytestream.h> namespace Swift { - class IncomingFileTransfer { + class IncomingFileTransfer : public FileTransfer { public: typedef boost::shared_ptr<IncomingFileTransfer> ref; virtual ~IncomingFileTransfer(); virtual void accept(WriteBytestream::ref) = 0; + virtual const JID& getSender() const = 0; }; } diff --git a/Swiften/FileTransfer/IncomingFileTransferManager.cpp b/Swiften/FileTransfer/IncomingFileTransferManager.cpp index 79d2391..c01c906 100644 --- a/Swiften/FileTransfer/IncomingFileTransferManager.cpp +++ b/Swiften/FileTransfer/IncomingFileTransferManager.cpp @@ -18,7 +18,9 @@ namespace Swift { -IncomingFileTransferManager::IncomingFileTransferManager(JingleSessionManager* jingleSessionManager, IQRouter* router) : jingleSessionManager(jingleSessionManager), router(router) { +IncomingFileTransferManager::IncomingFileTransferManager(const JID& ourFullJID, JingleSessionManager* jingleSessionManager, IQRouter* router, + RemoteJingleTransportCandidateSelectorFactory* remoteFactory, + LocalJingleTransportCandidateGeneratorFactory* localFactory, SOCKS5BytestreamRegistry* bytestreamRegistry, SOCKS5BytestreamProxy* bytestreamProxy, TimerFactory* timerFactory) : ourJID(ourFullJID), jingleSessionManager(jingleSessionManager), router(router), remoteFactory(remoteFactory), localFactory(localFactory), bytestreamRegistry(bytestreamRegistry), bytestreamProxy(bytestreamProxy), timerFactory(timerFactory) { jingleSessionManager->addIncomingSessionHandler(this); } @@ -29,13 +31,19 @@ IncomingFileTransferManager::~IncomingFileTransferManager() { bool IncomingFileTransferManager::handleIncomingJingleSession(JingleSession::ref session, const std::vector<JingleContentPayload::ref>& contents) { if (JingleContentPayload::ref content = Jingle::getContentWithDescription<JingleFileTransferDescription>(contents)) { if (content->getTransport<JingleIBBTransportPayload>() || content->getTransport<JingleS5BTransportPayload>()) { - RemoteJingleTransportCandidateSelectorFactory* a; - LocalJingleTransportCandidateGeneratorFactory* b; - IncomingJingleFileTransfer::ref transfer = boost::make_shared<IncomingJingleFileTransfer>(session, content, a, b, router); - onIncomingFileTransfer(transfer); + + JingleFileTransferDescription::ref description = content->getDescription<JingleFileTransferDescription>(); + + if (description && description->getOffers().size() == 1) { + IncomingJingleFileTransfer::ref transfer = boost::make_shared<IncomingJingleFileTransfer>(ourJID, session, content, remoteFactory, localFactory, router, bytestreamRegistry, bytestreamProxy, timerFactory); + onIncomingFileTransfer(transfer); + } else { + std::cerr << "Received a file-transfer request with no description or more than one file!" << std::endl; + session->sendTerminate(JinglePayload::Reason::FailedApplication); + } } else { - session->terminate(JinglePayload::Reason::UnsupportedTransports); + session->sendTerminate(JinglePayload::Reason::UnsupportedTransports); } return true; } diff --git a/Swiften/FileTransfer/IncomingFileTransferManager.h b/Swiften/FileTransfer/IncomingFileTransferManager.h index 428a838..d1573f6 100644 --- a/Swiften/FileTransfer/IncomingFileTransferManager.h +++ b/Swiften/FileTransfer/IncomingFileTransferManager.h @@ -17,10 +17,13 @@ namespace Swift { class JingleSessionManager; class RemoteJingleTransportCandidateSelectorFactory; class LocalJingleTransportCandidateGeneratorFactory; + class SOCKS5BytestreamRegistry; + class SOCKS5BytestreamProxy; + class TimerFactory; class IncomingFileTransferManager : public IncomingJingleSessionHandler { public: - IncomingFileTransferManager(JingleSessionManager* jingleSessionManager, IQRouter* router); + IncomingFileTransferManager(const JID& ourFullJID, JingleSessionManager* jingleSessionManager, IQRouter* router, RemoteJingleTransportCandidateSelectorFactory* remoteFactory, LocalJingleTransportCandidateGeneratorFactory* localFactory, SOCKS5BytestreamRegistry* bytestreamRegistry, SOCKS5BytestreamProxy* bytestreamProxy, TimerFactory* timerFactory); ~IncomingFileTransferManager(); boost::signal<void (IncomingFileTransfer::ref)> onIncomingFileTransfer; @@ -29,7 +32,13 @@ namespace Swift { bool handleIncomingJingleSession(JingleSession::ref session, const std::vector<JingleContentPayload::ref>& contents); private: + JID ourJID; JingleSessionManager* jingleSessionManager; IQRouter* router; + RemoteJingleTransportCandidateSelectorFactory* remoteFactory; + LocalJingleTransportCandidateGeneratorFactory* localFactory; + SOCKS5BytestreamRegistry* bytestreamRegistry; + SOCKS5BytestreamProxy* bytestreamProxy; + TimerFactory* timerFactory; }; } diff --git a/Swiften/FileTransfer/IncomingJingleFileTransfer.cpp b/Swiften/FileTransfer/IncomingJingleFileTransfer.cpp index 904b53e..1189830 100644 --- a/Swiften/FileTransfer/IncomingJingleFileTransfer.cpp +++ b/Swiften/FileTransfer/IncomingJingleFileTransfer.cpp @@ -9,29 +9,47 @@ #include <boost/bind.hpp> #include <boost/smart_ptr/make_shared.hpp> -#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelectorFactory.h> -#include <Swiften/FileTransfer/LocalJingleTransportCandidateGeneratorFactory.h> -#include <Swiften/FileTransfer/JingleIncomingIBBTransport.h> +#include <Swiften/Base/Log.h> +#include <Swiften/Base/foreach.h> #include <Swiften/Elements/JingleIBBTransportPayload.h> #include <Swiften/Elements/JingleS5BTransportPayload.h> -#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.h> +#include <Swiften/Elements/JingleFileTransferHash.h> +#include <Swiften/Elements/S5BProxyRequest.h> +#include <Swiften/FileTransfer/IncrementalBytestreamHashCalculator.h> +#include <Swiften/FileTransfer/JingleIncomingIBBTransport.h> #include <Swiften/FileTransfer/LocalJingleTransportCandidateGenerator.h> +#include <Swiften/FileTransfer/LocalJingleTransportCandidateGeneratorFactory.h> +#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.h> +#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelectorFactory.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamProxy.h> +#include <Swiften/Network/TimerFactory.h> +#include <Swiften/Queries/GenericRequest.h> namespace Swift { IncomingJingleFileTransfer::IncomingJingleFileTransfer( + const JID& ourJID, JingleSession::ref session, JingleContentPayload::ref content, RemoteJingleTransportCandidateSelectorFactory* candidateSelectorFactory, LocalJingleTransportCandidateGeneratorFactory* candidateGeneratorFactory, - IQRouter* router) : + IQRouter* router, + SOCKS5BytestreamRegistry* registry, + SOCKS5BytestreamProxy* proxy, + TimerFactory* timerFactory) : + ourJID(ourJID), session(session), router(router), + timerFactory(timerFactory), initialContent(content), - contentID(content->getName(), content->getCreator()), state(Initial), + receivedBytes(0), + s5bRegistry(registry), + s5bProxy(proxy), remoteTransportCandidateSelectFinished(false), - localTransportCandidateSelectFinished(false) { + localTransportCandidateSelectFinished(false), + serverSession(0) { candidateSelector = candidateSelectorFactory->createCandidateSelector(); candidateSelector->onRemoteTransportCandidateSelectFinished.connect(boost::bind(&IncomingJingleFileTransfer::handleRemoteTransportCandidateSelectFinished, this, _1)); @@ -41,13 +59,26 @@ IncomingJingleFileTransfer::IncomingJingleFileTransfer( session->onTransportInfoReceived.connect(boost::bind(&IncomingJingleFileTransfer::handleTransportInfoReceived, this, _1, _2)); session->onTransportReplaceReceived.connect(boost::bind(&IncomingJingleFileTransfer::handleTransportReplaceReceived, this, _1, _2)); - session->onSessionTerminateReceived.connect(boost::bind(&IncomingJingleFileTransfer::handleSessionTerminateReceived, this)); + session->onSessionTerminateReceived.connect(boost::bind(&IncomingJingleFileTransfer::handleSessionTerminateReceived, this, _1)); + session->onSessionInfoReceived.connect(boost::bind(&IncomingJingleFileTransfer::handleSessionInfoReceived, this, _1)); description = initialContent->getDescription<JingleFileTransferDescription>(); assert(description); + assert(description->getOffers().size() == 1); + StreamInitiationFileInfo fileInfo = description->getOffers().front(); + fileSizeInBytes = fileInfo.getSize(); + filename = fileInfo.getName(); + hash = fileInfo.getHash(); + algo = fileInfo.getAlgo(); + + waitOnHashTimer = timerFactory->createTimer(5000); + waitOnHashTimer->onTick.connect(boost::bind(&IncomingJingleFileTransfer::finishOffTransfer, this)); } IncomingJingleFileTransfer::~IncomingJingleFileTransfer() { + stream->onWrite.disconnect(boost::bind(&IncrementalBytestreamHashCalculator::feedData, hashCalculator, _1)); + delete hashCalculator; + session->onSessionTerminateReceived.disconnect(boost::bind(&IncomingJingleFileTransfer::handleSessionTerminateReceived, this)); session->onTransportReplaceReceived.disconnect(boost::bind(&IncomingJingleFileTransfer::handleTransportReplaceReceived, this, _1, _2)); session->onTransportInfoReceived.disconnect(boost::bind(&IncomingJingleFileTransfer::handleTransportInfoReceived, this, _1, _2)); @@ -60,44 +91,87 @@ IncomingJingleFileTransfer::~IncomingJingleFileTransfer() { } void IncomingJingleFileTransfer::accept(WriteBytestream::ref stream) { - assert(!stream); + assert(!this->stream); this->stream = stream; + hashCalculator = new IncrementalBytestreamHashCalculator( algo == "md5" || hash.empty() , algo == "sha-1" || hash.empty() ); + stream->onWrite.connect(boost::bind(&IncrementalBytestreamHashCalculator::feedData, hashCalculator, _1)); + stream->onWrite.connect(boost::bind(&IncomingJingleFileTransfer::handleWriteStreamDataReceived, this, _1)); + onStateChange(FileTransfer::State(FileTransfer::State::Negotiating)); if (JingleIBBTransportPayload::ref ibbTransport = initialContent->getTransport<JingleIBBTransportPayload>()) { + SWIFT_LOG(debug) << "Got IBB transport payload!" << std::endl; setActiveTransport(createIBBTransport(ibbTransport)); - session->accept(); + session->sendAccept(getContentID(), initialContent->getDescriptions()[0], ibbTransport); } else if (JingleS5BTransportPayload::ref s5bTransport = initialContent->getTransport<JingleS5BTransportPayload>()) { + SWIFT_LOG(debug) << "Got S5B transport payload!" << std::endl; state = CreatingInitialTransports; + s5bSessionID = s5bTransport->getSessionID().empty() ? idGenerator.generateID() : s5bTransport->getSessionID(); + s5bDestination = SOCKS5BytestreamRegistry::getHostname(s5bSessionID, ourJID, session->getInitiator()); + s5bRegistry->addWriteBytestream(s5bDestination, stream); + fillCandidateMap(theirCandidates, s5bTransport); candidateSelector->addRemoteTransportCandidates(s5bTransport); - candidateGenerator->generateLocalTransportCandidates(); + candidateSelector->setRequesterTargtet(session->getInitiator(), ourJID); + s5bTransport->setSessionID(s5bSessionID); + candidateGenerator->generateLocalTransportCandidates(s5bTransport); } else { assert(false); } } +const JID& IncomingJingleFileTransfer::getSender() const { + return session->getInitiator(); +} + +void IncomingJingleFileTransfer::cancel() { + session->sendTerminate(JinglePayload::Reason::Cancel); + + if (activeTransport) activeTransport->stop(); + if (serverSession) serverSession->stop(); + if (clientSession) clientSession->stop(); + onStateChange(FileTransfer::State(FileTransfer::State::Canceled)); +} void IncomingJingleFileTransfer::handleLocalTransportCandidatesGenerated(JingleTransportPayload::ref candidates) { if (state == CreatingInitialTransports) { - if (!candidates) { - localTransportCandidateSelectFinished = true; + if (JingleS5BTransportPayload::ref s5bCandidates = boost::dynamic_pointer_cast<JingleS5BTransportPayload>(candidates)) { + //localTransportCandidateSelectFinished = true; + //JingleS5BTransportPayload::ref emptyCandidates = boost::make_shared<JingleS5BTransportPayload>(); + //emptyCandidates->setSessionID(s5bCandidates->getSessionID()); + fillCandidateMap(ourCandidates, s5bCandidates); + session->sendAccept(getContentID(), initialContent->getDescriptions()[0], s5bCandidates); + + state = NegotiatingTransport; + candidateSelector->selectCandidate(); } - session->accept(candidates); - state = NegotiatingTransport; - candidateSelector->selectCandidate(); + } + else { + SWIFT_LOG(debug) << "Unhandled state!" << std::endl; } } void IncomingJingleFileTransfer::handleRemoteTransportCandidateSelectFinished(JingleTransportPayload::ref transport) { - remoteTransportCandidateSelectFinished = true; - selectedRemoteTransportCandidate = transport; - session->sendTransportInfo(contentID, transport); - checkCandidateSelected(); + SWIFT_LOG(debug) << std::endl; + if (state == Terminated) { + return; + } + if (JingleS5BTransportPayload::ref s5bPayload = boost::dynamic_pointer_cast<JingleS5BTransportPayload>(transport)) { + //remoteTransportCandidateSelectFinished = true; + //selectedRemoteTransportCandidate = transport; + ourCandidate = s5bPayload; + //checkCandidateSelected(); + decideOnUsedTransport(); + session->sendTransportInfo(getContentID(), s5bPayload); + } + else { + SWIFT_LOG(debug) << "Expected something different here." << std::endl; + } } void IncomingJingleFileTransfer::checkCandidateSelected() { + assert(false); if (localTransportCandidateSelectFinished && remoteTransportCandidateSelectFinished) { if (candidateGenerator->isActualCandidate(selectedLocalTransportCandidate) && candidateSelector->isActualCandidate(selectedRemoteTransportCandidate)) { if (candidateGenerator->getPriority(selectedLocalTransportCandidate) > candidateSelector->getPriority(selectedRemoteTransportCandidate)) { @@ -121,37 +195,293 @@ void IncomingJingleFileTransfer::checkCandidateSelected() { void IncomingJingleFileTransfer::setActiveTransport(JingleTransport::ref transport) { state = Transferring; + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); activeTransport = transport; activeTransport->onDataReceived.connect(boost::bind(&IncomingJingleFileTransfer::handleTransportDataReceived, this, _1)); + activeTransport->onFinished.connect(boost::bind(&IncomingJingleFileTransfer::handleTransferFinished, this, _1)); activeTransport->start(); } -void IncomingJingleFileTransfer::handleSessionTerminateReceived() { - // TODO +bool IncomingJingleFileTransfer::verifyReceviedData() { + if (algo.empty() || hash.empty()) { + SWIFT_LOG(debug) << "no verification possible, skipping" << std::endl; + return true; + } else { + if (algo == "sha-1") { + SWIFT_LOG(debug) << "verify data via SHA-1 hash: " << (hash == hashCalculator->getSHA1String()) << std::endl; + return hash == hashCalculator->getSHA1String(); + } + else if (algo == "md5") { + SWIFT_LOG(debug) << "verify data via MD5 hash: " << (hash == hashCalculator->getMD5String()) << std::endl; + return hash == hashCalculator->getMD5String(); + } + else { + SWIFT_LOG(debug) << "no verification possible, skipping" << std::endl; + return true; + } + } +} + +void IncomingJingleFileTransfer::finishOffTransfer() { + if (verifyReceviedData()) { + onStateChange(FileTransfer::State(FileTransfer::State::Finished)); + session->sendTerminate(JinglePayload::Reason::Success); + } else { + onStateChange(FileTransfer::State(FileTransfer::State::Failed, "Verification failed.")); + session->sendTerminate(JinglePayload::Reason::MediaError); + } + state = Terminated; + waitOnHashTimer->stop(); +} + +void IncomingJingleFileTransfer::handleSessionInfoReceived(JinglePayload::ref jinglePayload) { + if (state == Terminated) { + return; + } + JingleFileTransferHash::ref transferHash = jinglePayload->getPayload<JingleFileTransferHash>(); + if (transferHash) { + SWIFT_LOG(debug) << "Recevied hash information." << std::endl; + if (transferHash->getHashes().find("sha-1") != transferHash->getHashes().end()) { + algo = "sha-1"; + hash = transferHash->getHashes().at("sha-1"); + } + else if (transferHash->getHashes().find("md5") != transferHash->getHashes().end()) { + algo = "md5"; + hash = transferHash->getHashes().at("md5"); + } + checkIfAllDataReceived(); + } +} + +void IncomingJingleFileTransfer::handleSessionTerminateReceived(boost::optional<JinglePayload::Reason> reason) { + SWIFT_LOG(debug) << "session terminate received" << std::endl; + if (activeTransport) activeTransport->stop(); + if (reason && reason.get().type == JinglePayload::Reason::Cancel) { + onStateChange(FileTransfer::State(FileTransfer::State::Canceled, "Other user canceled the transfer.")); + } + else if (reason && reason.get().type == JinglePayload::Reason::Success) { + /*if (verifyReceviedData()) { + onStateChange(FileTransfer::State(FileTransfer::State::Finished)); + } else { + onStateChange(FileTransfer::State(FileTransfer::State::Failed, "Verification failed.")); + }*/ + } state = Terminated; } +void IncomingJingleFileTransfer::checkIfAllDataReceived() { + if (receivedBytes == fileSizeInBytes) { + SWIFT_LOG(debug) << "All data received." << std::endl; + if (hash.empty()) { + SWIFT_LOG(debug) << "No hash information yet. Waiting 5 seconds on hash info." << std::endl; + waitOnHashTimer->start(); + } else { + SWIFT_LOG(debug) << "We already have hash info using " << algo << " algorithm. Finishing off transfer." << std::endl; + finishOffTransfer(); + } + } + else if (receivedBytes > fileSizeInBytes) { + SWIFT_LOG(debug) << "We got more than we could handle!" << std::endl; + } +} + void IncomingJingleFileTransfer::handleTransportDataReceived(const std::vector<unsigned char>& data) { + SWIFT_LOG(debug) << data.size() << " bytes received" << std::endl; + onProcessedBytes(data.size()); stream->write(data); + receivedBytes += data.size(); + checkIfAllDataReceived(); +} + +void IncomingJingleFileTransfer::handleWriteStreamDataReceived(const std::vector<unsigned char>& data) { + receivedBytes += data.size(); + checkIfAllDataReceived(); +} + +void IncomingJingleFileTransfer::useOurCandidateChoiceForTransfer(JingleS5BTransportPayload::Candidate candidate) { + SWIFT_LOG(debug) << std::endl; + if (candidate.type == JingleS5BTransportPayload::Candidate::ProxyType) { + // get proxy client session from remoteCandidateSelector + clientSession = candidateSelector->getS5BSession(); + + // wait on <activated/> transport-info + } else { + // ask s5b client + clientSession = candidateSelector->getS5BSession(); + if (clientSession) { + state = Transferring; + SWIFT_LOG(debug) << clientSession->getAddressPort().toString() << std::endl; + clientSession->onBytesReceived.connect(boost::bind(boost::ref(onProcessedBytes), _1)); + clientSession->onFinished.connect(boost::bind(&IncomingJingleFileTransfer::handleTransferFinished, this, _1)); + clientSession->startReceiving(stream); + } else { + SWIFT_LOG(debug) << "No S5B client session found!!!" << std::endl; + } + } } +void IncomingJingleFileTransfer::useTheirCandidateChoiceForTransfer(JingleS5BTransportPayload::Candidate candidate) { + SWIFT_LOG(debug) << std::endl; + + if (candidate.type == JingleS5BTransportPayload::Candidate::ProxyType) { + // get proxy client session from s5bRegistry + clientSession = s5bProxy->createSOCKS5BytestreamClientSession(candidate.hostPort, SOCKS5BytestreamRegistry::getHostname(s5bSessionID, ourJID, session->getInitiator())); + clientSession->onSessionReady.connect(boost::bind(&IncomingJingleFileTransfer::proxySessionReady, this, candidate.jid, _1)); + clientSession->start(); + + // on reply send activate + } else { + // ask s5b server + serverSession = s5bRegistry->getConnectedSession(s5bDestination); + if (serverSession) { + state = Transferring; + serverSession->onBytesReceived.connect(boost::bind(boost::ref(onProcessedBytes), _1)); + serverSession->onFinished.connect(boost::bind(&IncomingJingleFileTransfer::handleTransferFinished, this, _1)); + serverSession->startTransfer(); + } else { + SWIFT_LOG(debug) << "No S5B server session found!!!" << std::endl; + } + } +} + +void IncomingJingleFileTransfer::fillCandidateMap(CandidateMap& map, JingleS5BTransportPayload::ref s5bPayload) { + map.clear(); + foreach (JingleS5BTransportPayload::Candidate candidate, s5bPayload->getCandidates()) { + map[candidate.cid] = candidate; + } +} + + +void IncomingJingleFileTransfer::decideOnUsedTransport() { + if (ourCandidate && theirCandidate) { + if (ourCandidate->hasCandidateError() && theirCandidate->hasCandidateError()) { + state = WaitingForFallbackOrTerminate; + return; + } + std::string our_cid = ourCandidate->getCandidateUsed(); + std::string their_cid = theirCandidate->getCandidateUsed(); + if (ourCandidate->hasCandidateError() && !their_cid.empty()) { + useTheirCandidateChoiceForTransfer(ourCandidates[their_cid]); + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); + } + else if (theirCandidate->hasCandidateError() && !our_cid.empty()) { + useOurCandidateChoiceForTransfer(theirCandidates[our_cid]); + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); + } + else if (!our_cid.empty() && !their_cid.empty()) { + // compare priorites, if same they win + if (ourCandidates.find(their_cid) == ourCandidates.end() || theirCandidates.find(our_cid) == theirCandidates.end()) { + SWIFT_LOG(debug) << "Didn't recognize candidate IDs!" << std::endl; + session->sendTerminate(JinglePayload::Reason::FailedTransport); + onStateChange(FileTransfer::State(FileTransfer::State::Canceled, "Failed to negotiate candidate.")); + onFinished(FileTransferError(FileTransferError::PeerError)); + return; + } + + JingleS5BTransportPayload::Candidate our_candidate = theirCandidates[our_cid]; + JingleS5BTransportPayload::Candidate their_candidate = ourCandidates[their_cid]; + if (our_candidate.priority > their_candidate.priority) { + useOurCandidateChoiceForTransfer(our_candidate); + } + else if (our_candidate.priority < their_candidate.priority) { + useTheirCandidateChoiceForTransfer(their_candidate); + } + else { + useTheirCandidateChoiceForTransfer(their_candidate); + } + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); + } + else { + assert(false); + } + } else { + SWIFT_LOG(debug) << "Can't make a transport decision yet." << std::endl; + } +} + +void IncomingJingleFileTransfer::proxySessionReady(const JID& proxy, bool error) { + if (error) { + // indicate proxy error + } else { + // activate proxy + activateProxySession(proxy); + } +} + +void IncomingJingleFileTransfer::activateProxySession(const JID &proxy) { + S5BProxyRequest::ref proxyRequest = boost::make_shared<S5BProxyRequest>(); + proxyRequest->setSID(s5bSessionID); + proxyRequest->setActivate(session->getInitiator()); + + boost::shared_ptr<GenericRequest<S5BProxyRequest> > request = boost::make_shared<GenericRequest<S5BProxyRequest> >(IQ::Set, proxy, proxyRequest, router); + request->onResponse.connect(boost::bind(&IncomingJingleFileTransfer::handleActivateProxySessionResult, this, _1, _2)); + request->send(); +} + +void IncomingJingleFileTransfer::handleActivateProxySessionResult(boost::shared_ptr<S5BProxyRequest> /*request*/, ErrorPayload::ref error) { + SWIFT_LOG(debug) << std::endl; + if (error) { + SWIFT_LOG(debug) << "ERROR" << std::endl; + } else { + // send activated to other jingle party + JingleS5BTransportPayload::ref proxyActivate = boost::make_shared<JingleS5BTransportPayload>(); + proxyActivate->setActivated(theirCandidate->getCandidateUsed()); + session->sendTransportInfo(getContentID(), proxyActivate); + + // start transferring + clientSession->onBytesReceived.connect(boost::bind(boost::ref(onProcessedBytes), _1)); + clientSession->onFinished.connect(boost::bind(&IncomingJingleFileTransfer::handleTransferFinished, this, _1)); + clientSession->startReceiving(stream); + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); + } +} void IncomingJingleFileTransfer::handleTransportInfoReceived(const JingleContentID&, JingleTransportPayload::ref transport) { - localTransportCandidateSelectFinished = true; + SWIFT_LOG(debug) << "transport info received" << std::endl; + if (state == Terminated) { + return; + } + if (JingleS5BTransportPayload::ref s5bPayload = boost::dynamic_pointer_cast<JingleS5BTransportPayload>(transport)) { + if (!s5bPayload->getActivated().empty()) { + if (ourCandidate->getCandidateUsed() == s5bPayload->getActivated()) { + clientSession->onBytesReceived.connect(boost::bind(boost::ref(onProcessedBytes), _1)); + clientSession->onFinished.connect(boost::bind(&IncomingJingleFileTransfer::handleTransferFinished, this, _1)); + clientSession->startReceiving(stream); + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); + } else { + SWIFT_LOG(debug) << "ourCandidateChoice doesn't match activated proxy candidate!" << std::endl; + JingleS5BTransportPayload::ref proxyError = boost::make_shared<JingleS5BTransportPayload>(); + proxyError->setProxyError(true); + proxyError->setSessionID(s5bSessionID); + session->sendTransportInfo(getContentID(), proxyError); + } + } else { + theirCandidate = s5bPayload; + decideOnUsedTransport(); + } + } + else { + SWIFT_LOG(debug) << "Expected something different here." << std::endl; + } + /*localTransportCandidateSelectFinished = true; selectedLocalTransportCandidate = transport; if (candidateGenerator->isActualCandidate(transport)) { candidateSelector->setMinimumPriority(candidateGenerator->getPriority(transport)); - } - checkCandidateSelected(); + }*/ + //checkCandidateSelected(); } void IncomingJingleFileTransfer::handleTransportReplaceReceived(const JingleContentID& content, JingleTransportPayload::ref transport) { + if (state == Terminated) { + return; + } if (JingleIBBTransportPayload::ref ibbTransport = boost::dynamic_pointer_cast<JingleIBBTransportPayload>(transport)) { + SWIFT_LOG(debug) << "transport replaced with IBB" << std::endl; setActiveTransport(createIBBTransport(ibbTransport)); - session->acceptTransport(content, transport); - } - else { - session->rejectTransport(content, transport); + session->sendTransportAccept(content, ibbTransport); + } else { + SWIFT_LOG(debug) << "transport replaced failed" << std::endl; + session->sendTransportReject(content, transport); } } @@ -163,7 +493,25 @@ void IncomingJingleFileTransfer::stopActiveTransport() { } JingleIncomingIBBTransport::ref IncomingJingleFileTransfer::createIBBTransport(JingleIBBTransportPayload::ref ibbTransport) { - return boost::make_shared<JingleIncomingIBBTransport>(session->getInitiator(), ibbTransport->getSessionID(), description->getOffer()->size, router); + // TODO: getOffer() -> getOffers correction + return boost::make_shared<JingleIncomingIBBTransport>(session->getInitiator(), ibbTransport->getSessionID(), description->getOffers()[0].getSize(), router); +} + +JingleContentID IncomingJingleFileTransfer::getContentID() const { + return JingleContentID(initialContent->getName(), initialContent->getCreator()); +} + +void IncomingJingleFileTransfer::handleTransferFinished(boost::optional<FileTransferError> error) { + if (state == Terminated) { + return; + } + + if (error) { + session->sendTerminate(JinglePayload::Reason::ConnectivityError); + onStateChange(FileTransfer::State(FileTransfer::State::Failed)); + onFinished(error); + } + // } } diff --git a/Swiften/FileTransfer/IncomingJingleFileTransfer.h b/Swiften/FileTransfer/IncomingJingleFileTransfer.h index 164d868..4ae0d1d 100644 --- a/Swiften/FileTransfer/IncomingJingleFileTransfer.h +++ b/Swiften/FileTransfer/IncomingJingleFileTransfer.h @@ -8,14 +8,21 @@ #include <boost/shared_ptr.hpp> +#include <Swiften/Base/IDGenerator.h> +#include <Swiften/Network/Timer.h> #include <Swiften/Jingle/JingleSession.h> #include <Swiften/Jingle/JingleContentID.h> #include <Swiften/FileTransfer/IncomingFileTransfer.h> #include <Swiften/FileTransfer/JingleTransport.h> #include <Swiften/FileTransfer/JingleIncomingIBBTransport.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamClientSession.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamServerSession.h> #include <Swiften/Elements/JingleContentPayload.h> #include <Swiften/Elements/JingleFileTransferDescription.h> #include <Swiften/Elements/JingleIBBTransportPayload.h> +#include <Swiften/Elements/JingleS5BTransportPayload.h> +#include <Swiften/Elements/S5BProxyRequest.h> +#include <Swiften/Elements/ErrorPayload.h> namespace Swift { class IQRouter; @@ -23,6 +30,9 @@ namespace Swift { class LocalJingleTransportCandidateGeneratorFactory; class RemoteJingleTransportCandidateSelector; class LocalJingleTransportCandidateGenerator; + class SOCKS5BytestreamRegistry; + class SOCKS5BytestreamProxy; + class IncrementalBytestreamHashCalculator; class IncomingJingleFileTransfer : public IncomingFileTransfer { public: @@ -37,42 +47,86 @@ namespace Swift { }; IncomingJingleFileTransfer( + const JID& ourJID, JingleSession::ref, JingleContentPayload::ref content, RemoteJingleTransportCandidateSelectorFactory*, LocalJingleTransportCandidateGeneratorFactory*, - IQRouter* router); + IQRouter* router, + SOCKS5BytestreamRegistry* bytestreamRegistry, + SOCKS5BytestreamProxy* bytestreamProxy, + TimerFactory*); ~IncomingJingleFileTransfer(); virtual void accept(WriteBytestream::ref); + virtual const JID& getSender() const; + void cancel(); private: - void handleSessionTerminateReceived(); + void handleSessionTerminateReceived(boost::optional<JinglePayload::Reason>); + void handleSessionInfoReceived(JinglePayload::ref); void handleTransportReplaceReceived(const JingleContentID&, JingleTransportPayload::ref); void handleTransportInfoReceived(const JingleContentID&, JingleTransportPayload::ref); void handleLocalTransportCandidatesGenerated(JingleTransportPayload::ref candidates); void handleRemoteTransportCandidateSelectFinished(JingleTransportPayload::ref candidate); void setActiveTransport(JingleTransport::ref transport); void handleTransportDataReceived(const std::vector<unsigned char>& data); + void handleWriteStreamDataReceived(const std::vector<unsigned char>& data); void stopActiveTransport(); void checkCandidateSelected(); JingleIncomingIBBTransport::ref createIBBTransport(JingleIBBTransportPayload::ref ibbTransport); + JingleContentID getContentID() const; + void checkIfAllDataReceived(); + bool verifyReceviedData(); + void finishOffTransfer(); + void handleTransferFinished(boost::optional<FileTransferError>); private: + typedef std::map<std::string, JingleS5BTransportPayload::Candidate> CandidateMap; + + private: + void activateProxySession(const JID &proxy); + void handleActivateProxySessionResult(boost::shared_ptr<S5BProxyRequest> request, ErrorPayload::ref error); + void proxySessionReady(const JID& proxy, bool error); + + private: + void useOurCandidateChoiceForTransfer(JingleS5BTransportPayload::Candidate candidate); + void useTheirCandidateChoiceForTransfer(JingleS5BTransportPayload::Candidate candidate); + void decideOnUsedTransport(); + void fillCandidateMap(CandidateMap& map, JingleS5BTransportPayload::ref s5bPayload); + + private: + JID ourJID; JingleSession::ref session; IQRouter* router; + TimerFactory* timerFactory; JingleContentPayload::ref initialContent; - JingleContentID contentID; State state; JingleFileTransferDescription::ref description; WriteBytestream::ref stream; + uintmax_t receivedBytes; + IncrementalBytestreamHashCalculator* hashCalculator; + Timer::ref waitOnHashTimer; + IDGenerator idGenerator; + RemoteJingleTransportCandidateSelector* candidateSelector; LocalJingleTransportCandidateGenerator* candidateGenerator; + SOCKS5BytestreamRegistry* s5bRegistry; + SOCKS5BytestreamProxy* s5bProxy; bool remoteTransportCandidateSelectFinished; JingleTransportPayload::ref selectedRemoteTransportCandidate; bool localTransportCandidateSelectFinished; JingleTransportPayload::ref selectedLocalTransportCandidate; + JingleS5BTransportPayload::ref ourCandidate; + JingleS5BTransportPayload::ref theirCandidate; + CandidateMap ourCandidates; + CandidateMap theirCandidates; + SOCKS5BytestreamClientSession::ref clientSession; + std::string s5bDestination; + std::string s5bSessionID; + SOCKS5BytestreamServerSession* serverSession; + JingleTransport::ref activeTransport; }; } diff --git a/Swiften/FileTransfer/IncrementalBytestreamHashCalculator.cpp b/Swiften/FileTransfer/IncrementalBytestreamHashCalculator.cpp new file mode 100644 index 0000000..6b53a1b --- /dev/null +++ b/Swiften/FileTransfer/IncrementalBytestreamHashCalculator.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swiften/FileTransfer/IncrementalBytestreamHashCalculator.h> + +#include <Swiften/StringCodecs/Hexify.h> +#include <Swiften/StringCodecs/MD5.h> +#include <Swiften/StringCodecs/SHA1.h> + +namespace Swift { + +IncrementalBytestreamHashCalculator::IncrementalBytestreamHashCalculator(bool doMD5, bool doSHA1) { + md5Hasher = doMD5 ? new MD5() : NULL; + sha1Hasher = doSHA1 ? new SHA1() : NULL; +} + +IncrementalBytestreamHashCalculator::~IncrementalBytestreamHashCalculator() { + delete md5Hasher; + delete sha1Hasher; +} + +void IncrementalBytestreamHashCalculator::feedData(const ByteArray& data) { + if (md5Hasher) { + md5Hasher->update(data); + } + if (sha1Hasher) { + sha1Hasher->update(data); + } +} +/* +void IncrementalBytestreamHashCalculator::feedData(const SafeByteArray& data) { + if (md5Hasher) { + md5Hasher->update(createByteArray(data.data(), data.size())); + } + if (sha1Hasher) { + sha1Hasher->update(createByteArray(data.data(), data.size())); + } +}*/ + +std::string IncrementalBytestreamHashCalculator::getSHA1String() { + if (sha1Hasher) { + ByteArray result = sha1Hasher->getHash(); + return Hexify::hexify(result); + } else { + return std::string(); + } +} + +std::string IncrementalBytestreamHashCalculator::getMD5String() { + if (md5Hasher) { + ByteArray result = md5Hasher->getHash(); + return Hexify::hexify(result); + } else { + return std::string(); + } +} + +} diff --git a/Swiften/FileTransfer/IncrementalBytestreamHashCalculator.h b/Swiften/FileTransfer/IncrementalBytestreamHashCalculator.h new file mode 100644 index 0000000..64f4b5f --- /dev/null +++ b/Swiften/FileTransfer/IncrementalBytestreamHashCalculator.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Base/ByteArray.h> +#include <Swiften/Base/SafeByteArray.h> + +namespace Swift { + +class MD5; +class SHA1; + +class IncrementalBytestreamHashCalculator { +public: + IncrementalBytestreamHashCalculator(bool doMD5, bool doSHA1); + ~IncrementalBytestreamHashCalculator(); + + void feedData(const ByteArray& data); + //void feedData(const SafeByteArray& data); + + std::string getSHA1String(); + std::string getMD5String(); + +private: + MD5* md5Hasher; + SHA1* sha1Hasher; +}; + +} diff --git a/Swiften/FileTransfer/JingleIncomingIBBTransport.cpp b/Swiften/FileTransfer/JingleIncomingIBBTransport.cpp index 0ca899f..9b5c354 100644 --- a/Swiften/FileTransfer/JingleIncomingIBBTransport.cpp +++ b/Swiften/FileTransfer/JingleIncomingIBBTransport.cpp @@ -8,8 +8,9 @@ namespace Swift { -JingleIncomingIBBTransport::JingleIncomingIBBTransport(const JID& from, const std::string& id, size_t size, IQRouter* router) : ibbSession(from, id, size, router) { +JingleIncomingIBBTransport::JingleIncomingIBBTransport(const JID& from, const std::string& id, size_t size, IQRouter* router) : ibbSession(id, from, size, router) { ibbSession.onDataReceived.connect(boost::ref(onDataReceived)); + ibbSession.onFinished.connect(boost::ref(onFinished)); } void JingleIncomingIBBTransport::start() { diff --git a/Swiften/FileTransfer/JingleTransport.h b/Swiften/FileTransfer/JingleTransport.h index 1d163d0..fa296e8 100644 --- a/Swiften/FileTransfer/JingleTransport.h +++ b/Swiften/FileTransfer/JingleTransport.h @@ -9,6 +9,7 @@ #include <boost/shared_ptr.hpp> #include <Swiften/Base/boost_bsignals.h> +#include <Swiften/FileTransfer/FileTransfer.h> namespace Swift { class JingleTransport { @@ -21,5 +22,6 @@ namespace Swift { virtual void stop() = 0; boost::signal<void (const std::vector<unsigned char>&)> onDataReceived; + boost::signal<void (boost::optional<FileTransferError>)> onFinished; }; } diff --git a/Swiften/FileTransfer/LocalJingleTransportCandidateGenerator.h b/Swiften/FileTransfer/LocalJingleTransportCandidateGenerator.h index c111005..041afe3 100644 --- a/Swiften/FileTransfer/LocalJingleTransportCandidateGenerator.h +++ b/Swiften/FileTransfer/LocalJingleTransportCandidateGenerator.h @@ -15,8 +15,10 @@ namespace Swift { class LocalJingleTransportCandidateGenerator { public: virtual ~LocalJingleTransportCandidateGenerator(); - - virtual void generateLocalTransportCandidates() = 0; + /** + * Should call onLocalTransportCandidatesGenerated if it has finished discovering local candidates. + */ + virtual void generateLocalTransportCandidates(JingleTransportPayload::ref) = 0; virtual bool isActualCandidate(JingleTransportPayload::ref) = 0; virtual int getPriority(JingleTransportPayload::ref) = 0; diff --git a/Swiften/FileTransfer/OutgoingFileTransfer.h b/Swiften/FileTransfer/OutgoingFileTransfer.h index a8c1e81..1ec1ae3 100644 --- a/Swiften/FileTransfer/OutgoingFileTransfer.h +++ b/Swiften/FileTransfer/OutgoingFileTransfer.h @@ -6,8 +6,14 @@ #pragma once +#include <boost/shared_ptr.hpp> + +#include <Swiften/FileTransfer/FileTransfer.h> + namespace Swift { - class OutgoingFileTransfer { + class OutgoingFileTransfer : public FileTransfer { + public: + typedef boost::shared_ptr<OutgoingFileTransfer> ref; public: virtual ~OutgoingFileTransfer(); diff --git a/Swiften/FileTransfer/OutgoingFileTransferManager.cpp b/Swiften/FileTransfer/OutgoingFileTransferManager.cpp new file mode 100644 index 0000000..321a57a --- /dev/null +++ b/Swiften/FileTransfer/OutgoingFileTransferManager.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "OutgoingFileTransferManager.h" + +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/JID/JID.h> +#include <Swiften/Jingle/JingleSessionManager.h> +#include <Swiften/Jingle/JingleSessionImpl.h> +#include <Swiften/Jingle/JingleContentID.h> +#include <Swiften/FileTransfer/OutgoingJingleFileTransfer.h> +#include <Swiften/Base/IDGenerator.h> + +namespace Swift { + +OutgoingFileTransferManager::OutgoingFileTransferManager(const JID& ownFullJID, JingleSessionManager* jingleSessionManager, IQRouter* router, EntityCapsProvider* capsProvider, RemoteJingleTransportCandidateSelectorFactory* remoteFactory, LocalJingleTransportCandidateGeneratorFactory* localFactory, SOCKS5BytestreamRegistry* bytestreamRegistry, SOCKS5BytestreamProxy* bytestreamProxy) : ownJID(ownFullJID), jsManager(jingleSessionManager), iqRouter(router), capsProvider(capsProvider), remoteFactory(remoteFactory), localFactory(localFactory), bytestreamRegistry(bytestreamRegistry), bytestreamProxy(bytestreamProxy) { + idGenerator = new IDGenerator(); +} + +OutgoingFileTransferManager::~OutgoingFileTransferManager() { + delete idGenerator; +} + +boost::shared_ptr<OutgoingFileTransfer> OutgoingFileTransferManager::createOutgoingFileTransfer(const JID& receipient, boost::shared_ptr<ReadBytestream> readBytestream, const StreamInitiationFileInfo& fileInfo) { + // check if receipient support Jingle FT + + + JingleSessionImpl::ref jingleSession = boost::make_shared<JingleSessionImpl>(ownJID, receipient, idGenerator->generateID(), iqRouter); + + //jsManager->getSession(receipient, idGenerator->generateID()); + assert(jingleSession); + jsManager->registerOutgoingSession(ownJID, jingleSession); + boost::shared_ptr<OutgoingJingleFileTransfer> jingleFT = boost::shared_ptr<OutgoingJingleFileTransfer>(new OutgoingJingleFileTransfer(jingleSession, remoteFactory, localFactory, iqRouter, idGenerator, receipient, readBytestream, fileInfo, bytestreamRegistry, bytestreamProxy)); + + // otherwise try SI + + // else fail + + return jingleFT; +} + +} diff --git a/Swiften/FileTransfer/OutgoingFileTransferManager.h b/Swiften/FileTransfer/OutgoingFileTransferManager.h new file mode 100644 index 0000000..d57c14d --- /dev/null +++ b/Swiften/FileTransfer/OutgoingFileTransferManager.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> + +#include <Swiften/JID/JID.h> + +namespace Swift { + +class JingleSessionManager; +class IQRouter; +class EntityCapsProvider; +class RemoteJingleTransportCandidateSelectorFactory; +class LocalJingleTransportCandidateGeneratorFactory; +class OutgoingFileTransfer; +class JID; +class IDGenerator; +class ReadBytestream; +class StreamInitiationFileInfo; +class SOCKS5BytestreamRegistry; +class SOCKS5BytestreamProxy; + +class OutgoingFileTransferManager { +public: + OutgoingFileTransferManager(const JID& ownFullJID, JingleSessionManager* jingleSessionManager, IQRouter* router, EntityCapsProvider* capsProvider, RemoteJingleTransportCandidateSelectorFactory* remoteFactory, LocalJingleTransportCandidateGeneratorFactory* localFactory, SOCKS5BytestreamRegistry* bytestreamRegistry, SOCKS5BytestreamProxy* bytestreamProxy); + ~OutgoingFileTransferManager(); + + boost::shared_ptr<OutgoingFileTransfer> createOutgoingFileTransfer(const JID&, boost::shared_ptr<ReadBytestream>, const StreamInitiationFileInfo&); + +private: + JID ownJID; + JingleSessionManager* jsManager; + IQRouter* iqRouter; + EntityCapsProvider* capsProvider; + RemoteJingleTransportCandidateSelectorFactory* remoteFactory; + LocalJingleTransportCandidateGeneratorFactory* localFactory; + IDGenerator *idGenerator; + SOCKS5BytestreamRegistry* bytestreamRegistry; + SOCKS5BytestreamProxy* bytestreamProxy; +}; + +} diff --git a/Swiften/FileTransfer/OutgoingJingleFileTransfer.cpp b/Swiften/FileTransfer/OutgoingJingleFileTransfer.cpp new file mode 100644 index 0000000..9b71165 --- /dev/null +++ b/Swiften/FileTransfer/OutgoingJingleFileTransfer.cpp @@ -0,0 +1,401 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "OutgoingJingleFileTransfer.h" + +#include <boost/bind.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/IDGenerator.h> +#include <Swiften/Jingle/JingleContentID.h> +#include <Swiften/Elements/JingleFileTransferDescription.h> +#include <Swiften/Elements/JingleFileTransferHash.h> +#include <Swiften/Elements/JingleTransportPayload.h> +#include <Swiften/Elements/JingleIBBTransportPayload.h> +#include <Swiften/Elements/JingleS5BTransportPayload.h> +#include <Swiften/Queries/GenericRequest.h> +#include <Swiften/FileTransfer/IBBSendSession.h> +#include <Swiften/FileTransfer/IncrementalBytestreamHashCalculator.h> +#include <Swiften/FileTransfer/LocalJingleTransportCandidateGenerator.h> +#include <Swiften/FileTransfer/LocalJingleTransportCandidateGeneratorFactory.h> +#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.h> +#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelectorFactory.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamProxy.h> +#include <Swiften/StringCodecs/Hexify.h> +#include <Swiften/StringCodecs/SHA1.h> + +#include <Swiften/Base/Log.h> + +namespace Swift { + +OutgoingJingleFileTransfer::OutgoingJingleFileTransfer(JingleSession::ref session, + RemoteJingleTransportCandidateSelectorFactory* remoteFactory, + LocalJingleTransportCandidateGeneratorFactory* localFactory, + IQRouter* router, + IDGenerator *idGenerator, + const JID& toJID, + boost::shared_ptr<ReadBytestream> readStream, + const StreamInitiationFileInfo &fileInfo, + SOCKS5BytestreamRegistry* bytestreamRegistry, + SOCKS5BytestreamProxy* bytestreamProxy) : + session(session), remoteFactory(remoteFactory), localFactory(localFactory), router(router), idGenerator(idGenerator), toJID(toJID), readStream(readStream), fileInfo(fileInfo), s5bRegistry(bytestreamRegistry), s5bProxy(bytestreamProxy), serverSession(NULL), contentID(JingleContentID(idGenerator->generateID(), JingleContentPayload::InitiatorCreator)), canceled(false) { + session->onSessionAcceptReceived.connect(boost::bind(&OutgoingJingleFileTransfer::handleSessionAcceptReceived, this, _1, _2, _3)); + session->onSessionTerminateReceived.connect(boost::bind(&OutgoingJingleFileTransfer::handleSessionTerminateReceived, this, _1)); + session->onTransportInfoReceived.connect(boost::bind(&OutgoingJingleFileTransfer::handleTransportInfoReceived, this, _1, _2)); + session->onTransportAcceptReceived.connect(boost::bind(&OutgoingJingleFileTransfer::handleTransportAcceptReceived, this, _1, _2)); + fileSizeInBytes = fileInfo.getSize(); + filename = fileInfo.getName(); + + localCandidateGenerator = localFactory->createCandidateGenerator(); + localCandidateGenerator->onLocalTransportCandidatesGenerated.connect(boost::bind(&OutgoingJingleFileTransfer::handleLocalTransportCandidatesGenerated, this, _1)); + + remoteCandidateSelector = remoteFactory->createCandidateSelector(); + remoteCandidateSelector->onRemoteTransportCandidateSelectFinished.connect(boost::bind(&OutgoingJingleFileTransfer::handleRemoteTransportCandidateSelectFinished, this, _1)); + // calculate both, MD5 and SHA-1 since we don't know which one the other side supports + hashCalculator = new IncrementalBytestreamHashCalculator(true, true); + this->readStream->onRead.connect(boost::bind(&IncrementalBytestreamHashCalculator::feedData, hashCalculator, _1)); +} + +OutgoingJingleFileTransfer::~OutgoingJingleFileTransfer() { + readStream->onRead.disconnect(boost::bind(&IncrementalBytestreamHashCalculator::feedData, hashCalculator, _1)); + delete hashCalculator; +} + +void OutgoingJingleFileTransfer::start() { + onStateChange(FileTransfer::State(FileTransfer::State::WaitingForStart)); + + s5bSessionID = s5bRegistry->generateSessionID(); + SWIFT_LOG(debug) << "S5B SessionID: " << s5bSessionID << std::endl; + + //s5bProxy->connectToProxies(s5bSessionID); + + JingleS5BTransportPayload::ref transport = boost::make_shared<JingleS5BTransportPayload>(); + localCandidateGenerator->generateLocalTransportCandidates(transport); +} + +void OutgoingJingleFileTransfer::stop() { + +} + +void OutgoingJingleFileTransfer::cancel() { + canceled = true; + session->sendTerminate(JinglePayload::Reason::Cancel); + + if (ibbSession) { + ibbSession->stop(); + } + SOCKS5BytestreamServerSession *serverSession = s5bRegistry->getConnectedSession(SOCKS5BytestreamRegistry::getHostname(s5bSessionID, session->getInitiator(), toJID)); + if (serverSession) { + serverSession->stop(); + } + if (clientSession) { + clientSession->stop(); + } + onStateChange(FileTransfer::State(FileTransfer::State::Canceled)); +} + +void OutgoingJingleFileTransfer::handleSessionAcceptReceived(const JingleContentID& id, JingleDescription::ref /* decription */, JingleTransportPayload::ref transportPayload) { + if (canceled) { + return; + } + onStateChange(FileTransfer::State(FileTransfer::State::Negotiating)); + + JingleIBBTransportPayload::ref ibbPayload; + JingleS5BTransportPayload::ref s5bPayload; + if ((ibbPayload = boost::dynamic_pointer_cast<JingleIBBTransportPayload>(transportPayload))) { + ibbSession = boost::make_shared<IBBSendSession>(ibbPayload->getSessionID(), toJID, readStream, router); + ibbSession->setBlockSize(ibbPayload->getBlockSize()); + ibbSession->onBytesSent.connect(boost::bind(boost::ref(onProcessedBytes), _1)); + ibbSession->onFinished.connect(boost::bind(&OutgoingJingleFileTransfer::handleTransferFinished, this, _1)); + ibbSession->start(); + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); + } + else if ((s5bPayload = boost::dynamic_pointer_cast<JingleS5BTransportPayload>(transportPayload))) { + fillCandidateMap(theirCandidates, s5bPayload); + remoteCandidateSelector->setRequesterTargtet(toJID, session->getInitiator()); + remoteCandidateSelector->addRemoteTransportCandidates(s5bPayload); + remoteCandidateSelector->selectCandidate(); + } + else { + // TODO: error handling + SWIFT_LOG(debug) << "Unknown transport payload! Try replaceing with IBB." << std::endl; + replaceTransportWithIBB(id); + } +} + +void OutgoingJingleFileTransfer::handleSessionTerminateReceived(boost::optional<JinglePayload::Reason> reason) { + if (canceled) { + return; + } + + if (ibbSession) { + ibbSession->stop(); + } + if (clientSession) { + clientSession->stop(); + } + if (serverSession) { + serverSession->stop(); + } + + if (reason.is_initialized() && reason.get().type == JinglePayload::Reason::Cancel) { + onStateChange(FileTransfer::State(FileTransfer::State::Canceled)); + onFinished(FileTransferError(FileTransferError::PeerError)); + } else if (reason.is_initialized() && reason.get().type == JinglePayload::Reason::Success) { + onStateChange(FileTransfer::State(FileTransfer::State::Finished)); + onFinished(boost::optional<FileTransferError>()); + } else { + onStateChange(FileTransfer::State(FileTransfer::State::Failed)); + onFinished(FileTransferError(FileTransferError::PeerError)); + } + canceled = true; +} + +void OutgoingJingleFileTransfer::handleTransportAcceptReceived(const JingleContentID& /* contentID */, JingleTransportPayload::ref transport) { + if (canceled) { + return; + } + + if (JingleIBBTransportPayload::ref ibbPayload = boost::dynamic_pointer_cast<JingleIBBTransportPayload>(transport)) { + ibbSession = boost::make_shared<IBBSendSession>(ibbPayload->getSessionID(), toJID, readStream, router); + ibbSession->setBlockSize(ibbPayload->getBlockSize()); + ibbSession->onBytesSent.connect(boost::bind(boost::ref(onProcessedBytes), _1)); + ibbSession->onFinished.connect(boost::bind(&OutgoingJingleFileTransfer::handleTransferFinished, this, _1)); + ibbSession->start(); + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); + } else { + // error handling + SWIFT_LOG(debug) << "Replacing with anything other than IBB isn't supported yet." << std::endl; + session->sendTerminate(JinglePayload::Reason::FailedTransport); + onStateChange(FileTransfer::State(FileTransfer::State::Failed)); + } +} + +void OutgoingJingleFileTransfer::startTransferViaOurCandidateChoice(JingleS5BTransportPayload::Candidate candidate) { + SWIFT_LOG(debug) << "Transferring data using our candidate." << std::endl; + if (candidate.type == JingleS5BTransportPayload::Candidate::ProxyType) { + // get proxy client session from remoteCandidateSelector + clientSession = remoteCandidateSelector->getS5BSession(); + + // wait on <activated/> transport-info + } else { + clientSession = remoteCandidateSelector->getS5BSession(); + clientSession->onBytesSent.connect(boost::bind(boost::ref(onProcessedBytes), _1)); + clientSession->onFinished.connect(boost::bind(&OutgoingJingleFileTransfer::handleTransferFinished, this, _1)); + clientSession->startSending(readStream); + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); + } + assert(clientSession); +} + +void OutgoingJingleFileTransfer::startTransferViaTheirCandidateChoice(JingleS5BTransportPayload::Candidate candidate) { + SWIFT_LOG(debug) << "Transferring data using their candidate." << std::endl; + if (candidate.type == JingleS5BTransportPayload::Candidate::ProxyType) { + // connect to proxy + clientSession = s5bProxy->createSOCKS5BytestreamClientSession(candidate.hostPort, SOCKS5BytestreamRegistry::getHostname(s5bSessionID, session->getInitiator(), toJID)); + clientSession->onSessionReady.connect(boost::bind(&OutgoingJingleFileTransfer::proxySessionReady, this, candidate.jid, _1)); + clientSession->start(); + + // on reply send activate + + } else { + serverSession = s5bRegistry->getConnectedSession(SOCKS5BytestreamRegistry::getHostname(s5bSessionID, session->getInitiator(), toJID)); + serverSession->onBytesSent.connect(boost::bind(boost::ref(onProcessedBytes), _1)); + serverSession->onFinished.connect(boost::bind(&OutgoingJingleFileTransfer::handleTransferFinished, this, _1)); + serverSession->startTransfer(); + } + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); +} + +// decide on candidates according to http://xmpp.org/extensions/xep-0260.html#complete +void OutgoingJingleFileTransfer::decideOnCandidates() { + if (ourCandidateChoice && theirCandidateChoice) { + std::string our_cid = ourCandidateChoice->getCandidateUsed(); + std::string their_cid = theirCandidateChoice->getCandidateUsed(); + if (ourCandidateChoice->hasCandidateError() && theirCandidateChoice->hasCandidateError()) { + replaceTransportWithIBB(contentID); + } + else if (!our_cid.empty() && theirCandidateChoice->hasCandidateError()) { + // use our candidate + startTransferViaOurCandidateChoice(theirCandidates[our_cid]); + } + else if (!their_cid.empty() && ourCandidateChoice->hasCandidateError()) { + // use their candidate + startTransferViaTheirCandidateChoice(ourCandidates[their_cid]); + } + else if (!our_cid.empty() && !their_cid.empty()) { + // compare priorites, if same we win + if (ourCandidates.find(their_cid) == ourCandidates.end() || theirCandidates.find(our_cid) == theirCandidates.end()) { + SWIFT_LOG(debug) << "Didn't recognize candidate IDs!" << std::endl; + session->sendTerminate(JinglePayload::Reason::FailedTransport); + onStateChange(FileTransfer::State(FileTransfer::State::Failed)); + onFinished(FileTransferError(FileTransferError::PeerError)); + return; + } + + JingleS5BTransportPayload::Candidate ourCandidate = theirCandidates[our_cid]; + JingleS5BTransportPayload::Candidate theirCandidate = ourCandidates[their_cid]; + if (ourCandidate.priority > theirCandidate.priority) { + startTransferViaOurCandidateChoice(ourCandidate); + } + else if (ourCandidate.priority < theirCandidate.priority) { + startTransferViaTheirCandidateChoice(theirCandidate); + } + else { + startTransferViaOurCandidateChoice(ourCandidate); + } + } + } else { + SWIFT_LOG(debug) << "Can't make a decision yet!" << std::endl; + } +} + +void OutgoingJingleFileTransfer::fillCandidateMap(CandidateMap& map, JingleS5BTransportPayload::ref s5bPayload) { + map.clear(); + foreach (JingleS5BTransportPayload::Candidate candidate, s5bPayload->getCandidates()) { + map[candidate.cid] = candidate; + } +} + +void OutgoingJingleFileTransfer::proxySessionReady(const JID& proxy, bool error) { + if (error) { + // indicate proxy error + } else { + // activate proxy + activateProxySession(proxy); + } +} + +void OutgoingJingleFileTransfer::activateProxySession(const JID& proxy) { + S5BProxyRequest::ref proxyRequest = boost::make_shared<S5BProxyRequest>(); + proxyRequest->setSID(s5bSessionID); + proxyRequest->setActivate(toJID); + + boost::shared_ptr<GenericRequest<S5BProxyRequest> > request = boost::make_shared<GenericRequest<S5BProxyRequest> >(IQ::Set, proxy, proxyRequest, router); + request->onResponse.connect(boost::bind(&OutgoingJingleFileTransfer::handleActivateProxySessionResult, this, _1, _2)); + request->send(); +} + +void OutgoingJingleFileTransfer::handleActivateProxySessionResult(boost::shared_ptr<S5BProxyRequest> /*request*/, ErrorPayload::ref error) { + if (error) { + SWIFT_LOG(debug) << "ERROR" << std::endl; + } else { + // send activated to other jingle party + JingleS5BTransportPayload::ref proxyActivate = boost::make_shared<JingleS5BTransportPayload>(); + proxyActivate->setActivated(theirCandidateChoice->getCandidateUsed()); + session->sendTransportInfo(contentID, proxyActivate); + + // start transferring + clientSession->onBytesSent.connect(boost::bind(boost::ref(onProcessedBytes), _1)); + clientSession->onFinished.connect(boost::bind(&OutgoingJingleFileTransfer::handleTransferFinished, this, _1)); + clientSession->startSending(readStream); + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); + } +} + +void OutgoingJingleFileTransfer::sendSessionInfoHash() { + SWIFT_LOG(debug) << std::endl; + JingleFileTransferHash::ref hashElement = boost::make_shared<JingleFileTransferHash>(); + hashElement->setHash("sha-1", hashCalculator->getSHA1String()); + hashElement->setHash("md5", hashCalculator->getMD5String()); + session->sendInfo(hashElement); +} + +void OutgoingJingleFileTransfer::handleTransportInfoReceived(const JingleContentID& /* contentID */, JingleTransportPayload::ref transport) { + if (canceled) { + return; + } + if (JingleS5BTransportPayload::ref s5bPayload = boost::dynamic_pointer_cast<JingleS5BTransportPayload>(transport)) { + if (s5bPayload->hasCandidateError() || !s5bPayload->getCandidateUsed().empty()) { + theirCandidateChoice = s5bPayload; + decideOnCandidates(); + } else if(!s5bPayload->getActivated().empty()) { + if (ourCandidateChoice->getCandidateUsed() == s5bPayload->getActivated()) { + clientSession->onBytesSent.connect(boost::bind(boost::ref(onProcessedBytes), _1)); + clientSession->onFinished.connect(boost::bind(&OutgoingJingleFileTransfer::handleTransferFinished, this, _1)); + clientSession->startSending(readStream); + onStateChange(FileTransfer::State(FileTransfer::State::Transferring)); + } else { + SWIFT_LOG(debug) << "ourCandidateChoice doesn't match activated proxy candidate!" << std::endl; + JingleS5BTransportPayload::ref proxyError = boost::make_shared<JingleS5BTransportPayload>(); + proxyError->setProxyError(true); + proxyError->setSessionID(s5bSessionID); + session->sendTransportInfo(contentID, proxyError); + } + } + } +} + +void OutgoingJingleFileTransfer::handleLocalTransportCandidatesGenerated(JingleTransportPayload::ref payload) { + if (canceled) { + return; + } + JingleFileTransferDescription::ref description = boost::make_shared<JingleFileTransferDescription>(); + description->addOffer(fileInfo); + + JingleTransportPayload::ref transport; + if (JingleIBBTransportPayload::ref ibbTransport = boost::dynamic_pointer_cast<JingleIBBTransportPayload>(payload)) { + ibbTransport->setBlockSize(4096); + ibbTransport->setSessionID(idGenerator->generateID()); + transport = ibbTransport; + } + else if (JingleS5BTransportPayload::ref s5bTransport = boost::dynamic_pointer_cast<JingleS5BTransportPayload>(payload)) { + //fillCandidateMap(ourCandidates, s5bTransport); + //s5bTransport->setSessionID(s5bSessionID); + + JingleS5BTransportPayload::ref emptyCandidates = boost::make_shared<JingleS5BTransportPayload>(); + emptyCandidates->setSessionID(s5bTransport->getSessionID()); + fillCandidateMap(ourCandidates, emptyCandidates); + + transport = emptyCandidates; + s5bRegistry->addReadBytestream(SOCKS5BytestreamRegistry::getHostname(s5bSessionID, session->getInitiator(), toJID), readStream); + } + else { + SWIFT_LOG(debug) << "Unknown tranport payload: " << typeid(*payload).name() << std::endl; + return; + } + session->sendInitiate(contentID, description, transport); + onStateChange(FileTransfer::State(FileTransfer::State::WaitingForAccept)); +} + +void OutgoingJingleFileTransfer::handleRemoteTransportCandidateSelectFinished(JingleTransportPayload::ref payload) { + if (canceled) { + return; + } + if (JingleS5BTransportPayload::ref s5bPayload = boost::dynamic_pointer_cast<JingleS5BTransportPayload>(payload)) { + ourCandidateChoice = s5bPayload; + session->sendTransportInfo(contentID, s5bPayload); + decideOnCandidates(); + } +} + +void OutgoingJingleFileTransfer::replaceTransportWithIBB(const JingleContentID& id) { + SWIFT_LOG(debug) << "Both parties failed. Replace transport with IBB." << std::endl; + JingleIBBTransportPayload::ref ibbTransport = boost::make_shared<JingleIBBTransportPayload>(); + ibbTransport->setBlockSize(4096); + ibbTransport->setSessionID(idGenerator->generateID()); + session->sendTransportReplace(id, ibbTransport); +} + +void OutgoingJingleFileTransfer::handleTransferFinished(boost::optional<FileTransferError> error) { + if (error) { + session->sendTerminate(JinglePayload::Reason::ConnectivityError); + onStateChange(FileTransfer::State(FileTransfer::State::Failed)); + onFinished(error); + } else { + sendSessionInfoHash(); + /* + session->terminate(JinglePayload::Reason::Success); + onStateChange(FileTransfer::State(FileTransfer::State::Finished)); + */ + } + // +} + +} diff --git a/Swiften/FileTransfer/OutgoingJingleFileTransfer.h b/Swiften/FileTransfer/OutgoingJingleFileTransfer.h new file mode 100644 index 0000000..fecfbdb --- /dev/null +++ b/Swiften/FileTransfer/OutgoingJingleFileTransfer.h @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> + +#include <Swiften/Elements/JingleContentPayload.h> +#include <Swiften/Elements/JingleS5BTransportPayload.h> +#include <Swiften/Elements/StreamInitiationFileInfo.h> +#include <Swiften/Elements/S5BProxyRequest.h> +#include <Swiften/Elements/ErrorPayload.h> +#include <Swiften/FileTransfer/OutgoingFileTransfer.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamClientSession.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamServerSession.h> +#include <Swiften/Jingle/JingleContentID.h> +#include <Swiften/Jingle/JingleSession.h> +#include <Swiften/StringCodecs/SHA1.h> + +namespace Swift { + +class RemoteJingleTransportCandidateSelectorFactory; +class RemoteJingleTransportCandidateSelector; +class LocalJingleTransportCandidateGeneratorFactory; +class LocalJingleTransportCandidateGenerator; +class IQRouter; +class ReadBytestream; +class IBBSendSession; +class IDGenerator; +class IncrementalBytestreamHashCalculator; +class SOCKS5BytestreamRegistry; +class SOCKS5BytestreamProxy; + +class OutgoingJingleFileTransfer : public OutgoingFileTransfer { +public: + OutgoingJingleFileTransfer(JingleSession::ref, + RemoteJingleTransportCandidateSelectorFactory*, + LocalJingleTransportCandidateGeneratorFactory*, + IQRouter*, + IDGenerator*, + const JID&, + boost::shared_ptr<ReadBytestream>, + const StreamInitiationFileInfo&, + SOCKS5BytestreamRegistry*, + SOCKS5BytestreamProxy*); + virtual ~OutgoingJingleFileTransfer(); + + void start(); + void stop(); + + void cancel(); + +private: + void handleSessionAcceptReceived(const JingleContentID&, JingleDescription::ref, JingleTransportPayload::ref); + void handleSessionTerminateReceived(boost::optional<JinglePayload::Reason> reason); + void handleTransportAcceptReceived(const JingleContentID&, JingleTransportPayload::ref); + void handleTransportInfoReceived(const JingleContentID&, JingleTransportPayload::ref); + + void handleLocalTransportCandidatesGenerated(JingleTransportPayload::ref); + void handleRemoteTransportCandidateSelectFinished(JingleTransportPayload::ref); + +private: + void replaceTransportWithIBB(const JingleContentID&); + void handleTransferFinished(boost::optional<FileTransferError>); + void activateProxySession(const JID &proxy); + void handleActivateProxySessionResult(boost::shared_ptr<S5BProxyRequest> request, ErrorPayload::ref error); + void proxySessionReady(const JID& proxy, bool error); + +private: + typedef std::map<std::string, JingleS5BTransportPayload::Candidate> CandidateMap; + +private: + void startTransferViaOurCandidateChoice(JingleS5BTransportPayload::Candidate); + void startTransferViaTheirCandidateChoice(JingleS5BTransportPayload::Candidate); + void decideOnCandidates(); + void fillCandidateMap(CandidateMap& map, JingleS5BTransportPayload::ref s5bPayload); + +private: + void sendSessionInfoHash(); + +private: + JingleSession::ref session; + RemoteJingleTransportCandidateSelector* remoteCandidateSelector; + RemoteJingleTransportCandidateSelectorFactory* remoteFactory; + LocalJingleTransportCandidateGenerator* localCandidateGenerator; + LocalJingleTransportCandidateGeneratorFactory* localFactory; + + IQRouter* router; + IDGenerator* idGenerator; + JID toJID; + boost::shared_ptr<ReadBytestream> readStream; + StreamInitiationFileInfo fileInfo; + IncrementalBytestreamHashCalculator *hashCalculator; + + boost::shared_ptr<IBBSendSession> ibbSession; + + JingleS5BTransportPayload::ref ourCandidateChoice; + JingleS5BTransportPayload::ref theirCandidateChoice; + CandidateMap ourCandidates; + CandidateMap theirCandidates; + + SOCKS5BytestreamRegistry* s5bRegistry; + SOCKS5BytestreamProxy* s5bProxy; + SOCKS5BytestreamClientSession::ref clientSession; + SOCKS5BytestreamServerSession* serverSession; + JingleContentID contentID; + std::string s5bSessionID; + + bool canceled; +}; + +} diff --git a/Swiften/FileTransfer/OutgoingSIFileTransfer.cpp b/Swiften/FileTransfer/OutgoingSIFileTransfer.cpp index 85ac505..dfcf028 100644 --- a/Swiften/FileTransfer/OutgoingSIFileTransfer.cpp +++ b/Swiften/FileTransfer/OutgoingSIFileTransfer.cpp @@ -38,7 +38,7 @@ void OutgoingSIFileTransfer::handleStreamInitiationRequestResponse(StreamInitiat } else { if (response->getRequestedMethod() == "http://jabber.org/protocol/bytestreams") { - socksServer->addBytestream(id, from, to, bytestream); + socksServer->addReadBytestream(id, from, to, bytestream); Bytestreams::ref bytestreams(new Bytestreams()); bytestreams->setStreamID(id); HostAddressPort addressPort = socksServer->getAddressPort(); @@ -67,7 +67,7 @@ void OutgoingSIFileTransfer::finish(boost::optional<FileTransferError> error) { ibbSession->onFinished.disconnect(boost::bind(&OutgoingSIFileTransfer::handleIBBSessionFinished, this, _1)); ibbSession.reset(); } - socksServer->removeBytestream(id, from, to); + socksServer->removeReadBytestream(id, from, to); onFinished(error); } diff --git a/Swiften/FileTransfer/ReadBytestream.h b/Swiften/FileTransfer/ReadBytestream.h index 2601192..9e070f7 100644 --- a/Swiften/FileTransfer/ReadBytestream.h +++ b/Swiften/FileTransfer/ReadBytestream.h @@ -9,11 +9,16 @@ #include <vector> #include <cstring> +#include <Swiften/Base/boost_bsignals.h> + namespace Swift { class ReadBytestream { public: virtual ~ReadBytestream(); virtual std::vector<unsigned char> read(size_t size) = 0; virtual bool isFinished() const = 0; + + public: + boost::signal<void (std::vector<unsigned char>)> onRead; }; } diff --git a/Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.h b/Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.h index b12b06b..f8df8f9 100644 --- a/Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.h +++ b/Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.h @@ -8,8 +8,10 @@ #include <Swiften/Base/boost_bsignals.h> +#include <Swiften/JID/JID.h> #include <Swiften/Elements/JingleTransportPayload.h> #include <Swiften/FileTransfer/JingleTransport.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamClientSession.h> namespace Swift { class RemoteJingleTransportCandidateSelector { @@ -19,6 +21,8 @@ namespace Swift { virtual void addRemoteTransportCandidates(JingleTransportPayload::ref) = 0; virtual void selectCandidate() = 0; virtual void setMinimumPriority(int) = 0; + virtual void setRequesterTargtet(const JID&, const JID&) {} + virtual SOCKS5BytestreamClientSession::ref getS5BSession() { return SOCKS5BytestreamClientSession::ref(); } virtual bool isActualCandidate(JingleTransportPayload::ref) = 0; virtual int getPriority(JingleTransportPayload::ref) = 0; diff --git a/Swiften/FileTransfer/SConscript b/Swiften/FileTransfer/SConscript index 24fc9e8..ef02557 100644 --- a/Swiften/FileTransfer/SConscript +++ b/Swiften/FileTransfer/SConscript @@ -3,6 +3,9 @@ Import("swiften_env", "env") sources = [ "OutgoingFileTransfer.cpp", "OutgoingSIFileTransfer.cpp", + "OutgoingJingleFileTransfer.cpp", + "OutgoingFileTransferManager.cpp", + "OutgoingFileTransferManager.cpp", "IncomingFileTransfer.cpp", "IncomingJingleFileTransfer.cpp", "IncomingFileTransferManager.cpp", @@ -10,23 +13,36 @@ sources = [ "RemoteJingleTransportCandidateSelectorFactory.cpp", "LocalJingleTransportCandidateGenerator.cpp", "LocalJingleTransportCandidateGeneratorFactory.cpp", + "DefaultRemoteJingleTransportCandidateSelectorFactory.cpp", + "DefaultLocalJingleTransportCandidateGeneratorFactory.cpp", + "DefaultRemoteJingleTransportCandidateSelector.cpp", + "DefaultLocalJingleTransportCandidateGenerator.cpp", "JingleTransport.cpp", "JingleIncomingIBBTransport.cpp", "ReadBytestream.cpp", "WriteBytestream.cpp", "FileReadBytestream.cpp", "FileWriteBytestream.cpp", + "SOCKS5BytestreamClientSession.cpp", "SOCKS5BytestreamServer.cpp", "SOCKS5BytestreamServerSession.cpp", "SOCKS5BytestreamRegistry.cpp", + "SOCKS5BytestreamProxy.cpp", "IBBSendSession.cpp", "IBBReceiveSession.cpp", + "FileTransferManager.cpp", + "FileTransferManagerImpl.cpp", + "IncrementalBytestreamHashCalculator.cpp", + "ConnectivityManager.cpp", ] swiften_env.Append(SWIFTEN_OBJECTS = swiften_env.SwiftenObject(sources)) env.Append(UNITTEST_SOURCES = [ File("UnitTest/SOCKS5BytestreamServerSessionTest.cpp"), + File("UnitTest/SOCKS5BytestreamClientSessionTest.cpp"), File("UnitTest/IBBSendSessionTest.cpp"), File("UnitTest/IBBReceiveSessionTest.cpp"), + File("UnitTest/IncomingJingleFileTransferTest.cpp"), + File("UnitTest/OutgoingJingleFileTransferTest.cpp"), ]) diff --git a/Swiften/FileTransfer/SOCKS5BytestreamClientSession.cpp b/Swiften/FileTransfer/SOCKS5BytestreamClientSession.cpp new file mode 100644 index 0000000..f90b73b --- /dev/null +++ b/Swiften/FileTransfer/SOCKS5BytestreamClientSession.cpp @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "SOCKS5BytestreamClientSession.h" + +#include <boost/bind.hpp> + +#include <Swiften/Base/Algorithm.h> +#include <Swiften/Base/SafeByteArray.h> +#include <Swiften/Base/Concat.h> +#include <Swiften/Base/Log.h> +#include <Swiften/StringCodecs/SHA1.h> +#include <Swiften/StringCodecs/Hexify.h> +#include <Swiften/FileTransfer/BytestreamException.h> +#include <Swiften/Network/TimerFactory.h> + +namespace Swift { + +SOCKS5BytestreamClientSession::SOCKS5BytestreamClientSession(boost::shared_ptr<Connection> connection, const HostAddressPort& addressPort, const std::string& destination, TimerFactory* timerFactory) : + connection(connection), addressPort(addressPort), destination(destination), state(Initial), chunkSize(131072) { + connection->onConnectFinished.connect(boost::bind(&SOCKS5BytestreamClientSession::handleConnectFinished, this, _1)); + connection->onDisconnected.connect(boost::bind(&SOCKS5BytestreamClientSession::handleDisconnected, this, _1)); + weFailedTimeout = timerFactory->createTimer(2000); + weFailedTimeout->onTick.connect(boost::bind(&SOCKS5BytestreamClientSession::handleWeFailedTimeout, this)); +} + +void SOCKS5BytestreamClientSession::start() { + assert(state == Initial); + SWIFT_LOG(debug) << "Trying to connect via TCP to " << addressPort.toString() << "." << std::endl; + weFailedTimeout->start(); + connection->connect(addressPort); +} + +void SOCKS5BytestreamClientSession::stop() { + connection->disconnect(); + connection->onDataWritten.disconnect(boost::bind(&SOCKS5BytestreamClientSession::sendData, this)); + connection->onDataRead.disconnect(boost::bind(&SOCKS5BytestreamClientSession::handleDataRead, this, _1)); + readBytestream.reset(); + state = Finished; +} + +void SOCKS5BytestreamClientSession::process() { + SWIFT_LOG(debug) << "unprocessedData.size(): " << unprocessedData.size() << std::endl; + ByteArray bndAddress; + switch(state) { + case Initial: + hello(); + break; + case Hello: + if (unprocessedData.size() > 1) { + unsigned char version = unprocessedData[0]; + unsigned char authMethod = unprocessedData[1]; + if (version != 5 || authMethod != 0) { + // signal failure to upper level + finish(true); + return; + } + unprocessedData.clear(); + authenticate(); + } + break; + case Authenticating: + if (unprocessedData.size() < 5) { + // need more data to start progressing + break; + } + if (unprocessedData[0] != '\x05') { + // wrong version + // disconnect & signal failure + finish(true); + break; + } + if (unprocessedData[1] != '\x00') { + // no success + // disconnect & signal failure + finish(true); + break; + } + if (unprocessedData[3] != '\x03') { + // we expect x'03' = DOMAINNAME here + // discconect & signal failure + finish(true); + break; + } + if (static_cast<size_t>(unprocessedData[4]) + 1 > unprocessedData.size() + 5) { + // complete domainname and port not available yet + break; + } + bndAddress = createByteArray(&(unprocessedData.data()[5]), unprocessedData[4]); + if (unprocessedData[unprocessedData[4] + 5] != 0 && bndAddress == createByteArray(destination)) { + // we expect a 0 as port + // disconnect and fail + finish(true); + } + unprocessedData.clear(); + state = Ready; + SWIFT_LOG(debug) << "session ready" << std::endl; + // issue ready signal so the bytestream can be used for reading or writing + weFailedTimeout->stop(); + onSessionReady(false); + break; + case Ready: + SWIFT_LOG(debug) << "Received further data in Ready state." << std::endl; + break; + case Reading: + case Writing: + case Finished: + SWIFT_LOG(debug) << "Unexpected receive of data. Current state: " << state << std::endl; + SWIFT_LOG(debug) << "Data: " << Hexify::hexify(unprocessedData) << std::endl; + unprocessedData.clear(); + //assert(false); + } +} + +void SOCKS5BytestreamClientSession::hello() { + // Version 5, 1 auth method, No authentication + const SafeByteArray hello = createSafeByteArray("\x05\x01\x00", 3); + connection->write(hello); + state = Hello; +} + +void SOCKS5BytestreamClientSession::authenticate() { + SWIFT_LOG(debug) << std::endl; + SafeByteArray header = createSafeByteArray("\x05\x01\x00\x03", 4); + SafeByteArray message = header; + append(message, createSafeByteArray(destination.size())); + authenticateAddress = createByteArray(destination); + append(message, authenticateAddress); + append(message, createSafeByteArray("\x00\x00", 2)); // 2 byte for port + connection->write(message); + state = Authenticating; +} + +void SOCKS5BytestreamClientSession::startReceiving(boost::shared_ptr<WriteBytestream> writeStream) { + if (state == Ready) { + state = Reading; + writeBytestream = writeStream; + writeBytestream->write(unprocessedData); + onBytesReceived(unprocessedData.size()); + unprocessedData.clear(); + } else { + SWIFT_LOG(debug) << "Session isn't ready for transfer yet!" << std::endl; + } +} + +void SOCKS5BytestreamClientSession::startSending(boost::shared_ptr<ReadBytestream> readStream) { + if (state == Ready) { + state = Writing; + readBytestream = readStream; + connection->onDataWritten.connect(boost::bind(&SOCKS5BytestreamClientSession::sendData, this)); + sendData(); + } else { + SWIFT_LOG(debug) << "Session isn't ready for transfer yet!" << std::endl; + } +} + +HostAddressPort SOCKS5BytestreamClientSession::getAddressPort() const { + return addressPort; +} + +void SOCKS5BytestreamClientSession::sendData() { + if (!readBytestream->isFinished()) { + try { + SafeByteArray dataToSend = createSafeByteArray(readBytestream->read(chunkSize)); + connection->write(dataToSend); + onBytesSent(dataToSend.size()); + } + catch (const BytestreamException&) { + finish(true); + } + } + else { + finish(false); + } +} + +void SOCKS5BytestreamClientSession::finish(bool error) { + weFailedTimeout->stop(); + connection->disconnect(); + connection->onDataWritten.disconnect(boost::bind(&SOCKS5BytestreamClientSession::sendData, this)); + connection->onDataRead.disconnect(boost::bind(&SOCKS5BytestreamClientSession::handleDataRead, this, _1)); + readBytestream.reset(); + if (state == Initial || state == Hello || state == Authenticating) { + onSessionReady(true); + } + else { + state = Finished; + if (error) { + onFinished(boost::optional<FileTransferError>(FileTransferError::ReadError)); + } else { + onFinished(boost::optional<FileTransferError>()); + } + } +} + +void SOCKS5BytestreamClientSession::handleConnectFinished(bool error) { + if (error) { + SWIFT_LOG(debug) << "Failed to connect via TCP to " << addressPort.toString() << "." << std::endl; + finish(true); + } else { + SWIFT_LOG(debug) << "Successfully connected via TCP" << addressPort.toString() << "." << std::endl; + weFailedTimeout->start(); + connection->onDataRead.connect(boost::bind(&SOCKS5BytestreamClientSession::handleDataRead, this, _1)); + process(); + } +} + +void SOCKS5BytestreamClientSession::handleDataRead(const SafeByteArray& data) { + SWIFT_LOG(debug) << "state: " << state << " data.size() = " << data.size() << std::endl; + if (state != Reading) { + append(unprocessedData, data); + process(); + } + else { + writeBytestream->write(createByteArray(data.data(), data.size())); + onBytesReceived(data.size()); + } +} + +void SOCKS5BytestreamClientSession::handleDisconnected(const boost::optional<Connection::Error>& error) { + SWIFT_LOG(debug) << (error ? (error == Connection::ReadError ? "Read Error" : "Write Error") : "No Error") << std::endl; + if (error) { + finish(true); + } +} + +void SOCKS5BytestreamClientSession::handleWeFailedTimeout() { + SWIFT_LOG(debug) << "Failed due to timeout!" << std::endl; + finish(true); +} + +} diff --git a/Swiften/FileTransfer/SOCKS5BytestreamClientSession.h b/Swiften/FileTransfer/SOCKS5BytestreamClientSession.h new file mode 100644 index 0000000..894e977 --- /dev/null +++ b/Swiften/FileTransfer/SOCKS5BytestreamClientSession.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/optional.hpp> + +#include <Swiften/Base/ByteArray.h> +#include <Swiften/FileTransfer/FileTransferError.h> +#include <Swiften/FileTransfer/WriteBytestream.h> +#include <Swiften/FileTransfer/ReadBytestream.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Network/Connection.h> +#include <Swiften/Network/HostAddressPort.h> +#include <Swiften/Network/Timer.h> + +namespace Swift { + +class SOCKS5BytestreamRegistry; +class Connection; +class TimerFactory; + +/** + * A session which has been connected to a SOCKS5 server (requester). + * + */ +class SOCKS5BytestreamClientSession { +public: + enum State { + Initial, + Hello, + Authenticating, + Ready, + Writing, + Reading, + Finished, + }; + +public: + typedef boost::shared_ptr<SOCKS5BytestreamClientSession> ref; + +public: + SOCKS5BytestreamClientSession(boost::shared_ptr<Connection> connection, const HostAddressPort&, const std::string&, TimerFactory*); + + void start(); + void stop(); + + void startReceiving(boost::shared_ptr<WriteBytestream>); + void startSending(boost::shared_ptr<ReadBytestream>); + + HostAddressPort getAddressPort() const; + + boost::signal<void (bool /*error*/)> onSessionReady; + + boost::signal<void (boost::optional<FileTransferError>)> onFinished; + boost::signal<void (int)> onBytesSent; + boost::signal<void (int)> onBytesReceived; + +private: + void process(); + void hello(); + void authenticate(); + + void handleConnectFinished(bool error); + void handleDataRead(const SafeByteArray&); + void handleDisconnected(const boost::optional<Connection::Error>&); + void handleWeFailedTimeout(); + + void finish(bool error); + void sendData(); + +private: + boost::shared_ptr<Connection> connection; + HostAddressPort addressPort; + std::string destination; // hexify(SHA1(sessionID + requester + target)) + + State state; + int destinationPort; + + ByteArray unprocessedData; + ByteArray authenticateAddress; + + int chunkSize; + boost::shared_ptr<WriteBytestream> writeBytestream; + boost::shared_ptr<ReadBytestream> readBytestream; + + Timer::ref weFailedTimeout; +}; + +} diff --git a/Swiften/FileTransfer/SOCKS5BytestreamProxy.cpp b/Swiften/FileTransfer/SOCKS5BytestreamProxy.cpp new file mode 100644 index 0000000..9599fd1 --- /dev/null +++ b/Swiften/FileTransfer/SOCKS5BytestreamProxy.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "SOCKS5BytestreamProxy.h" + +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamClientSession.h> +#include <Swiften/Base/Log.h> + +namespace Swift { + +SOCKS5BytestreamProxy::SOCKS5BytestreamProxy(ConnectionFactory *connFactory, TimerFactory *timeFactory) : connectionFactory(connFactory), timerFactory(timeFactory) { + +} + +void SOCKS5BytestreamProxy::addS5BProxy(S5BProxyRequest::ref proxy) { + localS5BProxies.push_back(proxy); +} + +const std::vector<S5BProxyRequest::ref>& SOCKS5BytestreamProxy::getS5BProxies() const { + return localS5BProxies; +} + +void SOCKS5BytestreamProxy::connectToProxies(const std::string& sessionID) { + SWIFT_LOG(debug) << "session ID: " << sessionID << std::endl; + ProxyJIDClientSessionMap clientSessions; + + foreach(S5BProxyRequest::ref proxy, localS5BProxies) { + boost::shared_ptr<Connection> conn = connectionFactory->createConnection(); + + boost::shared_ptr<SOCKS5BytestreamClientSession> session = boost::make_shared<SOCKS5BytestreamClientSession>(conn, proxy->getStreamHost().get().addressPort, sessionID, timerFactory); + clientSessions[proxy->getStreamHost().get().jid] = session; + session->start(); + } + + proxySessions[sessionID] = clientSessions; +} + +boost::shared_ptr<SOCKS5BytestreamClientSession> SOCKS5BytestreamProxy::getProxySessionAndCloseOthers(const JID& proxyJID, const std::string& sessionID) { + // checking parameters + if (proxySessions.find(sessionID) == proxySessions.end()) { + return boost::shared_ptr<SOCKS5BytestreamClientSession>(); + } + if (proxySessions[sessionID].find(proxyJID) == proxySessions[sessionID].end()) { + return boost::shared_ptr<SOCKS5BytestreamClientSession>(); + } + + // get active session + boost::shared_ptr<SOCKS5BytestreamClientSession> activeSession = proxySessions[sessionID][proxyJID]; + proxySessions[sessionID].erase(proxyJID); + + // close other sessions + foreach(const ProxyJIDClientSessionMap::value_type& myPair, proxySessions[sessionID]) { + myPair.second->stop(); + } + + proxySessions.erase(sessionID); + + return activeSession; +} + +boost::shared_ptr<SOCKS5BytestreamClientSession> SOCKS5BytestreamProxy::createSOCKS5BytestreamClientSession(HostAddressPort addressPort, const std::string& destAddr) { + SOCKS5BytestreamClientSession::ref connection = boost::make_shared<SOCKS5BytestreamClientSession>(connectionFactory->createConnection(), addressPort, destAddr, timerFactory); + return connection; +} + +} diff --git a/Swiften/FileTransfer/SOCKS5BytestreamProxy.h b/Swiften/FileTransfer/SOCKS5BytestreamProxy.h new file mode 100644 index 0000000..8f80cea --- /dev/null +++ b/Swiften/FileTransfer/SOCKS5BytestreamProxy.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <string> +#include <vector> + +#include <Swiften/Elements/S5BProxyRequest.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamClientSession.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Network/ConnectionFactory.h> +#include <Swiften/Network/TimerFactory.h> + +namespace Swift { + +/** + * - manages list of working S5B proxies + * - creates initial connections (for the candidates you provide) + */ +class SOCKS5BytestreamProxy { +public: + SOCKS5BytestreamProxy(ConnectionFactory*, TimerFactory*); + + void addS5BProxy(S5BProxyRequest::ref); + const std::vector<S5BProxyRequest::ref>& getS5BProxies() const; + + void connectToProxies(const std::string& sessionID); + boost::shared_ptr<SOCKS5BytestreamClientSession> getProxySessionAndCloseOthers(const JID& proxyJID, const std::string& sessionID); + + boost::shared_ptr<SOCKS5BytestreamClientSession> createSOCKS5BytestreamClientSession(HostAddressPort addressPort, const std::string& destAddr); + +private: + ConnectionFactory* connectionFactory; + TimerFactory* timerFactory; + + typedef std::map<JID, boost::shared_ptr<SOCKS5BytestreamClientSession> > ProxyJIDClientSessionMap; + std::map<std::string, ProxyJIDClientSessionMap> proxySessions; + + std::vector<S5BProxyRequest::ref> localS5BProxies; +}; + +} diff --git a/Swiften/FileTransfer/SOCKS5BytestreamRegistry.cpp b/Swiften/FileTransfer/SOCKS5BytestreamRegistry.cpp index 429c7f2..ffc4298 100644 --- a/Swiften/FileTransfer/SOCKS5BytestreamRegistry.cpp +++ b/Swiften/FileTransfer/SOCKS5BytestreamRegistry.cpp @@ -6,27 +6,67 @@ #include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/Algorithm.h> +#include <Swiften/Base/Log.h> +#include <Swiften/Base/foreach.h> +#include <Swiften/StringCodecs/SHA1.h> +#include <Swiften/StringCodecs/Hexify.h> + namespace Swift { SOCKS5BytestreamRegistry::SOCKS5BytestreamRegistry() { } -void SOCKS5BytestreamRegistry::addBytestream(const std::string& destination, boost::shared_ptr<ReadBytestream> byteStream) { - byteStreams[destination] = byteStream; +void SOCKS5BytestreamRegistry::addReadBytestream(const std::string& destination, boost::shared_ptr<ReadBytestream> byteStream) { + readBytestreams[destination] = byteStream; } -void SOCKS5BytestreamRegistry::removeBytestream(const std::string& destination) { - byteStreams.erase(destination); +void SOCKS5BytestreamRegistry::removeReadBytestream(const std::string& destination) { + readBytestreams.erase(destination); } -boost::shared_ptr<ReadBytestream> SOCKS5BytestreamRegistry::getBytestream(const std::string& destination) const { - BytestreamMap::const_iterator i = byteStreams.find(destination); - if (i != byteStreams.end()) { +boost::shared_ptr<ReadBytestream> SOCKS5BytestreamRegistry::getReadBytestream(const std::string& destination) const { + ReadBytestreamMap::const_iterator i = readBytestreams.find(destination); + if (i != readBytestreams.end()) { return i->second; } return boost::shared_ptr<ReadBytestream>(); } +void SOCKS5BytestreamRegistry::addWriteBytestream(const std::string& destination, boost::shared_ptr<WriteBytestream> byteStream) { + writeBytestreams[destination] = byteStream; +} + +void SOCKS5BytestreamRegistry::removeWriteBytestream(const std::string& destination) { + writeBytestreams.erase(destination); +} + +boost::shared_ptr<WriteBytestream> SOCKS5BytestreamRegistry::getWriteBytestream(const std::string& destination) const { + WriteBytestreamMap::const_iterator i = writeBytestreams.find(destination); + if (i != writeBytestreams.end()) { + return i->second; + } + return boost::shared_ptr<WriteBytestream>(); +} + +std::string SOCKS5BytestreamRegistry::generateSessionID() { + return idGenerator.generateID(); +} + +SOCKS5BytestreamServerSession* SOCKS5BytestreamRegistry::getConnectedSession(const std::string& destination) { + if (serverSessions.find(destination) != serverSessions.end()) { + return serverSessions[destination]; + } else { + SWIFT_LOG(debug) << "No active connction for stream ID " << destination << " found!" << std::endl; + return NULL; + } +} + +std::string SOCKS5BytestreamRegistry::getHostname(const std::string& sessionID, const JID& requester, const JID& target) { + return Hexify::hexify(SHA1::getHash(createSafeByteArray(sessionID + requester.toString() + target.toString()))); +} } diff --git a/Swiften/FileTransfer/SOCKS5BytestreamRegistry.h b/Swiften/FileTransfer/SOCKS5BytestreamRegistry.h index 955b900..779aabb 100644 --- a/Swiften/FileTransfer/SOCKS5BytestreamRegistry.h +++ b/Swiften/FileTransfer/SOCKS5BytestreamRegistry.h @@ -7,23 +7,58 @@ #pragma once #include <boost/shared_ptr.hpp> -#include <map> +#include <map> #include <string> +#include <vector> +#include <set> + +#include <Swiften/Base/IDGenerator.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/S5BProxyRequest.h> #include <Swiften/FileTransfer/ReadBytestream.h> +#include <Swiften/FileTransfer/WriteBytestream.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamServerSession.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamClientSession.h> +#include <Swiften/Network/HostAddressPort.h> namespace Swift { class SOCKS5BytestreamRegistry { public: SOCKS5BytestreamRegistry(); - boost::shared_ptr<ReadBytestream> getBytestream(const std::string& destination) const; - void addBytestream(const std::string& destination, boost::shared_ptr<ReadBytestream> byteStream); - void removeBytestream(const std::string& destination); + boost::shared_ptr<ReadBytestream> getReadBytestream(const std::string& destination) const; + void addReadBytestream(const std::string& destination, boost::shared_ptr<ReadBytestream> byteStream); + void removeReadBytestream(const std::string& destination); + + boost::shared_ptr<WriteBytestream> getWriteBytestream(const std::string& destination) const; + void addWriteBytestream(const std::string& destination, boost::shared_ptr<WriteBytestream> byteStream); + void removeWriteBytestream(const std::string& destination); + + /** + * Generate a new session ID to use for new S5B streams. + */ + std::string generateSessionID(); + + /** + * Start an actual transfer. + */ + SOCKS5BytestreamServerSession* getConnectedSession(const std::string& destination); + + public: + static std::string getHostname(const std::string& sessionID, const JID& requester, const JID& target); private: - typedef std::map<std::string, boost::shared_ptr<ReadBytestream> > BytestreamMap; - BytestreamMap byteStreams; + friend class SOCKS5BytestreamServerSession; + + typedef std::map<std::string, boost::shared_ptr<ReadBytestream> > ReadBytestreamMap; + ReadBytestreamMap readBytestreams; + + typedef std::map<std::string, boost::shared_ptr<WriteBytestream> > WriteBytestreamMap; + WriteBytestreamMap writeBytestreams; + + std::map<std::string, SOCKS5BytestreamServerSession*> serverSessions; + + IDGenerator idGenerator; }; } - diff --git a/Swiften/FileTransfer/SOCKS5BytestreamServer.cpp b/Swiften/FileTransfer/SOCKS5BytestreamServer.cpp index 9731d2d..90fed7a 100644 --- a/Swiften/FileTransfer/SOCKS5BytestreamServer.cpp +++ b/Swiften/FileTransfer/SOCKS5BytestreamServer.cpp @@ -8,13 +8,15 @@ #include <boost/bind.hpp> +#include <Swiften/Base/Log.h> #include <Swiften/StringCodecs/Hexify.h> #include <Swiften/StringCodecs/SHA1.h> #include <Swiften/FileTransfer/SOCKS5BytestreamServerSession.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> namespace Swift { -SOCKS5BytestreamServer::SOCKS5BytestreamServer(boost::shared_ptr<ConnectionServer> connectionServer) : connectionServer(connectionServer) { +SOCKS5BytestreamServer::SOCKS5BytestreamServer(boost::shared_ptr<ConnectionServer> connectionServer, SOCKS5BytestreamRegistry* registry) : connectionServer(connectionServer), registry(registry) { } void SOCKS5BytestreamServer::start() { @@ -25,12 +27,12 @@ void SOCKS5BytestreamServer::stop() { connectionServer->onNewConnection.disconnect(boost::bind(&SOCKS5BytestreamServer::handleNewConnection, this, _1)); } -void SOCKS5BytestreamServer::addBytestream(const std::string& id, const JID& from, const JID& to, boost::shared_ptr<ReadBytestream> byteStream) { - bytestreams.addBytestream(getSOCKSDestinationAddress(id, from, to), byteStream); +void SOCKS5BytestreamServer::addReadBytestream(const std::string& id, const JID& from, const JID& to, boost::shared_ptr<ReadBytestream> byteStream) { + registry->addReadBytestream(getSOCKSDestinationAddress(id, from, to), byteStream); } -void SOCKS5BytestreamServer::removeBytestream(const std::string& id, const JID& from, const JID& to) { - bytestreams.removeBytestream(getSOCKSDestinationAddress(id, from, to)); +void SOCKS5BytestreamServer::removeReadBytestream(const std::string& id, const JID& from, const JID& to) { + registry->removeReadBytestream(getSOCKSDestinationAddress(id, from, to)); } std::string SOCKS5BytestreamServer::getSOCKSDestinationAddress(const std::string& id, const JID& from, const JID& to) { @@ -38,7 +40,7 @@ std::string SOCKS5BytestreamServer::getSOCKSDestinationAddress(const std::string } void SOCKS5BytestreamServer::handleNewConnection(boost::shared_ptr<Connection> connection) { - boost::shared_ptr<SOCKS5BytestreamServerSession> session(new SOCKS5BytestreamServerSession(connection, &bytestreams)); + boost::shared_ptr<SOCKS5BytestreamServerSession> session(new SOCKS5BytestreamServerSession(connection, registry)); sessions.push_back(session); session->start(); } diff --git a/Swiften/FileTransfer/SOCKS5BytestreamServer.h b/Swiften/FileTransfer/SOCKS5BytestreamServer.h index 7fa709e..6bb598e 100644 --- a/Swiften/FileTransfer/SOCKS5BytestreamServer.h +++ b/Swiften/FileTransfer/SOCKS5BytestreamServer.h @@ -20,15 +20,15 @@ namespace Swift { class SOCKS5BytestreamServer { public: - SOCKS5BytestreamServer(boost::shared_ptr<ConnectionServer> connectionServer); + SOCKS5BytestreamServer(boost::shared_ptr<ConnectionServer> connectionServer, SOCKS5BytestreamRegistry* registry); HostAddressPort getAddressPort() const; void start(); void stop(); - void addBytestream(const std::string& id, const JID& from, const JID& to, boost::shared_ptr<ReadBytestream> byteStream); - void removeBytestream(const std::string& id, const JID& from, const JID& to); + void addReadBytestream(const std::string& id, const JID& from, const JID& to, boost::shared_ptr<ReadBytestream> byteStream); + void removeReadBytestream(const std::string& id, const JID& from, const JID& to); /*protected: boost::shared_ptr<ReadBytestream> getBytestream(const std::string& dest);*/ @@ -42,7 +42,7 @@ namespace Swift { friend class SOCKS5BytestreamServerSession; boost::shared_ptr<ConnectionServer> connectionServer; - SOCKS5BytestreamRegistry bytestreams; + SOCKS5BytestreamRegistry* registry; std::vector<boost::shared_ptr<SOCKS5BytestreamServerSession> > sessions; }; } diff --git a/Swiften/FileTransfer/SOCKS5BytestreamServerSession.cpp b/Swiften/FileTransfer/SOCKS5BytestreamServerSession.cpp index 477af4b..6f33862 100644 --- a/Swiften/FileTransfer/SOCKS5BytestreamServerSession.cpp +++ b/Swiften/FileTransfer/SOCKS5BytestreamServerSession.cpp @@ -13,12 +13,14 @@ #include <Swiften/Base/SafeByteArray.h> #include <Swiften/Base/Algorithm.h> #include <Swiften/Base/Concat.h> +#include <Swiften/Base/Log.h> #include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> #include <Swiften/FileTransfer/BytestreamException.h> namespace Swift { -SOCKS5BytestreamServerSession::SOCKS5BytestreamServerSession(boost::shared_ptr<Connection> connection, SOCKS5BytestreamRegistry* bytestreams) : connection(connection), bytestreams(bytestreams), state(Initial), chunkSize(4096) { +SOCKS5BytestreamServerSession::SOCKS5BytestreamServerSession(boost::shared_ptr<Connection> connection, SOCKS5BytestreamRegistry* bytestreams) : connection(connection), bytestreams(bytestreams), state(Initial), chunkSize(131072) { + connection->onDisconnected.connect(boost::bind(&SOCKS5BytestreamServerSession::handleDisconnected, this, _1)); } SOCKS5BytestreamServerSession::~SOCKS5BytestreamServerSession() { @@ -29,17 +31,55 @@ SOCKS5BytestreamServerSession::~SOCKS5BytestreamServerSession() { } void SOCKS5BytestreamServerSession::start() { + SWIFT_LOG(debug) << std::endl; connection->onDataRead.connect(boost::bind(&SOCKS5BytestreamServerSession::handleDataRead, this, _1)); state = WaitingForAuthentication; } void SOCKS5BytestreamServerSession::stop() { - finish(false); + connection->onDataWritten.disconnect(boost::bind(&SOCKS5BytestreamServerSession::sendData, this)); + connection->onDataRead.disconnect(boost::bind(&SOCKS5BytestreamServerSession::handleDataRead, this, _1)); + connection->disconnect(); + state = Finished; +} + +void SOCKS5BytestreamServerSession::startTransfer() { + if (state == ReadyForTransfer) { + if (readBytestream) { + state = WritingData; + connection->onDataWritten.connect(boost::bind(&SOCKS5BytestreamServerSession::sendData, this)); + sendData(); + } + else if(writeBytestream) { + state = ReadingData; + writeBytestream->write(unprocessedData); + onBytesReceived(unprocessedData.size()); + unprocessedData.clear(); + } + } else { + SWIFT_LOG(debug) << "Not ready for transfer!" << std::endl; + } +} + +HostAddressPort SOCKS5BytestreamServerSession::getAddressPort() const { + return connection->getLocalAddress(); } void SOCKS5BytestreamServerSession::handleDataRead(const SafeByteArray& data) { - append(unprocessedData, data); - process(); + if (state != ReadingData) { + append(unprocessedData, data); + process(); + } else { + writeBytestream->write(createByteArray(data.data(), data.size())); + onBytesReceived(data.size()); + } +} + +void SOCKS5BytestreamServerSession::handleDisconnected(const boost::optional<Connection::Error>& error) { + SWIFT_LOG(debug) << (error ? (error == Connection::ReadError ? "Read Error" : "Write Error") : "No Error") << std::endl; + if (error) { + finish(true); + } } void SOCKS5BytestreamServerSession::process() { @@ -54,7 +94,7 @@ void SOCKS5BytestreamServerSession::process() { if (i == 2 + authCount) { // Authentication message is complete if (i != unprocessedData.size()) { - std::cerr << "SOCKS5BytestreamServerSession: Junk after authentication mechanism"; + SWIFT_LOG(debug) << "Junk after authentication mechanism" << std::endl; } unprocessedData.clear(); connection->write(createSafeByteArray("\x05\x00", 2)); @@ -71,26 +111,31 @@ void SOCKS5BytestreamServerSession::process() { requestID.push_back(unprocessedData[i]); ++i; } - // Skip the port: + // Skip the port: 2 byte large, one already skipped. Add one for comparison with size i += 2; - if (i >= unprocessedData.size()) { + if (i <= unprocessedData.size()) { if (i != unprocessedData.size()) { - std::cerr << "SOCKS5BytestreamServerSession: Junk after authentication mechanism"; + SWIFT_LOG(debug) << "Junk after authentication mechanism" << std::endl; } - bytestream = bytestreams->getBytestream(byteArrayToString(requestID)); + unprocessedData.clear(); + std::string streamID = byteArrayToString(requestID); + readBytestream = bytestreams->getReadBytestream(streamID); + writeBytestream = bytestreams->getWriteBytestream(streamID); SafeByteArray result = createSafeByteArray("\x05", 1); - result.push_back(bytestream ? 0x0 : 0x4); + result.push_back((readBytestream || writeBytestream) ? 0x0 : 0x4); append(result, createByteArray("\x00\x03", 2)); result.push_back(static_cast<char>(requestID.size())); append(result, concat(requestID, createByteArray("\x00\x00", 2))); - if (!bytestream) { + if (!readBytestream && !writeBytestream) { + SWIFT_LOG(debug) << "Readstream or Wrtiestream with ID " << streamID << " not found!" << std::endl; connection->write(result); finish(true); } else { - state = SendingData; - connection->onDataWritten.connect(boost::bind(&SOCKS5BytestreamServerSession::sendData, this)); + SWIFT_LOG(deubg) << "Found " << (readBytestream ? "Readstream" : "Writestream") << ". Sent OK." << std::endl; connection->write(result); + bytestreams->serverSessions[streamID] = this; + state = ReadyForTransfer; } } } @@ -98,9 +143,11 @@ void SOCKS5BytestreamServerSession::process() { } void SOCKS5BytestreamServerSession::sendData() { - if (!bytestream->isFinished()) { + if (!readBytestream->isFinished()) { try { - connection->write(createSafeByteArray(bytestream->read(chunkSize))); + SafeByteArray dataToSend = createSafeByteArray(readBytestream->read(chunkSize)); + connection->write(dataToSend); + onBytesSent(dataToSend.size()); } catch (const BytestreamException&) { finish(true); @@ -114,9 +161,14 @@ void SOCKS5BytestreamServerSession::sendData() { void SOCKS5BytestreamServerSession::finish(bool error) { connection->onDataWritten.disconnect(boost::bind(&SOCKS5BytestreamServerSession::sendData, this)); connection->onDataRead.disconnect(boost::bind(&SOCKS5BytestreamServerSession::handleDataRead, this, _1)); - bytestream.reset(); + connection->onDisconnected.disconnect(boost::bind(&SOCKS5BytestreamServerSession::handleDisconnected, this, _1)); + readBytestream.reset(); state = Finished; - onFinished(error); + if (error) { + onFinished(boost::optional<FileTransferError>(FileTransferError::PeerError)); + } else { + onFinished(boost::optional<FileTransferError>()); + } } } diff --git a/Swiften/FileTransfer/SOCKS5BytestreamServerSession.h b/Swiften/FileTransfer/SOCKS5BytestreamServerSession.h index 761a365..3e1018f 100644 --- a/Swiften/FileTransfer/SOCKS5BytestreamServerSession.h +++ b/Swiften/FileTransfer/SOCKS5BytestreamServerSession.h @@ -11,17 +11,24 @@ #include <Swiften/Base/boost_bsignals.h> #include <Swiften/Network/Connection.h> #include <Swiften/FileTransfer/ReadBytestream.h> +#include <Swiften/FileTransfer/WriteBytestream.h> +#include <Swiften/FileTransfer/FileTransferError.h> namespace Swift { class SOCKS5BytestreamRegistry; class SOCKS5BytestreamServerSession { public: + typedef boost::shared_ptr<SOCKS5BytestreamServerSession> ref; + + public: enum State { Initial, WaitingForAuthentication, WaitingForRequest, - SendingData, + ReadyForTransfer, + ReadingData, + WritingData, Finished, }; @@ -35,12 +42,18 @@ namespace Swift { void start(); void stop(); - boost::signal<void (bool /* error */)> onFinished; + void startTransfer(); + HostAddressPort getAddressPort() const; + + boost::signal<void (boost::optional<FileTransferError>)> onFinished; + boost::signal<void (int)> onBytesSent; + boost::signal<void (int)> onBytesReceived; private: void finish(bool error); void process(); void handleDataRead(const SafeByteArray&); + void handleDisconnected(const boost::optional<Connection::Error>&); void sendData(); private: @@ -49,6 +62,7 @@ namespace Swift { ByteArray unprocessedData; State state; int chunkSize; - boost::shared_ptr<ReadBytestream> bytestream; + boost::shared_ptr<ReadBytestream> readBytestream; + boost::shared_ptr<WriteBytestream> writeBytestream; }; } diff --git a/Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h b/Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h new file mode 100644 index 0000000..ae06cd3 --- /dev/null +++ b/Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <string> +#include <boost/filesystem.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> + +#include <Swiften/FileTransfer/FileTransferManager.h> + +namespace Swift { + class DummyFileTransferManager : public FileTransferManager { + public: + DummyFileTransferManager() : FileTransferManager() { + } + + virtual OutgoingFileTransfer::ref createOutgoingFileTransfer(const JID&, const boost::filesystem::path&, const std::string&, boost::shared_ptr<ReadBytestream>) { + return OutgoingFileTransfer::ref(); + } + + virtual OutgoingFileTransfer::ref createOutgoingFileTransfer(const JID&, const std::string&, const std::string&, const boost::uintmax_t, const boost::posix_time::ptime&, boost::shared_ptr<ReadBytestream>) { + return OutgoingFileTransfer::ref(); + } + + virtual void startListeningOnPort(int) { + } + + virtual void addS5BProxy(boost::shared_ptr<S5BProxyRequest>) { + } + + }; +} diff --git a/Swiften/FileTransfer/UnitTest/IncomingJingleFileTransferTest.cpp b/Swiften/FileTransfer/UnitTest/IncomingJingleFileTransferTest.cpp new file mode 100644 index 0000000..7407f44 --- /dev/null +++ b/Swiften/FileTransfer/UnitTest/IncomingJingleFileTransferTest.cpp @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/ByteArray.h> +#include <Swiften/Base/Log.h> +#include <Swiften/Client/DummyStanzaChannel.h> +#include <Swiften/Elements/IBB.h> +#include <Swiften/Elements/JingleIBBTransportPayload.h> +#include <Swiften/Elements/JingleS5BTransportPayload.h> +#include <Swiften/FileTransfer/ByteArrayWriteBytestream.h> +#include <Swiften/FileTransfer/IncomingJingleFileTransfer.h> +#include <Swiften/FileTransfer/LocalJingleTransportCandidateGenerator.h> +#include <Swiften/FileTransfer/LocalJingleTransportCandidateGeneratorFactory.h> +#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.h> +#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelectorFactory.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamProxy.h> +#include <Swiften/Jingle/FakeJingleSession.h> +#include <Swiften/Network/DummyTimerFactory.h> +#include <Swiften/EventLoop/DummyEventLoop.h> +#include <Swiften/Network/DummyConnectionFactory.h> +#include <Swiften/Network/PlatformNATTraversalWorker.h> +#include <Swiften/Queries/IQRouter.h> + +#include <iostream> + +using namespace Swift; +using namespace boost; + +class FakeRemoteJingleTransportCandidateSelector : public RemoteJingleTransportCandidateSelector { + void addRemoteTransportCandidates(JingleTransportPayload::ref cand) { + candidate = cand; + } + + void selectCandidate() { + boost::shared_ptr<JingleS5BTransportPayload> payload = make_shared<JingleS5BTransportPayload>(); + payload->setCandidateError(true); + payload->setSessionID(candidate->getSessionID()); + onRemoteTransportCandidateSelectFinished(payload); + } + + void setMinimumPriority(int) { + + } + + bool isActualCandidate(JingleTransportPayload::ref) { + return false; + } + + int getPriority(JingleTransportPayload::ref) { + return 0; + } + + JingleTransport::ref selectTransport(JingleTransportPayload::ref) { + return JingleTransport::ref(); + } + +private: + JingleTransportPayload::ref candidate; +}; + +class FakeRemoteJingleTransportCandidateSelectorFactory : public RemoteJingleTransportCandidateSelectorFactory { +public: + virtual ~FakeRemoteJingleTransportCandidateSelectorFactory() { + + } + + virtual RemoteJingleTransportCandidateSelector* createCandidateSelector() { + return new FakeRemoteJingleTransportCandidateSelector(); + } +}; + +class FakeLocalJingleTransportCandidateGenerator : public LocalJingleTransportCandidateGenerator { +public: + virtual void generateLocalTransportCandidates(JingleTransportPayload::ref payload) { + JingleS5BTransportPayload::ref payL = make_shared<JingleS5BTransportPayload>(); + payL->setSessionID(payload->getSessionID()); + onLocalTransportCandidatesGenerated(payL); + } + + void emitonLocalTransportCandidatesGenerated(JingleTransportPayload::ref payload) { + onLocalTransportCandidatesGenerated(payload); + } + + virtual bool isActualCandidate(JingleTransportPayload::ref) { + return false; + } + + virtual int getPriority(JingleTransportPayload::ref) { + return 0; + } + + virtual JingleTransport::ref selectTransport(JingleTransportPayload::ref) { + return JingleTransport::ref(); + } +}; + +class FakeLocalJingleTransportCandidateGeneratorFactory : public LocalJingleTransportCandidateGeneratorFactory { +public: + virtual LocalJingleTransportCandidateGenerator* createCandidateGenerator() { + return new FakeLocalJingleTransportCandidateGenerator(); + } +}; + +class IncomingJingleFileTransferTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(IncomingJingleFileTransferTest); + CPPUNIT_TEST(test_AcceptOnyIBBSendsSessionAccept); + CPPUNIT_TEST(test_OnlyIBBTransferReceiveWorks); + CPPUNIT_TEST(test_AcceptFailingS5BFallsBackToIBB); + CPPUNIT_TEST_SUITE_END(); +public: + shared_ptr<IncomingJingleFileTransfer> createTestling() { + JID ourJID("our@jid.org/full"); + return make_shared<IncomingJingleFileTransfer>(ourJID, shared_ptr<JingleSession>(fakeJingleSession), jingleContentPayload, fakeRJTCSF.get(), fakeLJTCF.get(), iqRouter, bytestreamRegistry, bytestreamProxy, timerFactory); + } + + IQ::ref createIBBRequest(IBB::ref ibb, const JID& from, const std::string& id) { + IQ::ref request = IQ::createRequest(IQ::Set, JID("foo@bar.com/baz"), id, ibb); + request->setFrom(from); + return request; + } + + void setUp() { + eventLoop = new DummyEventLoop(); + fakeJingleSession = new FakeJingleSession("foo@bar.com/baz", "mysession"); + jingleContentPayload = make_shared<JingleContentPayload>(); + fakeRJTCSF = make_shared<FakeRemoteJingleTransportCandidateSelectorFactory>(); + fakeLJTCF = make_shared<FakeLocalJingleTransportCandidateGeneratorFactory>(); + stanzaChannel = new DummyStanzaChannel(); + iqRouter = new IQRouter(stanzaChannel); + bytestreamRegistry = new SOCKS5BytestreamRegistry(); + timerFactory = new DummyTimerFactory(); + connectionFactory = new DummyConnectionFactory(eventLoop); + bytestreamProxy = new SOCKS5BytestreamProxy(connectionFactory, timerFactory); + } + + void tearDown() { + delete bytestreamProxy; + delete connectionFactory; + delete timerFactory; + delete bytestreamRegistry; + delete iqRouter; + delete stanzaChannel; + delete eventLoop; + } + + // Tests whether IncomingJingleFileTransfer would accept a IBB only file transfer. + void test_AcceptOnyIBBSendsSessionAccept() { + //1. create your test incoming file transfer + shared_ptr<JingleFileTransferDescription> desc = make_shared<JingleFileTransferDescription>(); + desc->addOffer(StreamInitiationFileInfo()); + jingleContentPayload->addDescription(desc); + JingleIBBTransportPayload::ref tpRef = make_shared<JingleIBBTransportPayload>(); + tpRef->setSessionID("mysession"); + jingleContentPayload->addTransport(tpRef); + + shared_ptr<IncomingJingleFileTransfer> fileTransfer = createTestling(); + + //2. do 'accept' on a dummy writebytestream (you'll have to look if there already is one) + shared_ptr<ByteArrayWriteBytestream> byteStream = make_shared<ByteArrayWriteBytestream>(); + fileTransfer->accept(byteStream); + + // check whether accept has been called + getCall<FakeJingleSession::AcceptCall>(0); + } + + void test_OnlyIBBTransferReceiveWorks() { + //1. create your test incoming file transfer + shared_ptr<JingleFileTransferDescription> desc = make_shared<JingleFileTransferDescription>(); + desc->addOffer(StreamInitiationFileInfo()); + jingleContentPayload->addDescription(desc); + JingleIBBTransportPayload::ref tpRef = make_shared<JingleIBBTransportPayload>(); + tpRef->setSessionID("mysession"); + jingleContentPayload->addTransport(tpRef); + + shared_ptr<IncomingJingleFileTransfer> fileTransfer = createTestling(); + + //2. do 'accept' on a dummy writebytestream (you'll have to look if there already is one) + shared_ptr<ByteArrayWriteBytestream> byteStream = make_shared<ByteArrayWriteBytestream>(); + fileTransfer->accept(byteStream); + + // check whether accept has been called + getCall<FakeJingleSession::AcceptCall>(0); + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBOpen("mysession", 0x10), "foo@bar.com/baz", "id-open")); + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBData("mysession", 0, createByteArray("abc")), "foo@bar.com/baz", "id-a")); + CPPUNIT_ASSERT(createByteArray("abc") == byteStream->getData()); + } + + void test_AcceptFailingS5BFallsBackToIBB() { + //1. create your test incoming file transfer + addFileTransferDescription(); + + // add SOCKS5BytestreamTransportPayload + JingleS5BTransportPayload::ref payLoad = addJingleS5BPayload(); + + shared_ptr<IncomingJingleFileTransfer> fileTransfer = createTestling(); + + //2. do 'accept' on a dummy writebytestream (you'll have to look if there already is one) + shared_ptr<ByteArrayWriteBytestream> byteStream = make_shared<ByteArrayWriteBytestream>(); + fileTransfer->accept(byteStream); + + // check whether accept has been called + FakeJingleSession::AcceptCall acceptCall = getCall<FakeJingleSession::AcceptCall>(0); + CPPUNIT_ASSERT_EQUAL(payLoad->getSessionID(), acceptCall.payload->getSessionID()); + + // check for candidate error + FakeJingleSession::InfoTransportCall infoTransportCall = getCall<FakeJingleSession::InfoTransportCall>(1); + JingleS5BTransportPayload::ref s5bPayload = dynamic_pointer_cast<JingleS5BTransportPayload>(infoTransportCall.payload); + CPPUNIT_ASSERT(s5bPayload->hasCandidateError()); + + // indicate transport replace (Romeo) + fakeJingleSession->onTransportReplaceReceived(getContentID(), addJingleIBBPayload()); + + FakeJingleSession::AcceptTransportCall acceptTransportCall = getCall<FakeJingleSession::AcceptTransportCall>(2); + + // send a bit of data + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBOpen("mysession", 0x10), "foo@bar.com/baz", "id-open")); + stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBData("mysession", 0, createByteArray("abc")), "foo@bar.com/baz", "id-a")); + CPPUNIT_ASSERT(createByteArray("abc") == byteStream->getData()); + } + + void test_S5BTransferReceiveTest() { + addFileTransferDescription(); + JingleS5BTransportPayload::ref payLoad = addJingleS5BPayload(); + } + +private: + void addFileTransferDescription() { + shared_ptr<JingleFileTransferDescription> desc = make_shared<JingleFileTransferDescription>(); + desc->addOffer(StreamInitiationFileInfo()); + jingleContentPayload->addDescription(desc); + } + + shared_ptr<JingleS5BTransportPayload> addJingleS5BPayload() { + JingleS5BTransportPayload::ref payLoad = make_shared<JingleS5BTransportPayload>(); + payLoad->setSessionID("mysession"); + jingleContentPayload->addTransport(payLoad); + return payLoad; + } + + shared_ptr<JingleIBBTransportPayload> addJingleIBBPayload() { + JingleIBBTransportPayload::ref payLoad = make_shared<JingleIBBTransportPayload>(); + payLoad->setSessionID("mysession"); + jingleContentPayload->addTransport(payLoad); + return payLoad; + } + + JingleContentID getContentID() const { + return JingleContentID(jingleContentPayload->getName(), jingleContentPayload->getCreator()); + } + + template <typename T> T getCall(int i) const { + size_t index = static_cast<size_t>(i); + CPPUNIT_ASSERT(index < fakeJingleSession->calledCommands.size()); + T* cmd = boost::get<T>(&fakeJingleSession->calledCommands[index]); + CPPUNIT_ASSERT(cmd); + return *cmd; + } + +private: + EventLoop* eventLoop; + FakeJingleSession* fakeJingleSession; + shared_ptr<JingleContentPayload> jingleContentPayload; + shared_ptr<FakeRemoteJingleTransportCandidateSelectorFactory> fakeRJTCSF; + shared_ptr<FakeLocalJingleTransportCandidateGeneratorFactory> fakeLJTCF; + DummyStanzaChannel* stanzaChannel; + IQRouter* iqRouter; + SOCKS5BytestreamRegistry* bytestreamRegistry; + DummyConnectionFactory* connectionFactory; + SOCKS5BytestreamProxy* bytestreamProxy; + DummyTimerFactory* timerFactory; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(IncomingJingleFileTransferTest); diff --git a/Swiften/FileTransfer/UnitTest/OutgoingJingleFileTransferTest.cpp b/Swiften/FileTransfer/UnitTest/OutgoingJingleFileTransferTest.cpp new file mode 100644 index 0000000..582c3e5 --- /dev/null +++ b/Swiften/FileTransfer/UnitTest/OutgoingJingleFileTransferTest.cpp @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include <boost/bind.hpp> +#include <boost/optional.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/FileTransfer/OutgoingJingleFileTransfer.h> +#include <Swiften/Jingle/FakeJingleSession.h> +#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelectorFactory.h> +#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.h> +#include <Swiften/FileTransfer/LocalJingleTransportCandidateGeneratorFactory.h> +#include <Swiften/FileTransfer/LocalJingleTransportCandidateGenerator.h> +#include <Swiften/Queries/IQRouter.h> +#include <Swiften/Client/DummyStanzaChannel.h> +#include <Swiften/FileTransfer/ByteArrayReadBytestream.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamProxy.h> + +#include <Swiften/Elements/JingleIBBTransportPayload.h> +#include <Swiften/Elements/JingleS5BTransportPayload.h> +#include <Swiften/Elements/JingleFileTransferDescription.h> +#include <Swiften/Elements/IBB.h> +#include <Swiften/Base/ByteArray.h> +#include <Swiften/Base/IDGenerator.h> +#include <Swiften/EventLoop/DummyEventLoop.h> +#include <Swiften/Network/PlatformNATTraversalWorker.h> +#include <Swiften/Network/DummyTimerFactory.h> +#include <Swiften/Network/DummyConnection.h> +#include <Swiften/Network/ConnectionFactory.h> +#include <Swiften/Network/DummyConnectionFactory.h> + +#include <Swiften/Base/Log.h> + +#include <iostream> + +using namespace Swift; + +class OFakeRemoteJingleTransportCandidateSelector : public RemoteJingleTransportCandidateSelector { + void addRemoteTransportCandidates(JingleTransportPayload::ref cand) { + candidate = cand; + } + + void selectCandidate() { + JingleS5BTransportPayload::ref payload = boost::make_shared<JingleS5BTransportPayload>(); + payload->setCandidateError(true); + payload->setSessionID(candidate->getSessionID()); + onRemoteTransportCandidateSelectFinished(payload); + } + + void setMinimumPriority(int) { + + } + + bool isActualCandidate(JingleTransportPayload::ref) { + return false; + } + + int getPriority(JingleTransportPayload::ref) { + return 0; + } + + JingleTransport::ref selectTransport(JingleTransportPayload::ref) { + return JingleTransport::ref(); + } + +private: + JingleTransportPayload::ref candidate; +}; + +class OFakeRemoteJingleTransportCandidateSelectorFactory : public RemoteJingleTransportCandidateSelectorFactory { +public: + virtual ~OFakeRemoteJingleTransportCandidateSelectorFactory() { + + } + + virtual RemoteJingleTransportCandidateSelector* createCandidateSelector() { + return new OFakeRemoteJingleTransportCandidateSelector(); + } +}; + +class OFakeLocalJingleTransportCandidateGenerator : public LocalJingleTransportCandidateGenerator { +public: + virtual void generateLocalTransportCandidates(JingleTransportPayload::ref /* payload */) { + //JingleTransportPayload::ref payL = make_shared<JingleTransportPayload>(); + //payL->setSessionID(payload->getSessionID()); + JingleS5BTransportPayload::ref payL = boost::make_shared<JingleS5BTransportPayload>(); + + onLocalTransportCandidatesGenerated(payL); + } + + void emitonLocalTransportCandidatesGenerated(JingleTransportPayload::ref payload) { + onLocalTransportCandidatesGenerated(payload); + } + + virtual bool isActualCandidate(JingleTransportPayload::ref) { + return false; + } + + virtual int getPriority(JingleTransportPayload::ref) { + return 0; + } + + virtual JingleTransport::ref selectTransport(JingleTransportPayload::ref) { + return JingleTransport::ref(); + } +}; + +class OFakeLocalJingleTransportCandidateGeneratorFactory : public LocalJingleTransportCandidateGeneratorFactory { +public: + virtual LocalJingleTransportCandidateGenerator* createCandidateGenerator() { + return new OFakeLocalJingleTransportCandidateGenerator(); + } +}; + +class OutgoingJingleFileTransferTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(OutgoingJingleFileTransferTest); + CPPUNIT_TEST(test_SendSessionInitiateOnStart); + CPPUNIT_TEST(test_IBBStartsAfterSendingSessionAccept); + CPPUNIT_TEST(test_ReceiveSessionTerminateAfterSessionInitiate); + CPPUNIT_TEST_SUITE_END(); + + class FTStatusHelper { + public: + bool finishedCalled; + FileTransferError::Type error; + void handleFileTransferFinished(boost::optional<FileTransferError> error) { + finishedCalled = true; + if (error.is_initialized()) this->error = error.get().getType(); + } + }; +public: + + boost::shared_ptr<OutgoingJingleFileTransfer> createTestling() { + JID to("test@foo.com/bla"); + StreamInitiationFileInfo fileInfo; + fileInfo.setDescription("some file"); + fileInfo.setName("test.bin"); + fileInfo.setHash("asdjasdas"); + fileInfo.setSize(1024 * 1024); + return boost::shared_ptr<OutgoingJingleFileTransfer>(new OutgoingJingleFileTransfer(boost::shared_ptr<JingleSession>(fakeJingleSession), fakeRJTCSF.get(), fakeLJTCF.get(), iqRouter, idGen, to, stream, fileInfo, s5bRegistry, s5bProxy)); + } + + IQ::ref createIBBRequest(IBB::ref ibb, const JID& from, const std::string& id) { + IQ::ref request = IQ::createRequest(IQ::Set, JID("foo@bar.com/baz"), id, ibb); + request->setFrom(from); + return request; + } + + void setUp() { + fakeJingleSession = new FakeJingleSession("foo@bar.com/baz", "mysession"); + jingleContentPayload = boost::make_shared<JingleContentPayload>(); + fakeRJTCSF = boost::make_shared<OFakeRemoteJingleTransportCandidateSelectorFactory>(); + fakeLJTCF = boost::make_shared<OFakeLocalJingleTransportCandidateGeneratorFactory>(); + stanzaChannel = new DummyStanzaChannel(); + iqRouter = new IQRouter(stanzaChannel); + eventLoop = new DummyEventLoop(); + timerFactory = new DummyTimerFactory(); + connectionFactory = new DummyConnectionFactory(eventLoop); + s5bRegistry = new SOCKS5BytestreamRegistry(); + s5bProxy = new SOCKS5BytestreamProxy(connectionFactory, timerFactory); + + data.clear(); + for (int n=0; n < 1024 * 1024; ++n) { + data.push_back(34); + } + + stream = boost::make_shared<ByteArrayReadBytestream>(data); + + idGen = new IDGenerator(); + } + + void tearDown() { + delete idGen; + delete s5bRegistry; + delete connectionFactory; + delete timerFactory; + delete eventLoop; + delete iqRouter; + delete stanzaChannel; + } + + + void test_SendSessionInitiateOnStart() { + boost::shared_ptr<OutgoingJingleFileTransfer> transfer = createTestling(); + transfer->start(); + FakeJingleSession::InitiateCall call = getCall<FakeJingleSession::InitiateCall>(0); + JingleFileTransferDescription::ref description = boost::dynamic_pointer_cast<JingleFileTransferDescription>(call.description); + CPPUNIT_ASSERT(description); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), description->getOffers().size()); + CPPUNIT_ASSERT(static_cast<size_t>(1048576) == description->getOffers()[0].getSize()); + + JingleS5BTransportPayload::ref transport = boost::dynamic_pointer_cast<JingleS5BTransportPayload>(call.payload); + CPPUNIT_ASSERT(transport); + } + + void test_IBBStartsAfterSendingSessionAccept() { + boost::shared_ptr<OutgoingJingleFileTransfer> transfer = createTestling(); + transfer->start(); + + FakeJingleSession::InitiateCall call = getCall<FakeJingleSession::InitiateCall>(0); + // FIXME: we initiate with SOCSK5 now and not IBB, needs to be fixed. + /* + fakeJingleSession->onSessionAcceptReceived(call.id, call.description, call.payload); + IQ::ref iqOpenStanza = stanzaChannel->getStanzaAtIndex<IQ>(0); + CPPUNIT_ASSERT(iqOpenStanza); + */ + } + + void test_ReceiveSessionTerminateAfterSessionInitiate() { + boost::shared_ptr<OutgoingJingleFileTransfer> transfer = createTestling(); + transfer->start(); + + getCall<FakeJingleSession::InitiateCall>(0); + + FTStatusHelper helper; + helper.finishedCalled = false; + transfer->onFinished.connect(bind(&FTStatusHelper::handleFileTransferFinished, &helper, _1)); + fakeJingleSession->onSessionTerminateReceived(JinglePayload::Reason(JinglePayload::Reason::Busy)); + CPPUNIT_ASSERT_EQUAL(true, helper.finishedCalled); + CPPUNIT_ASSERT(FileTransferError::PeerError == helper.error); + } + + +//TODO: some more testcases + +private: + void addFileTransferDescription() { + boost::shared_ptr<JingleFileTransferDescription> desc = boost::make_shared<JingleFileTransferDescription>(); + desc->addOffer(StreamInitiationFileInfo()); + jingleContentPayload->addDescription(desc); + } + + boost::shared_ptr<JingleS5BTransportPayload> addJingleS5BPayload() { + JingleS5BTransportPayload::ref payLoad = boost::make_shared<JingleS5BTransportPayload>(); + payLoad->setSessionID("mysession"); + jingleContentPayload->addTransport(payLoad); + return payLoad; + } + + boost::shared_ptr<JingleIBBTransportPayload> addJingleIBBPayload() { + JingleIBBTransportPayload::ref payLoad = boost::make_shared<JingleIBBTransportPayload>(); + payLoad->setSessionID("mysession"); + jingleContentPayload->addTransport(payLoad); + return payLoad; + } + + JingleContentID getContentID() const { + return JingleContentID(jingleContentPayload->getName(), jingleContentPayload->getCreator()); + } + + template <typename T> T getCall(int i) const { + size_t index = static_cast<size_t>(i); + CPPUNIT_ASSERT(index < fakeJingleSession->calledCommands.size()); + T* cmd = boost::get<T>(&fakeJingleSession->calledCommands[index]); + CPPUNIT_ASSERT(cmd); + return *cmd; + } + +private: + std::vector<unsigned char> data; + boost::shared_ptr<ByteArrayReadBytestream> stream; + FakeJingleSession* fakeJingleSession; + boost::shared_ptr<JingleContentPayload> jingleContentPayload; + boost::shared_ptr<OFakeRemoteJingleTransportCandidateSelectorFactory> fakeRJTCSF; + boost::shared_ptr<OFakeLocalJingleTransportCandidateGeneratorFactory> fakeLJTCF; + DummyStanzaChannel* stanzaChannel; + IQRouter* iqRouter; + IDGenerator* idGen; + EventLoop *eventLoop; + SOCKS5BytestreamRegistry* s5bRegistry; + SOCKS5BytestreamProxy* s5bProxy; + DummyTimerFactory* timerFactory; + DummyConnectionFactory* connectionFactory; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(OutgoingJingleFileTransferTest); diff --git a/Swiften/FileTransfer/UnitTest/SOCKS5BytestreamClientSessionTest.cpp b/Swiften/FileTransfer/UnitTest/SOCKS5BytestreamClientSessionTest.cpp new file mode 100644 index 0000000..35580bf --- /dev/null +++ b/Swiften/FileTransfer/UnitTest/SOCKS5BytestreamClientSessionTest.cpp @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swiften/Base/ByteArray.h> +#include <QA/Checker/IO.h> + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include <boost/bind.hpp> +#include <boost/random/mersenne_twister.hpp> +#include <boost/random/uniform_int.hpp> +#include <boost/random/variate_generator.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/Algorithm.h> +#include <Swiften/Base/ByteArray.h> +#include <Swiften/Base/Concat.h> +#include <Swiften/Base/Log.h> +#include <Swiften/Base/StartStopper.h> +#include <Swiften/EventLoop/DummyEventLoop.h> +#include <Swiften/FileTransfer/ByteArrayReadBytestream.h> +#include <Swiften/FileTransfer/ByteArrayWriteBytestream.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamClientSession.h> +#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Network/DummyConnection.h> +#include <Swiften/Network/DummyTimerFactory.h> +#include <Swiften/StringCodecs/Hexify.h> + +using namespace Swift; + +boost::mt19937 randomGen; + +class SOCKS5BytestreamClientSessionTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(SOCKS5BytestreamClientSessionTest); + CPPUNIT_TEST(testForSessionReady); + CPPUNIT_TEST(testErrorHandlingHello); + CPPUNIT_TEST(testErrorHandlingRequest); + CPPUNIT_TEST(testWriteBytestream); + CPPUNIT_TEST(testReadBytestream); + CPPUNIT_TEST_SUITE_END(); + + const HostAddressPort destinationAddressPort; + const std::string destination; + +public: + SOCKS5BytestreamClientSessionTest() : destinationAddressPort(HostAddressPort(HostAddress("127.0.0.1"), 8888)), + destination(SOCKS5BytestreamRegistry::getHostname("foo", JID("requester@example.com/test"), JID("target@example.com/test"))), eventLoop(NULL), timerFactory(NULL) { } + + void setUp() { + randomGen.seed(time(NULL)); + eventLoop = new DummyEventLoop(); + timerFactory = new DummyTimerFactory(); + connection = boost::make_shared<MockeryConnection>(failingPorts, true, eventLoop); + //connection->onDataSent.connect(boost::bind(&SOCKS5BytestreamServerSessionTest::handleDataWritten, this, _1)); + //stream1 = boost::shared_ptr<ByteArrayReadBytestream>(new ByteArrayReadBytestream(createByteArray("abcdefg"))); +// connection->onDataRead.connect(boost::bind(&SOCKS5BytestreamClientSessionTest::handleDataRead, this, _1)); + } + + void tearDown() { + //connection.reset(); + delete timerFactory; + delete eventLoop; + } + + void testForSessionReady() { + TestHelper helper; + connection->onDataSent.connect(boost::bind(&TestHelper::handleConnectionDataWritten, &helper, _1)); + + SOCKS5BytestreamClientSession::ref clientSession = boost::make_shared<SOCKS5BytestreamClientSession>(connection, destinationAddressPort, destination, timerFactory); + clientSession->onSessionReady.connect(boost::bind(&TestHelper::handleSessionReady, &helper, _1)); + + clientSession->start(); + eventLoop->processEvents(); + CPPUNIT_ASSERT(createByteArray("\x05\x01\x00", 3) == helper.unprocessedInput); + + helper.unprocessedInput.clear(); + serverRespondHelloOK(); + eventLoop->processEvents(); + CPPUNIT_ASSERT_EQUAL(createByteArray("\x05\x01\x00\x03", 4), createByteArray(&helper.unprocessedInput[0], 4)); + CPPUNIT_ASSERT_EQUAL(createByteArray(destination.size()), createByteArray(helper.unprocessedInput[4])); + CPPUNIT_ASSERT_EQUAL(createByteArray(destination), createByteArray(&helper.unprocessedInput[5], destination.size())); + CPPUNIT_ASSERT_EQUAL(createByteArray("\x00", 1), createByteArray(&helper.unprocessedInput[5 + destination.size()], 1)); + + helper.unprocessedInput.clear(); + serverRespondRequestOK(); + eventLoop->processEvents(); + CPPUNIT_ASSERT_EQUAL(true, helper.sessionReadyCalled); + CPPUNIT_ASSERT_EQUAL(false, helper.sessionReadyError); + } + + void testErrorHandlingHello() { + TestHelper helper; + connection->onDataSent.connect(boost::bind(&TestHelper::handleConnectionDataWritten, &helper, _1)); + + SOCKS5BytestreamClientSession::ref clientSession = boost::make_shared<SOCKS5BytestreamClientSession>(connection, destinationAddressPort, destination, timerFactory); + clientSession->onSessionReady.connect(boost::bind(&TestHelper::handleSessionReady, &helper, _1)); + + clientSession->start(); + eventLoop->processEvents(); + CPPUNIT_ASSERT_EQUAL(createByteArray("\x05\x01\x00", 3), helper.unprocessedInput); + + helper.unprocessedInput.clear(); + serverRespondHelloAuthFail(); + eventLoop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(true, helper.sessionReadyCalled); + CPPUNIT_ASSERT_EQUAL(true, helper.sessionReadyError); + CPPUNIT_ASSERT_EQUAL(true, connection->disconnectCalled); + } + + void testErrorHandlingRequest() { + TestHelper helper; + connection->onDataSent.connect(boost::bind(&TestHelper::handleConnectionDataWritten, &helper, _1)); + + SOCKS5BytestreamClientSession::ref clientSession = boost::make_shared<SOCKS5BytestreamClientSession>(connection, destinationAddressPort, destination, timerFactory); + clientSession->onSessionReady.connect(boost::bind(&TestHelper::handleSessionReady, &helper, _1)); + + clientSession->start(); + eventLoop->processEvents(); + CPPUNIT_ASSERT_EQUAL(createByteArray("\x05\x01\x00", 3), helper.unprocessedInput); + + helper.unprocessedInput.clear(); + serverRespondHelloOK(); + eventLoop->processEvents(); + CPPUNIT_ASSERT_EQUAL(createByteArray("\x05\x01\x00\x03", 4), createByteArray(&helper.unprocessedInput[0], 4)); + CPPUNIT_ASSERT_EQUAL(createByteArray(destination.size()), createByteArray(helper.unprocessedInput[4])); + CPPUNIT_ASSERT_EQUAL(createByteArray(destination), createByteArray(&helper.unprocessedInput[5], destination.size())); + CPPUNIT_ASSERT_EQUAL(createByteArray("\x00", 1), createByteArray(&helper.unprocessedInput[5 + destination.size()], 1)); + + helper.unprocessedInput.clear(); + serverRespondRequestFail(); + eventLoop->processEvents(); + CPPUNIT_ASSERT_EQUAL(true, helper.sessionReadyCalled); + CPPUNIT_ASSERT_EQUAL(true, helper.sessionReadyError); + CPPUNIT_ASSERT_EQUAL(true, connection->disconnectCalled); + } + + void testWriteBytestream() { + TestHelper helper; + connection->onDataSent.connect(boost::bind(&TestHelper::handleConnectionDataWritten, &helper, _1)); + + SOCKS5BytestreamClientSession::ref clientSession = boost::make_shared<SOCKS5BytestreamClientSession>(connection, destinationAddressPort, destination, timerFactory); + clientSession->onSessionReady.connect(boost::bind(&TestHelper::handleSessionReady, &helper, _1)); + + clientSession->start(); + eventLoop->processEvents(); + + helper.unprocessedInput.clear(); + serverRespondHelloOK(); + eventLoop->processEvents(); + + helper.unprocessedInput.clear(); + serverRespondRequestOK(); + eventLoop->processEvents(); + CPPUNIT_ASSERT_EQUAL(true, helper.sessionReadyCalled); + CPPUNIT_ASSERT_EQUAL(false, helper.sessionReadyError); + + boost::shared_ptr<ByteArrayWriteBytestream> output = boost::make_shared<ByteArrayWriteBytestream>(); + clientSession->startReceiving(output); + + ByteArray transferData = generateRandomByteArray(1024); + connection->onDataRead(createSafeByteArray(transferData.data(), transferData.size())); + CPPUNIT_ASSERT_EQUAL(transferData, output->getData()); + } + + void testReadBytestream() { + TestHelper helper; + connection->onDataSent.connect(boost::bind(&TestHelper::handleConnectionDataWritten, &helper, _1)); + + SOCKS5BytestreamClientSession::ref clientSession = boost::make_shared<SOCKS5BytestreamClientSession>(connection, destinationAddressPort, destination, timerFactory); + clientSession->onSessionReady.connect(boost::bind(&TestHelper::handleSessionReady, &helper, _1)); + + clientSession->start(); + eventLoop->processEvents(); + + helper.unprocessedInput.clear(); + serverRespondHelloOK(); + eventLoop->processEvents(); + + helper.unprocessedInput.clear(); + serverRespondRequestOK(); + eventLoop->processEvents(); + CPPUNIT_ASSERT_EQUAL(true, helper.sessionReadyCalled); + CPPUNIT_ASSERT_EQUAL(false, helper.sessionReadyError); + + helper.unprocessedInput.clear(); + ByteArray transferData = generateRandomByteArray(1024 * 1024); + boost::shared_ptr<ByteArrayReadBytestream> input = boost::make_shared<ByteArrayReadBytestream>(transferData); + clientSession->startSending(input); + eventLoop->processEvents(); + + CPPUNIT_ASSERT_EQUAL(createByteArray(transferData.data(), transferData.size()), helper.unprocessedInput); + } + + + +private: + static ByteArray generateRandomByteArray(size_t len) { + boost::uniform_int<> dist(0, 255); + boost::variate_generator<boost::mt19937&, boost::uniform_int<> > randomByte(randomGen, dist); + ByteArray result(len); + for (size_t i=0; i < len; ++i ) { + result[i] = randomByte(); + } + return result; + } + + // Server responses + void serverRespondHelloOK() { + connection->onDataRead(createSafeByteArray("\x05\00", 2)); + } + + void serverRespondHelloAuthFail() { + connection->onDataRead(createSafeByteArray("\x05\xFF", 2)); + } + + void serverRespondRequestOK() { + SafeByteArray dataToSend = createSafeByteArray("\x05\x00\x00\x03", 4); + append(dataToSend, createSafeByteArray(destination.size())); + append(dataToSend, createSafeByteArray(destination)); + append(dataToSend, createSafeByteArray("\x00", 1)); + connection->onDataRead(dataToSend); + } + + void serverRespondRequestFail() { + SafeByteArray correctData = createSafeByteArray("\x05\x00\x00\x03", 4); + append(correctData, createSafeByteArray(destination.size())); + append(correctData, createSafeByteArray(destination)); + append(correctData, createSafeByteArray("\x00", 1)); + + SafeByteArray dataToSend; + //ByteArray failingData = Hexify::unhexify("8417947d1d305c72c11520ea7d2c6e787396705e72c312c6ccc3f66613d7cae1b91b7ab48e8b59a17d559c15fb51"); + //append(dataToSend, failingData); + //SWIFT_LOG(debug) << "hexed: " << Hexify::hexify(failingData) << std::endl; + do { + ByteArray rndArray = generateRandomByteArray(correctData.size()); + dataToSend = createSafeByteArray(rndArray.data(), rndArray.size()); + } while (dataToSend == correctData); + connection->onDataRead(dataToSend); + } + +private: + struct TestHelper { + TestHelper() : sessionReadyCalled(false), sessionReadyError(false) {} + ByteArray unprocessedInput; + bool sessionReadyCalled; + bool sessionReadyError; + + void handleConnectionDataWritten(const SafeByteArray& data) { + append(unprocessedInput, data); + //SWIFT_LOG(debug) << "unprocessedInput (" << unprocessedInput.size() << "): " << Hexify::hexify(unprocessedInput) << std::endl; + } + + void handleSessionReady(bool error) { + sessionReadyCalled = true; + sessionReadyError = error; + } + }; + + +private: + struct MockeryConnection : public Connection, public EventOwner, public boost::enable_shared_from_this<MockeryConnection> { + public: + MockeryConnection(const std::vector<HostAddressPort>& failingPorts, bool isResponsive, EventLoop* eventLoop) : eventLoop(eventLoop), failingPorts(failingPorts), isResponsive(isResponsive), disconnectCalled(false) {} + + void listen() { assert(false); } + void connect(const HostAddressPort& address) { + hostAddressPort = address; + if (isResponsive) { + bool fail = std::find(failingPorts.begin(), failingPorts.end(), address) != failingPorts.end(); + eventLoop->postEvent(boost::bind(boost::ref(onConnectFinished), fail)); + } + } + + HostAddressPort getLocalAddress() const { return HostAddressPort(); } + void disconnect() { + disconnectCalled = true; + } + + void write(const SafeByteArray& data) { + eventLoop->postEvent(boost::ref(onDataWritten), shared_from_this()); + onDataSent(data); + } + + boost::signal<void (const SafeByteArray&)> onDataSent; + + EventLoop* eventLoop; + boost::optional<HostAddressPort> hostAddressPort; + std::vector<HostAddressPort> failingPorts; + bool isResponsive; + bool disconnectCalled; + }; + +private: + DummyEventLoop* eventLoop; + DummyTimerFactory* timerFactory; + boost::shared_ptr<MockeryConnection> connection; + const std::vector<HostAddressPort> failingPorts; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SOCKS5BytestreamClientSessionTest); diff --git a/Swiften/FileTransfer/UnitTest/SOCKS5BytestreamServerSessionTest.cpp b/Swiften/FileTransfer/UnitTest/SOCKS5BytestreamServerSessionTest.cpp index 06bc98f..cd480f0 100644 --- a/Swiften/FileTransfer/UnitTest/SOCKS5BytestreamServerSessionTest.cpp +++ b/Swiften/FileTransfer/UnitTest/SOCKS5BytestreamServerSessionTest.cpp @@ -34,6 +34,7 @@ class SOCKS5BytestreamServerSessionTest : public CppUnit::TestFixture { void setUp() { receivedDataChunks = 0; eventLoop = new DummyEventLoop(); + bytestreams = new SOCKS5BytestreamRegistry(); connection = boost::shared_ptr<DummyConnection>(new DummyConnection(eventLoop)); connection->onDataSent.connect(boost::bind(&SOCKS5BytestreamServerSessionTest::handleDataWritten, this, _1)); stream1 = boost::shared_ptr<ByteArrayReadBytestream>(new ByteArrayReadBytestream(createByteArray("abcdefg"))); @@ -41,6 +42,7 @@ class SOCKS5BytestreamServerSessionTest : public CppUnit::TestFixture { void tearDown() { connection.reset(); + delete bytestreams; delete eventLoop; } @@ -67,7 +69,7 @@ class SOCKS5BytestreamServerSessionTest : public CppUnit::TestFixture { void testRequest() { boost::shared_ptr<SOCKS5BytestreamServerSession> testling(createSession()); StartStopper<SOCKS5BytestreamServerSession> stopper(testling.get()); - bytestreams.addBytestream("abcdef", stream1); + bytestreams->addReadBytestream("abcdef", stream1); authenticate(); ByteArray hostname(createByteArray("abcdef")); @@ -88,10 +90,11 @@ class SOCKS5BytestreamServerSessionTest : public CppUnit::TestFixture { void testReceiveData() { boost::shared_ptr<SOCKS5BytestreamServerSession> testling(createSession()); StartStopper<SOCKS5BytestreamServerSession> stopper(testling.get()); - bytestreams.addBytestream("abcdef", stream1); + bytestreams->addReadBytestream("abcdef", stream1); authenticate(); request("abcdef"); eventLoop->processEvents(); + testling->startTransfer(); skipHeader("abcdef"); CPPUNIT_ASSERT(createByteArray("abcdefg") == receivedData); @@ -102,11 +105,12 @@ class SOCKS5BytestreamServerSessionTest : public CppUnit::TestFixture { boost::shared_ptr<SOCKS5BytestreamServerSession> testling(createSession()); testling->setChunkSize(3); StartStopper<SOCKS5BytestreamServerSession> stopper(testling.get()); - bytestreams.addBytestream("abcdef", stream1); + bytestreams->addReadBytestream("abcdef", stream1); authenticate(); request("abcdef"); eventLoop->processEvents(); - + testling->startTransfer(); + eventLoop->processEvents(); skipHeader("abcdef"); CPPUNIT_ASSERT(createByteArray("abcdefg") == receivedData); CPPUNIT_ASSERT_EQUAL(4, receivedDataChunks); @@ -141,13 +145,13 @@ class SOCKS5BytestreamServerSessionTest : public CppUnit::TestFixture { private: SOCKS5BytestreamServerSession* createSession() { - SOCKS5BytestreamServerSession* session = new SOCKS5BytestreamServerSession(connection, &bytestreams); + SOCKS5BytestreamServerSession* session = new SOCKS5BytestreamServerSession(connection, bytestreams); return session; } private: DummyEventLoop* eventLoop; - SOCKS5BytestreamRegistry bytestreams; + SOCKS5BytestreamRegistry* bytestreams; boost::shared_ptr<DummyConnection> connection; std::vector<unsigned char> receivedData; int receivedDataChunks; diff --git a/Swiften/FileTransfer/WriteBytestream.h b/Swiften/FileTransfer/WriteBytestream.h index c27aeff..fb6f2f1 100644 --- a/Swiften/FileTransfer/WriteBytestream.h +++ b/Swiften/FileTransfer/WriteBytestream.h @@ -9,6 +9,8 @@ #include <boost/shared_ptr.hpp> #include <vector> +#include <Swiften/Base/boost_bsignals.h> + namespace Swift { class WriteBytestream { public: @@ -17,5 +19,7 @@ namespace Swift { virtual ~WriteBytestream(); virtual void write(const std::vector<unsigned char>&) = 0; + + boost::signal<void (const std::vector<unsigned char>&)> onWrite; }; } diff --git a/Swiften/Jingle/FakeJingleSession.cpp b/Swiften/Jingle/FakeJingleSession.cpp new file mode 100644 index 0000000..82ec631 --- /dev/null +++ b/Swiften/Jingle/FakeJingleSession.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swiften/Jingle/FakeJingleSession.h> + +#include <boost/smart_ptr/make_shared.hpp> + +namespace Swift { + +FakeJingleSession::FakeJingleSession(const JID& initiator, const std::string& id) : JingleSession(initiator, id) { + +} + +FakeJingleSession::~FakeJingleSession() { +} + +void FakeJingleSession::sendInitiate(const JingleContentID& id, JingleDescription::ref desc, JingleTransportPayload::ref payload) { + calledCommands.push_back(InitiateCall(id, desc, payload)); +} + +void FakeJingleSession::sendTerminate(JinglePayload::Reason::Type reason) { + calledCommands.push_back(TerminateCall(reason)); +} + +void FakeJingleSession::sendInfo(boost::shared_ptr<Payload> payload) { + calledCommands.push_back(InfoCall(payload)); +} + +void FakeJingleSession::sendAccept(const JingleContentID& id, JingleDescription::ref desc, JingleTransportPayload::ref payload) { + calledCommands.push_back(AcceptCall(id, desc, payload)); +} + +void FakeJingleSession::sendTransportInfo(const JingleContentID& id, JingleTransportPayload::ref payload) { + calledCommands.push_back(InfoTransportCall(id, payload)); +} + +void FakeJingleSession::sendTransportAccept(const JingleContentID& id, JingleTransportPayload::ref payload) { + calledCommands.push_back(AcceptTransportCall(id, payload)); +} + +void FakeJingleSession::sendTransportReject(const JingleContentID& id, JingleTransportPayload::ref payload) { + calledCommands.push_back(RejectTransportCall(id, payload)); +} + +void FakeJingleSession::sendTransportReplace(const JingleContentID& id, JingleTransportPayload::ref payload) { + calledCommands.push_back(ReplaceTransportCall(id, payload)); +} + +} diff --git a/Swiften/Jingle/FakeJingleSession.h b/Swiften/Jingle/FakeJingleSession.h new file mode 100644 index 0000000..fd3d7b7 --- /dev/null +++ b/Swiften/Jingle/FakeJingleSession.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <string> +#include <vector> +#include <boost/variant.hpp> + +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/JinglePayload.h> +#include <Swiften/Jingle/JingleSession.h> +#include <Swiften/Jingle/JingleContentID.h> + +namespace Swift { + class JingleContentID; + + class FakeJingleSession : public JingleSession { + public: + struct InitiateCall { + InitiateCall(JingleContentID contentId, JingleDescription::ref desc, JingleTransportPayload::ref payL) : id(contentId), description(desc), payload(payL) {} + JingleContentID id; + JingleDescription::ref description; + JingleTransportPayload::ref payload; + }; + + struct TerminateCall { + TerminateCall(JinglePayload::Reason::Type r) : reason(r) {} + JinglePayload::Reason::Type reason; + }; + + struct InfoCall { + InfoCall(boost::shared_ptr<Payload> info) : payload(info) {} + boost::shared_ptr<Payload> payload; + }; + + struct AcceptCall { + AcceptCall(JingleContentID contentId, JingleDescription::ref desc, JingleTransportPayload::ref payL) : id(contentId), description(desc), payload(payL) {} + JingleContentID id; + JingleDescription::ref description; + JingleTransportPayload::ref payload; + }; + + struct InfoTransportCall { + InfoTransportCall(JingleContentID contentId, JingleTransportPayload::ref payL) : id(contentId), payload(payL) {} + JingleContentID id; + JingleTransportPayload::ref payload; + }; + + struct AcceptTransportCall { + AcceptTransportCall(JingleContentID contentId, JingleTransportPayload::ref payL) : id(contentId), payload(payL) {} + JingleContentID id; + JingleTransportPayload::ref payload; + }; + + struct RejectTransportCall { + RejectTransportCall(JingleContentID contentId, JingleTransportPayload::ref payL) : id(contentId), payload(payL) {} + JingleContentID id; + JingleTransportPayload::ref payload; + }; + + struct ReplaceTransportCall { + ReplaceTransportCall(JingleContentID contentId, JingleTransportPayload::ref payL) : id(contentId), payload(payL) {} + JingleContentID id; + JingleTransportPayload::ref payload; + }; + + typedef boost::variant<InitiateCall, TerminateCall, AcceptCall, InfoCall, InfoTransportCall, AcceptTransportCall, RejectTransportCall, ReplaceTransportCall> Command; + + public: + typedef boost::shared_ptr<FakeJingleSession> ref; + + FakeJingleSession(const JID& initiator, const std::string& id); + virtual ~FakeJingleSession(); + + virtual void sendInitiate(const JingleContentID&, JingleDescription::ref, JingleTransportPayload::ref); + virtual void sendTerminate(JinglePayload::Reason::Type reason); + virtual void sendInfo(boost::shared_ptr<Payload>); + virtual void sendAccept(const JingleContentID&, JingleDescription::ref, JingleTransportPayload::ref = JingleTransportPayload::ref()); + virtual void sendTransportInfo(const JingleContentID&, JingleTransportPayload::ref); + virtual void sendTransportAccept(const JingleContentID&, JingleTransportPayload::ref); + virtual void sendTransportReject(const JingleContentID&, JingleTransportPayload::ref); + virtual void sendTransportReplace(const JingleContentID&, JingleTransportPayload::ref); + + public: + std::vector<Command> calledCommands; + }; +} diff --git a/Swiften/Jingle/JingleContentID.h b/Swiften/Jingle/JingleContentID.h index 8d75581..fc0cc8f 100644 --- a/Swiften/Jingle/JingleContentID.h +++ b/Swiften/Jingle/JingleContentID.h @@ -15,6 +15,14 @@ namespace Swift { public: JingleContentID(const std::string& name, JingleContentPayload::Creator creator) : name(name), creator(creator) { } + + const std::string getName() const { + return this->name; + } + + JingleContentPayload::Creator getCreator() const { + return this->creator; + } private: std::string name; diff --git a/Swiften/Jingle/JingleResponder.cpp b/Swiften/Jingle/JingleResponder.cpp index 198f9a2..63f108e 100644 --- a/Swiften/Jingle/JingleResponder.cpp +++ b/Swiften/Jingle/JingleResponder.cpp @@ -11,9 +11,14 @@ #include <Swiften/Jingle/JingleSessionManager.h> #include <Swiften/Jingle/JingleSessionImpl.h> +#include <Swiften/Base/Log.h> + namespace Swift { -JingleResponder::JingleResponder(JingleSessionManager* sessionManager, IQRouter* router) : SetResponder<JinglePayload>(router), sessionManager(sessionManager) { +JingleResponder::JingleResponder(JingleSessionManager* sessionManager, IQRouter* router) : SetResponder<JinglePayload>(router), sessionManager(sessionManager), router(router) { +} + +JingleResponder::~JingleResponder() { } bool JingleResponder::handleSetRequest(const JID& from, const JID&, const std::string& id, boost::shared_ptr<JinglePayload> payload) { @@ -24,17 +29,29 @@ bool JingleResponder::handleSetRequest(const JID& from, const JID&, const std::s } else { sendResponse(from, id, boost::shared_ptr<JinglePayload>()); - JingleSessionImpl::ref session = boost::make_shared<JingleSessionImpl>(payload->getInitiator(), payload->getSessionID()); - sessionManager->handleIncomingSession(from, session, payload->getContents()); + if (!payload->getInitiator().isBare()) { + JingleSessionImpl::ref session = boost::make_shared<JingleSessionImpl>(payload->getInitiator(), from, payload->getSessionID(), router); + sessionManager->handleIncomingSession(from, session, payload->getContents()); + } else { + SWIFT_LOG(debug) << "Unable to create Jingle session due to initiator not being a full JID." << std::endl; + } } } else { - JingleSessionImpl::ref session = sessionManager->getSession(from, payload->getSessionID()); + JingleSessionImpl::ref session; + if (payload->getInitiator().isValid()) { + SWIFT_LOG(debug) << "Lookup session by initiator." << std::endl; + session = sessionManager->getSession(payload->getInitiator(), payload->getSessionID()); + } else { + SWIFT_LOG(debug) << "Lookup session by from attribute." << std::endl; + session = sessionManager->getSession(from, payload->getSessionID()); + } if (session) { session->handleIncomingAction(payload); sendResponse(from, id, boost::shared_ptr<JinglePayload>()); } else { + std::cerr << "WARN: Didn't find jingle session!" << std::endl; // TODO: Add jingle-specific error sendError(from, id, ErrorPayload::ItemNotFound, ErrorPayload::Cancel); } diff --git a/Swiften/Jingle/JingleResponder.h b/Swiften/Jingle/JingleResponder.h index 6e1965c..6f4d688 100644 --- a/Swiften/Jingle/JingleResponder.h +++ b/Swiften/Jingle/JingleResponder.h @@ -16,11 +16,12 @@ namespace Swift { class JingleResponder : public SetResponder<JinglePayload> { public: JingleResponder(JingleSessionManager* sessionManager, IQRouter* router); - + virtual ~JingleResponder(); private: virtual bool handleSetRequest(const JID& from, const JID& to, const std::string& id, boost::shared_ptr<JinglePayload> payload); private: JingleSessionManager* sessionManager; + IQRouter* router; }; } diff --git a/Swiften/Jingle/JingleSession.cpp b/Swiften/Jingle/JingleSession.cpp index 1366191..eb649f3 100644 --- a/Swiften/Jingle/JingleSession.cpp +++ b/Swiften/Jingle/JingleSession.cpp @@ -11,7 +11,9 @@ namespace Swift { JingleSession::JingleSession(const JID& initiator, const std::string& id) : initiator(initiator), id(id) { - + // initiator must always be a full JID; session lookup based on it wouldn't work otherwise + // this is checked on an upper level so that the assert never fails + assert(!initiator.isBare()); } JingleSession::~JingleSession() { diff --git a/Swiften/Jingle/JingleSession.h b/Swiften/Jingle/JingleSession.h index b8117bb..150ad79 100644 --- a/Swiften/Jingle/JingleSession.h +++ b/Swiften/Jingle/JingleSession.h @@ -7,6 +7,8 @@ #pragma once #include <boost/shared_ptr.hpp> +#include <boost/optional.hpp> + #include <string> #include <Swiften/Base/boost_bsignals.h> @@ -30,16 +32,22 @@ namespace Swift { const std::string& getID() const { return id; } - - virtual void terminate(JinglePayload::Reason::Type reason) = 0; - virtual void accept(JingleTransportPayload::ref = JingleTransportPayload::ref()) = 0; + virtual void sendInitiate(const JingleContentID&, JingleDescription::ref, JingleTransportPayload::ref) = 0; + virtual void sendTerminate(JinglePayload::Reason::Type reason) = 0; + virtual void sendInfo(boost::shared_ptr<Payload>) = 0; + virtual void sendAccept(const JingleContentID&, JingleDescription::ref, JingleTransportPayload::ref = JingleTransportPayload::ref()) = 0; virtual void sendTransportInfo(const JingleContentID&, JingleTransportPayload::ref) = 0; - virtual void acceptTransport(const JingleContentID&, JingleTransportPayload::ref) = 0; - virtual void rejectTransport(const JingleContentID&, JingleTransportPayload::ref) = 0; + virtual void sendTransportAccept(const JingleContentID&, JingleTransportPayload::ref) = 0; + virtual void sendTransportReject(const JingleContentID&, JingleTransportPayload::ref) = 0; + virtual void sendTransportReplace(const JingleContentID&, JingleTransportPayload::ref) = 0; public: - boost::signal<void ()> onSessionTerminateReceived; + boost::signal<void (const JingleContentID&, JingleDescription::ref, JingleTransportPayload::ref)> onSessionAcceptReceived; + boost::signal<void (JinglePayload::ref)> onSessionInfoReceived; + boost::signal<void (boost::optional<JinglePayload::Reason>)> onSessionTerminateReceived; + boost::signal<void (const JingleContentID&, JingleTransportPayload::ref)> onTransportAcceptReceived; boost::signal<void (const JingleContentID&, JingleTransportPayload::ref)> onTransportInfoReceived; + boost::signal<void (const JingleContentID&, JingleTransportPayload::ref)> onTransportRejectReceived; boost::signal<void (const JingleContentID&, JingleTransportPayload::ref)> onTransportReplaceReceived; private: diff --git a/Swiften/Jingle/JingleSessionImpl.cpp b/Swiften/Jingle/JingleSessionImpl.cpp index cbb2b42..98c5196 100644 --- a/Swiften/Jingle/JingleSessionImpl.cpp +++ b/Swiften/Jingle/JingleSessionImpl.cpp @@ -8,33 +8,176 @@ #include <boost/smart_ptr/make_shared.hpp> +#include <Swiften/Parser/PayloadParsers/JingleParser.h> +#include <Swiften/Jingle/JingleContentID.h> +#include <Swiften/Elements/JingleContentPayload.h> +#include <Swiften/Queries/Request.h> +#include <Swiften/Queries/GenericRequest.h> + +#include <Swiften/Base/Log.h> + +#include "Swiften/Serializer/PayloadSerializers/JinglePayloadSerializer.h" +#include "Swiften/FileTransfer/JingleTransport.h" + namespace Swift { -JingleSessionImpl::JingleSessionImpl(const JID& initiator, const std::string& id) : JingleSession(initiator, id) { +JingleSessionImpl::JingleSessionImpl(const JID& initiator, const JID& peerJID, const std::string& id, IQRouter* router) : JingleSession(initiator, id), iqRouter(router), peerJID(peerJID) { + SWIFT_LOG(debug) << "initiator: " << initiator << ", peerJID: " << peerJID << std::endl; +} + +void JingleSessionImpl::handleIncomingAction(JinglePayload::ref action) { + if (action->getAction() == JinglePayload::SessionTerminate) { + onSessionTerminateReceived(action->getReason()); + return; + } + if (action->getAction() == JinglePayload::SessionInfo) { + onSessionInfoReceived(action); + return; + } + + JingleContentPayload::ref content = action->getPayload<JingleContentPayload>(); + if (!content) { + SWIFT_LOG(debug) << "no content payload!" << std::endl; + return; + } + JingleContentID contentID(content->getName(), content->getCreator()); + JingleDescription::ref description = content->getDescriptions().empty() ? JingleDescription::ref() : content->getDescriptions()[0]; + JingleTransportPayload::ref transport = content->getTransports().empty() ? JingleTransportPayload::ref() : content->getTransports()[0]; + switch(action->getAction()) { + case JinglePayload::SessionAccept: + onSessionAcceptReceived(contentID, description, transport); + return; + case JinglePayload::TransportAccept: + onTransportAcceptReceived(contentID, transport); + return; + case JinglePayload::TransportInfo: + onTransportInfoReceived(contentID, transport); + return; + case JinglePayload::TransportReject: + onTransportRejectReceived(contentID, transport); + return; + case JinglePayload::TransportReplace: + onTransportReplaceReceived(contentID, transport); + return; + // following unused Jingle actions + case JinglePayload::ContentAccept: + case JinglePayload::ContentAdd: + case JinglePayload::ContentModify: + case JinglePayload::ContentReject: + case JinglePayload::ContentRemove: + case JinglePayload::DescriptionInfo: + case JinglePayload::SecurityInfo: + + // handled elsewhere + case JinglePayload::SessionInitiate: + case JinglePayload::SessionInfo: + case JinglePayload::SessionTerminate: + + case JinglePayload::UnknownAction: + return; + } + std::cerr << "Unhandled Jingle action!!! ACTION: " << action->getAction() << std::endl; } -void JingleSessionImpl::handleIncomingAction(JinglePayload::ref) { +void JingleSessionImpl::sendInitiate(const JingleContentID& id, JingleDescription::ref description, JingleTransportPayload::ref transport) { + JinglePayload::ref payload = boost::make_shared<JinglePayload>(JinglePayload::SessionInitiate, getID()); + payload->setInitiator(getInitiator()); + JingleContentPayload::ref content = boost::make_shared<JingleContentPayload>(); + content->setCreator(id.getCreator()); + content->setName(id.getName()); + content->addDescription(description); + content->addTransport(transport); + payload->addPayload(content); + + sendSetRequest(payload); } -void JingleSessionImpl::terminate(JinglePayload::Reason::Type reason) { +void JingleSessionImpl::sendTerminate(JinglePayload::Reason::Type reason) { JinglePayload::ref payload = boost::make_shared<JinglePayload>(JinglePayload::SessionTerminate, getID()); payload->setReason(JinglePayload::Reason(reason)); - //onAction(payload) + payload->setInitiator(getInitiator()); + sendSetRequest(payload); +} + +void JingleSessionImpl::sendInfo(boost::shared_ptr<Payload> info) { + JinglePayload::ref payload = boost::make_shared<JinglePayload>(JinglePayload::SessionInfo, getID()); + payload->addPayload(info); + + sendSetRequest(payload); +} + +void JingleSessionImpl::sendAccept(const JingleContentID& id, JingleDescription::ref description, JingleTransportPayload::ref transPayload) { + JinglePayload::ref payload = createPayload(); + + JingleContentPayload::ref content = boost::make_shared<JingleContentPayload>(); + content->setCreator(id.getCreator()); + content->setName(id.getName()); + content->addTransport(transPayload); + content->addDescription(description); + payload->setAction(JinglePayload::SessionAccept); + payload->addPayload(content); + + // put into IQ:set and send it away + sendSetRequest(payload); } -void JingleSessionImpl::acceptTransport(const JingleContentID&, JingleTransportPayload::ref) { +void JingleSessionImpl::sendTransportAccept(const JingleContentID& id, JingleTransportPayload::ref transPayload) { + JinglePayload::ref payload = createPayload(); + + JingleContentPayload::ref content = boost::make_shared<JingleContentPayload>(); + content->setCreator(id.getCreator()); + content->setName(id.getName()); + content->addTransport(transPayload); + payload->setAction(JinglePayload::TransportAccept); + payload->addPayload(content); + + // put into IQ:set and send it away + sendSetRequest(payload); +} + +void JingleSessionImpl::sendTransportInfo(const JingleContentID& id, JingleTransportPayload::ref transPayload) { + JinglePayload::ref payload = createPayload(); + + JingleContentPayload::ref content = boost::make_shared<JingleContentPayload>(); + content->setCreator(id.getCreator()); + content->setName(id.getName()); + content->addTransport(transPayload); + payload->setAction(JinglePayload::TransportInfo); + payload->addPayload(content); + + sendSetRequest(payload); +} + +void JingleSessionImpl::sendTransportReject(const JingleContentID& /* id */, JingleTransportPayload::ref /* transPayload */) { + SWIFT_LOG(debug) << "NOT IMPLEMENTED YET!!!!" << std::endl; } -void JingleSessionImpl::rejectTransport(const JingleContentID&, JingleTransportPayload::ref) { +void JingleSessionImpl::sendTransportReplace(const JingleContentID& id, JingleTransportPayload::ref transPayload) { + JinglePayload::ref payload = createPayload(); + JingleContentPayload::ref content = boost::make_shared<JingleContentPayload>(); + content->setCreator(id.getCreator()); + content->setName(id.getName()); + content->addTransport(transPayload); + payload->setAction(JinglePayload::TransportReplace); + payload->addContent(content); + + sendSetRequest(payload); } -void JingleSessionImpl::accept(JingleTransportPayload::ref) { + +void JingleSessionImpl::sendSetRequest(JinglePayload::ref payload) { + boost::shared_ptr<GenericRequest<JinglePayload> > request = boost::make_shared<GenericRequest<JinglePayload> >(IQ::Set, peerJID, payload, iqRouter); + request->send(); } -void JingleSessionImpl::sendTransportInfo(const JingleContentID&, JingleTransportPayload::ref) { +JinglePayload::ref JingleSessionImpl::createPayload() const { + JinglePayload::ref payload = boost::make_shared<JinglePayload>(); + payload->setSessionID(getID()); + payload->setInitiator(getInitiator()); + return payload; } diff --git a/Swiften/Jingle/JingleSessionImpl.h b/Swiften/Jingle/JingleSessionImpl.h index a254ead..3c1559a 100644 --- a/Swiften/Jingle/JingleSessionImpl.h +++ b/Swiften/Jingle/JingleSessionImpl.h @@ -11,20 +11,32 @@ #include <Swiften/Jingle/JingleSession.h> namespace Swift { + class IQRouter; + class JingleSessionImpl : public JingleSession { friend class JingleResponder; public: typedef boost::shared_ptr<JingleSessionImpl> ref; - JingleSessionImpl(const JID& initiator, const std::string& id); + JingleSessionImpl(const JID& initiator, const JID&, const std::string& id, IQRouter* router); - virtual void terminate(JinglePayload::Reason::Type reason); - virtual void accept(JingleTransportPayload::ref); + virtual void sendInitiate(const JingleContentID&, JingleDescription::ref, JingleTransportPayload::ref); + virtual void sendTerminate(JinglePayload::Reason::Type reason); + virtual void sendInfo(boost::shared_ptr<Payload>); + virtual void sendAccept(const JingleContentID&, JingleDescription::ref, JingleTransportPayload::ref); virtual void sendTransportInfo(const JingleContentID&, JingleTransportPayload::ref); - virtual void acceptTransport(const JingleContentID&, JingleTransportPayload::ref); - virtual void rejectTransport(const JingleContentID&, JingleTransportPayload::ref); + virtual void sendTransportAccept(const JingleContentID&, JingleTransportPayload::ref); + virtual void sendTransportReject(const JingleContentID&, JingleTransportPayload::ref); + virtual void sendTransportReplace(const JingleContentID&, JingleTransportPayload::ref); private: void handleIncomingAction(JinglePayload::ref); + + void sendSetRequest(JinglePayload::ref payload); + JinglePayload::ref createPayload() const; + + private: + IQRouter *iqRouter; + JID peerJID; }; } diff --git a/Swiften/Jingle/JingleSessionManager.cpp b/Swiften/Jingle/JingleSessionManager.cpp index f9a94f3..4299a1e 100644 --- a/Swiften/Jingle/JingleSessionManager.cpp +++ b/Swiften/Jingle/JingleSessionManager.cpp @@ -14,15 +14,17 @@ namespace Swift { JingleSessionManager::JingleSessionManager(IQRouter* router) : router(router) { responder = new JingleResponder(this, router); + responder->start(); } JingleSessionManager::~JingleSessionManager() { + responder->stop(); delete responder; } JingleSessionImpl::ref JingleSessionManager::getSession(const JID& jid, const std::string& id) const { - SessionMap::const_iterator i = incomingSessions.find(JIDSession(jid, id)); - return i != incomingSessions.end() ? i->second : JingleSessionImpl::ref(); + SessionMap::const_iterator i = sessions.find(JIDSession(jid, id)); + return i != sessions.end() ? i->second : JingleSessionImpl::ref(); } void JingleSessionManager::addIncomingSessionHandler(IncomingJingleSessionHandler* handler) { @@ -33,8 +35,13 @@ void JingleSessionManager::removeIncomingSessionHandler(IncomingJingleSessionHan erase(incomingSessionHandlers, handler); } -void JingleSessionManager::handleIncomingSession(const JID& from, JingleSessionImpl::ref session, const std::vector<JingleContentPayload::ref>& contents) { - incomingSessions.insert(std::make_pair(JIDSession(from, session->getID()), session)); +void JingleSessionManager::registerOutgoingSession(const JID& initiator, JingleSessionImpl::ref session) { + sessions.insert(std::make_pair(JIDSession(initiator, session->getID()), session)); + SWIFT_LOG(debug) << "Added session " << session->getID() << " for initiator " << initiator.toString() << std::endl; +} + +void JingleSessionManager::handleIncomingSession(const JID& initiator, JingleSessionImpl::ref session, const std::vector<JingleContentPayload::ref>& contents) { + sessions.insert(std::make_pair(JIDSession(initiator, session->getID()), session)); foreach (IncomingJingleSessionHandler* handler, incomingSessionHandlers) { if (handler->handleIncomingJingleSession(session, contents)) { return; diff --git a/Swiften/Jingle/JingleSessionManager.h b/Swiften/Jingle/JingleSessionManager.h index 3b23fb0..50c429b 100644 --- a/Swiften/Jingle/JingleSessionManager.h +++ b/Swiften/Jingle/JingleSessionManager.h @@ -28,22 +28,23 @@ namespace Swift { void addIncomingSessionHandler(IncomingJingleSessionHandler* handler); void removeIncomingSessionHandler(IncomingJingleSessionHandler* handler); + void registerOutgoingSession(const JID& initiator, JingleSessionImpl::ref); protected: - void handleIncomingSession(const JID& from, JingleSessionImpl::ref, const std::vector<JingleContentPayload::ref>& contents); + void handleIncomingSession(const JID& initiator, JingleSessionImpl::ref, const std::vector<JingleContentPayload::ref>& contents); private: IQRouter* router; JingleResponder* responder; std::vector<IncomingJingleSessionHandler*> incomingSessionHandlers; struct JIDSession { - JIDSession(const JID& jid, const std::string& session) : jid(jid), session(session) {} + JIDSession(const JID& initiator, const std::string& session) : initiator(initiator), session(session) {} bool operator<(const JIDSession& o) const { - return jid == o.jid ? session < o.session : jid < o.jid; + return initiator == o.initiator ? session < o.session : initiator < o.initiator; } - JID jid; + JID initiator; std::string session; }; typedef std::map<JIDSession, JingleSessionImpl::ref> SessionMap; - SessionMap incomingSessions; + SessionMap sessions; }; } diff --git a/Swiften/Jingle/SConscript b/Swiften/Jingle/SConscript index 6b3cfd3..5dcf293 100644 --- a/Swiften/Jingle/SConscript +++ b/Swiften/Jingle/SConscript @@ -6,6 +6,7 @@ sources = [ "IncomingJingleSessionHandler.cpp", "JingleSessionManager.cpp", "JingleResponder.cpp", + "FakeJingleSession.cpp", ] swiften_env.Append(SWIFTEN_OBJECTS = swiften_env.SwiftenObject(sources)) diff --git a/Swiften/Network/BoostNetworkFactories.cpp b/Swiften/Network/BoostNetworkFactories.cpp index cc80197..c13270f 100644 --- a/Swiften/Network/BoostNetworkFactories.cpp +++ b/Swiften/Network/BoostNetworkFactories.cpp @@ -9,6 +9,7 @@ #include <Swiften/Network/BoostConnectionFactory.h> #include <Swiften/Network/PlatformDomainNameResolver.h> #include <Swiften/Network/BoostConnectionServerFactory.h> +#include <Swiften/Network/PlatformNATTraversalWorker.h> namespace Swift { @@ -17,9 +18,11 @@ BoostNetworkFactories::BoostNetworkFactories(EventLoop* eventLoop) { connectionFactory = new BoostConnectionFactory(ioServiceThread.getIOService(), eventLoop); domainNameResolver = new PlatformDomainNameResolver(eventLoop); connectionServerFactory = new BoostConnectionServerFactory(ioServiceThread.getIOService(), eventLoop); + platformNATTraversalWorker = new PlatformNATTraversalWorker(eventLoop); } BoostNetworkFactories::~BoostNetworkFactories() { + delete platformNATTraversalWorker; delete connectionServerFactory; delete domainNameResolver; delete connectionFactory; diff --git a/Swiften/Network/BoostNetworkFactories.h b/Swiften/Network/BoostNetworkFactories.h index 96bcc6c..a1cf9ae 100644 --- a/Swiften/Network/BoostNetworkFactories.h +++ b/Swiften/Network/BoostNetworkFactories.h @@ -37,11 +37,16 @@ namespace Swift { return connectionServerFactory; } + PlatformNATTraversalWorker* getPlatformNATTraversalWorker() const { + return platformNATTraversalWorker; + } + private: BoostIOServiceThread ioServiceThread; TimerFactory* timerFactory; ConnectionFactory* connectionFactory; DomainNameResolver* domainNameResolver; ConnectionServerFactory* connectionServerFactory; + PlatformNATTraversalWorker* platformNATTraversalWorker; }; } diff --git a/Swiften/Network/DummyConnectionFactory.h b/Swiften/Network/DummyConnectionFactory.h new file mode 100644 index 0000000..e8a294e --- /dev/null +++ b/Swiften/Network/DummyConnectionFactory.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Network/ConnectionFactory.h> +#include <Swiften/Network/DummyConnection.h> + +namespace Swift { + +class EventLoop; + +class DummyConnectionFactory : public ConnectionFactory { +public: + DummyConnectionFactory(EventLoop *eventLoop) : eventLoop(eventLoop) {} + virtual ~DummyConnectionFactory() {} + virtual boost::shared_ptr<Connection> createConnection() { + return boost::make_shared<DummyConnection>(eventLoop); + } +private: + EventLoop* eventLoop; +}; + +} diff --git a/Swiften/Network/NATPMPNATTraversalForwardPortRequest.cpp b/Swiften/Network/NATPMPNATTraversalForwardPortRequest.cpp new file mode 100644 index 0000000..69b325c --- /dev/null +++ b/Swiften/Network/NATPMPNATTraversalForwardPortRequest.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "NATPMPNATTraversalForwardPortRequest.h" + +#include <natpmp.h> + +#include <Swiften/Base/Log.h> + +namespace Swift { + +NATPMPNATTraversalForwardPortRequest::NATPMPNATTraversalForwardPortRequest(PlatformNATTraversalForwardPortRequest::PortMapping mapping, PlatformNATTraversalWorker* worker) : PlatformNATTraversalForwardPortRequest(worker), mapping(mapping) { + +} + +NATPMPNATTraversalForwardPortRequest::~NATPMPNATTraversalForwardPortRequest() { + +} + +void NATPMPNATTraversalForwardPortRequest::runBlocking() { + boost::optional<PortMapping> result; + + natpmp_t natpmp; + natpmpresp_t response; + initnatpmp(&natpmp, 0, 0); + + do { + if (sendnewportmappingrequest(&natpmp, mapping.protocol == PortMapping::TCP ? NATPMP_PROTOCOL_TCP : NATPMP_PROTOCOL_UDP, mapping.leaseInSeconds, mapping.publicPort, mapping.localPort) != 2) { + SWIFT_LOG(debug) << "Failed to send NAT-PMP port forwarding request!" << std::endl; + break; + } + int r = 0; + + do { + fd_set fds; + struct timeval timeout; + FD_ZERO(&fds); + FD_SET(natpmp.s, &fds); + getnatpmprequesttimeout(&natpmp, &timeout); + select(FD_SETSIZE, &fds, NULL, NULL, &timeout); + r = readnatpmpresponseorretry(&natpmp, &response); + } while(r == NATPMP_TRYAGAIN); + + if (r == 0) { + if (response.pnu.newportmapping.privateport == mapping.localPort && + response.pnu.newportmapping.mappedpublicport == mapping.publicPort) { + mapping.leaseInSeconds = response.pnu.newportmapping.lifetime; + result = boost::optional<PortMapping>(mapping); + } + } else { + SWIFT_LOG(debug) << "Inavlid NAT-PMP response." << std::endl; + } + } while(false); + closenatpmp(&natpmp); + + onResult(result); +} + +} diff --git a/Swiften/Network/NATPMPNATTraversalForwardPortRequest.h b/Swiften/Network/NATPMPNATTraversalForwardPortRequest.h new file mode 100644 index 0000000..71d8621 --- /dev/null +++ b/Swiften/Network/NATPMPNATTraversalForwardPortRequest.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Network/PlatformNATTraversalForwardPortRequest.h> + +namespace Swift { + +class NATPMPNATTraversalForwardPortRequest : public PlatformNATTraversalForwardPortRequest { +public: + NATPMPNATTraversalForwardPortRequest(PlatformNATTraversalForwardPortRequest::PortMapping, PlatformNATTraversalWorker*); + virtual ~NATPMPNATTraversalForwardPortRequest(); + + virtual void runBlocking(); + +private: + PlatformNATTraversalForwardPortRequest::PortMapping mapping; +}; + +} diff --git a/Swiften/Network/NATPMPNATTraversalGetPublicIPRequest.cpp b/Swiften/Network/NATPMPNATTraversalGetPublicIPRequest.cpp new file mode 100644 index 0000000..06a21a3 --- /dev/null +++ b/Swiften/Network/NATPMPNATTraversalGetPublicIPRequest.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "NATPMPNATTraversalGetPublicIPRequest.h" + +#include <natpmp.h> + +#include <Swiften/Base/Log.h> + +namespace Swift { + +NATPMPNATTraversalGetPublicIPRequest::NATPMPNATTraversalGetPublicIPRequest(PlatformNATTraversalWorker* worker) : PlatformNATTraversalGetPublicIPRequest(worker) { + +} + +NATPMPNATTraversalGetPublicIPRequest::~NATPMPNATTraversalGetPublicIPRequest() { + +} + +/* +TODO: a non-blocking solution should be possible too here +void NATPMPNATTraversalGetPublicIPRequest::run() { + // we can run directly since libnatpmp's API is asynchronous + runBlocking(); +}*/ + +void NATPMPNATTraversalGetPublicIPRequest::runBlocking() { + boost::optional<HostAddress> result; + + natpmp_t natpmp; + natpmpresp_t response; + initnatpmp(&natpmp, 0, 0); + + do { + if (sendpublicaddressrequest(&natpmp) != 2) { + SWIFT_LOG(debug) << "Failed to send NAT-PMP public address request!" << std::endl; + break; + } + int r = 0; + + do { + fd_set fds; + struct timeval timeout; + FD_ZERO(&fds); + FD_SET(natpmp.s, &fds); + getnatpmprequesttimeout(&natpmp, &timeout); + select(FD_SETSIZE, &fds, NULL, NULL, &timeout); + r = readnatpmpresponseorretry(&natpmp, &response); + } while(r == NATPMP_TRYAGAIN); + + if (r == 0) { + result = boost::optional<HostAddress>(HostAddress(reinterpret_cast<const unsigned char*>(&(response.pnu.publicaddress.addr)), 4)); + } else { + SWIFT_LOG(debug) << "Inavlid NAT-PMP response." << std::endl; + } + } while(false); + closenatpmp(&natpmp); + onResult(result); +} + +} diff --git a/Swiften/Network/NATPMPNATTraversalGetPublicIPRequest.h b/Swiften/Network/NATPMPNATTraversalGetPublicIPRequest.h new file mode 100644 index 0000000..6112091 --- /dev/null +++ b/Swiften/Network/NATPMPNATTraversalGetPublicIPRequest.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Network/PlatformNATTraversalGetPublicIPRequest.h> + +namespace Swift { + +class NATPMPNATTraversalGetPublicIPRequest : public PlatformNATTraversalGetPublicIPRequest { +public: + NATPMPNATTraversalGetPublicIPRequest(PlatformNATTraversalWorker*); + virtual ~NATPMPNATTraversalGetPublicIPRequest(); + + //virtual void run(); + virtual void runBlocking(); +}; + +} diff --git a/Swiften/Network/NATPMPNATTraversalRemovePortForwardingRequest.cpp b/Swiften/Network/NATPMPNATTraversalRemovePortForwardingRequest.cpp new file mode 100644 index 0000000..c99ac92 --- /dev/null +++ b/Swiften/Network/NATPMPNATTraversalRemovePortForwardingRequest.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "NATPMPNATTraversalRemovePortForwardingRequest.h" + +#include <boost/format.hpp> + +#include <natpmp.h> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/Log.h> +#include <Swiften/Network/NetworkInterface.h> +#include <Swiften/Network/PlatformNetworkEnvironment.h> + +namespace Swift { + +NATPMPNATTraversalRemovePortForwardingRequest::NATPMPNATTraversalRemovePortForwardingRequest(PlatformNATTraversalRemovePortForwardingRequest::PortMapping mapping, PlatformNATTraversalWorker* worker) : PlatformNATTraversalRemovePortForwardingRequest(worker), mapping(mapping) { + +} + +NATPMPNATTraversalRemovePortForwardingRequest::~NATPMPNATTraversalRemovePortForwardingRequest() { + +} + +void NATPMPNATTraversalRemovePortForwardingRequest::runBlocking() { + boost::optional<bool> result; + + natpmp_t natpmp; + natpmpresp_t response; + initnatpmp(&natpmp, 0, 0); + + do { + if (sendnewportmappingrequest(&natpmp, mapping.protocol == PortMapping::TCP ? NATPMP_PROTOCOL_TCP : NATPMP_PROTOCOL_UDP, 0, 0, mapping.localPort) != 2) { + SWIFT_LOG(debug) << "Failed to send NAT-PMP remove forwarding request!" << std::endl; + break; + } + int r = 0; + + do { + fd_set fds; + struct timeval timeout; + FD_ZERO(&fds); + FD_SET(natpmp.s, &fds); + getnatpmprequesttimeout(&natpmp, &timeout); + select(FD_SETSIZE, &fds, NULL, NULL, &timeout); + r = readnatpmpresponseorretry(&natpmp, &response); + } while(r == NATPMP_TRYAGAIN); + + if (r == 0) { + if (response.pnu.newportmapping.privateport == mapping.localPort && + response.pnu.newportmapping.mappedpublicport == mapping.publicPort) { + mapping.leaseInSeconds = response.pnu.newportmapping.lifetime; + result = boost::optional<bool>(true); + } + } else { + result = boost::optional<bool>(false); + SWIFT_LOG(debug) << "Inavlid NAT-PMP response." << std::endl; + } + } while(false); + closenatpmp(&natpmp); + + onResult(result); +} + +HostAddress NATPMPNATTraversalRemovePortForwardingRequest::getLocalClient() { + PlatformNetworkEnvironment env; + + foreach (NetworkInterface::ref iface, env.getNetworkInterfaces()) { + if (!iface->isLoopback()) { + foreach (HostAddress address, iface->getAddresses()) { + if (address.getRawAddress().is_v4()) { + return address; + } + } + } + } + return HostAddress(); +} + +} diff --git a/Swiften/Network/NATPMPNATTraversalRemovePortForwardingRequest.h b/Swiften/Network/NATPMPNATTraversalRemovePortForwardingRequest.h new file mode 100644 index 0000000..c4ffcf3 --- /dev/null +++ b/Swiften/Network/NATPMPNATTraversalRemovePortForwardingRequest.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Network/PlatformNATTraversalRemovePortForwardingRequest.h> + +namespace Swift { + +class NATPMPNATTraversalRemovePortForwardingRequest : public PlatformNATTraversalRemovePortForwardingRequest { +public: + NATPMPNATTraversalRemovePortForwardingRequest(PlatformNATTraversalRemovePortForwardingRequest::PortMapping, PlatformNATTraversalWorker*); + virtual ~NATPMPNATTraversalRemovePortForwardingRequest(); + + virtual void runBlocking(); + +private: + HostAddress getLocalClient(); + +private: + PlatformNATTraversalRemovePortForwardingRequest::PortMapping mapping; +}; + +} diff --git a/Swiften/Network/NetworkEnvironment.h b/Swiften/Network/NetworkEnvironment.h new file mode 100644 index 0000000..348bdb9 --- /dev/null +++ b/Swiften/Network/NetworkEnvironment.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <vector> + +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/Network/NetworkInterface.h> + +namespace Swift { + +class NetworkEnvironment { +public: + virtual ~NetworkEnvironment() {}; + virtual std::vector<NetworkInterface::ref> getNetworkInterfaces() = 0; + + boost::signal <void (NetworkInterface::ref)> onNetworkInterfaceChange; +}; + +} diff --git a/Swiften/Network/NetworkFactories.h b/Swiften/Network/NetworkFactories.h index d0d2299..e7d2ff0 100644 --- a/Swiften/Network/NetworkFactories.h +++ b/Swiften/Network/NetworkFactories.h @@ -11,6 +11,7 @@ namespace Swift { class ConnectionFactory; class DomainNameResolver; class ConnectionServerFactory; + class PlatformNATTraversalWorker; /** * An interface collecting network factories. @@ -23,5 +24,6 @@ namespace Swift { virtual ConnectionFactory* getConnectionFactory() const = 0; virtual DomainNameResolver* getDomainNameResolver() const = 0; virtual ConnectionServerFactory* getConnectionServerFactory() const = 0; + virtual PlatformNATTraversalWorker* getPlatformNATTraversalWorker() const = 0; }; } diff --git a/Swiften/Network/NetworkInterface.h b/Swiften/Network/NetworkInterface.h new file mode 100644 index 0000000..062e1f9 --- /dev/null +++ b/Swiften/Network/NetworkInterface.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <vector> +#include <boost/shared_ptr.hpp> + +#include <Swiften/Network/HostAddress.h> + +namespace Swift { + +class NetworkInterface { +public: + typedef boost::shared_ptr<NetworkInterface> ref; + +public: + enum InterfaceType { + WLAN, + Ethernet, + Mobile, + VPN, + }; + +public: + virtual ~NetworkInterface() {}; + virtual std::vector<HostAddress> getAddresses() = 0; + virtual std::string getName() = 0; + virtual bool isLoopback() = 0; +}; + +} diff --git a/Swiften/Network/PlatformNATTraversalForwardPortRequest.cpp b/Swiften/Network/PlatformNATTraversalForwardPortRequest.cpp new file mode 100644 index 0000000..b28024a --- /dev/null +++ b/Swiften/Network/PlatformNATTraversalForwardPortRequest.cpp @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "PlatformNATTraversalForwardPortRequest.h" + +namespace Swift { + +PlatformNATTraversalForwardPortRequest::PlatformNATTraversalForwardPortRequest(PlatformNATTraversalWorker* worker) : PlatformNATTraversalRequest(worker) { +} + +PlatformNATTraversalForwardPortRequest::~PlatformNATTraversalForwardPortRequest() { + +} + +} diff --git a/Swiften/Network/PlatformNATTraversalForwardPortRequest.h b/Swiften/Network/PlatformNATTraversalForwardPortRequest.h new file mode 100644 index 0000000..cb1750c --- /dev/null +++ b/Swiften/Network/PlatformNATTraversalForwardPortRequest.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/Network/HostAddress.h> +#include <Swiften/Network/PlatformNATTraversalRequest.h> + +namespace Swift { + +class PlatformNATTraversalWorker; + +class PlatformNATTraversalForwardPortRequest : public PlatformNATTraversalRequest { +public: + struct PortMapping { + enum Protocol { + TCP, + UDP, + }; + + unsigned int publicPort; + unsigned int localPort; + Protocol protocol; + unsigned long leaseInSeconds; + }; + +public: + PlatformNATTraversalForwardPortRequest(PlatformNATTraversalWorker* worker); + virtual ~PlatformNATTraversalForwardPortRequest(); + + boost::signal<void (boost::optional<PortMapping>)> onResult; +}; + +} diff --git a/Swiften/Network/PlatformNATTraversalGetPublicIPRequest.cpp b/Swiften/Network/PlatformNATTraversalGetPublicIPRequest.cpp new file mode 100644 index 0000000..7a57e30 --- /dev/null +++ b/Swiften/Network/PlatformNATTraversalGetPublicIPRequest.cpp @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "PlatformNATTraversalGetPublicIPRequest.h" + +namespace Swift { + +PlatformNATTraversalGetPublicIPRequest::PlatformNATTraversalGetPublicIPRequest(PlatformNATTraversalWorker* worker) : PlatformNATTraversalRequest(worker) { +} + +PlatformNATTraversalGetPublicIPRequest::~PlatformNATTraversalGetPublicIPRequest() { + +} + +} diff --git a/Swiften/Network/PlatformNATTraversalGetPublicIPRequest.h b/Swiften/Network/PlatformNATTraversalGetPublicIPRequest.h new file mode 100644 index 0000000..1cb37fe --- /dev/null +++ b/Swiften/Network/PlatformNATTraversalGetPublicIPRequest.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/Network/HostAddress.h> +#include <Swiften/Network/PlatformNATTraversalRequest.h> + +namespace Swift { + +class PlatformNATTraversalWorker; + +class PlatformNATTraversalGetPublicIPRequest : public PlatformNATTraversalRequest { +public: + PlatformNATTraversalGetPublicIPRequest(PlatformNATTraversalWorker* worker); + virtual ~PlatformNATTraversalGetPublicIPRequest(); + + boost::signal<void (boost::optional<HostAddress>)> onResult; +}; + +} diff --git a/Swiften/Network/PlatformNATTraversalRemovePortForwardingRequest.cpp b/Swiften/Network/PlatformNATTraversalRemovePortForwardingRequest.cpp new file mode 100644 index 0000000..514988e --- /dev/null +++ b/Swiften/Network/PlatformNATTraversalRemovePortForwardingRequest.cpp @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "PlatformNATTraversalRemovePortForwardingRequest.h" + +namespace Swift { + +PlatformNATTraversalRemovePortForwardingRequest::PlatformNATTraversalRemovePortForwardingRequest(PlatformNATTraversalWorker* worker) : PlatformNATTraversalRequest(worker) { +} + +PlatformNATTraversalRemovePortForwardingRequest::~PlatformNATTraversalRemovePortForwardingRequest() { + +} + +} diff --git a/Swiften/Network/PlatformNATTraversalRemovePortForwardingRequest.h b/Swiften/Network/PlatformNATTraversalRemovePortForwardingRequest.h new file mode 100644 index 0000000..03427ad --- /dev/null +++ b/Swiften/Network/PlatformNATTraversalRemovePortForwardingRequest.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/Network/HostAddress.h> +#include <Swiften/Network/PlatformNATTraversalRequest.h> + +namespace Swift { + +class PlatformNATTraversalWorker; + +class PlatformNATTraversalRemovePortForwardingRequest : public PlatformNATTraversalRequest { +public: + struct PortMapping { + enum Protocol { + TCP, + UDP, + }; + + unsigned int publicPort; + unsigned int localPort; + Protocol protocol; + unsigned long leaseInSeconds; + }; + +public: + PlatformNATTraversalRemovePortForwardingRequest(PlatformNATTraversalWorker* worker); + virtual ~PlatformNATTraversalRemovePortForwardingRequest(); + + boost::signal<void (boost::optional<bool> /* failure */)> onResult; +}; + +} diff --git a/Swiften/Network/PlatformNATTraversalRequest.cpp b/Swiften/Network/PlatformNATTraversalRequest.cpp new file mode 100644 index 0000000..25e8a32 --- /dev/null +++ b/Swiften/Network/PlatformNATTraversalRequest.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "PlatformNATTraversalRequest.h" + +#include <Swiften/Network/PlatformNATTraversalWorker.h> + +namespace Swift { + +PlatformNATTraversalRequest::PlatformNATTraversalRequest(PlatformNATTraversalWorker* worker) : worker(worker) { + +} + +PlatformNATTraversalRequest::~PlatformNATTraversalRequest() { + +} + +void PlatformNATTraversalRequest::run() { + worker->addRequestToQueue(shared_from_this()); +} + +} diff --git a/Swiften/Network/PlatformNATTraversalRequest.h b/Swiften/Network/PlatformNATTraversalRequest.h new file mode 100644 index 0000000..4b760ad --- /dev/null +++ b/Swiften/Network/PlatformNATTraversalRequest.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/enable_shared_from_this.hpp> +#include <boost/shared_ptr.hpp> + +namespace Swift { + +class PlatformNATTraversalWorker; + +class PlatformNATTraversalRequest : public boost::enable_shared_from_this<PlatformNATTraversalRequest> { +public: + typedef boost::shared_ptr<PlatformNATTraversalRequest> ref; + +public: + PlatformNATTraversalRequest(PlatformNATTraversalWorker* worker); + virtual ~PlatformNATTraversalRequest(); + + virtual void run(); + virtual void runBlocking() = 0; + +private: + PlatformNATTraversalWorker* worker; +}; + +} diff --git a/Swiften/Network/PlatformNATTraversalWorker.cpp b/Swiften/Network/PlatformNATTraversalWorker.cpp new file mode 100644 index 0000000..a4efedd --- /dev/null +++ b/Swiften/Network/PlatformNATTraversalWorker.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "PlatformNATTraversalWorker.h" + +#include <boost/smart_ptr/make_shared.hpp> +#include <Swiften/Base/Log.h> +#include <Swiften/Network/UPnPNATTraversalGetPublicIPRequest.h> +#include <Swiften/Network/UPnPNATTraversalForwardPortRequest.h> +#include <Swiften/Network/UPnPNATTraversalRemovePortForwardingRequest.h> +#include <Swiften/Network/NATPMPNATTraversalGetPublicIPRequest.h> +#include <Swiften/Network/NATPMPNATTraversalForwardPortRequest.h> +#include <Swiften/Network/NATPMPNATTraversalRemovePortForwardingRequest.h> +#include <Swiften/Network/PlatformNATTraversalRemovePortForwardingRequest.h> + +namespace Swift { + +PlatformNATTraversalWorker::PlatformNATTraversalWorker(EventLoop* eventLoop) : backendType(NotYetDecided), eventLoop(eventLoop), stopRequested(false) { + checkAvailableNATTraversalProtocols(); + thread = new boost::thread(boost::bind(&PlatformNATTraversalWorker::run, this)); +} + +PlatformNATTraversalWorker::~PlatformNATTraversalWorker() { + stopRequested = true; + addRequestToQueue(boost::shared_ptr<PlatformNATTraversalRequest>()); + thread->join(); + delete thread; +} + +boost::shared_ptr<PlatformNATTraversalGetPublicIPRequest> PlatformNATTraversalWorker::createGetPublicIPRequest() { + switch(backendType) { + case UPnP: + return boost::make_shared<UPnPNATTraversalGetPublicIPRequest>(this); + case NATPMP: + return boost::make_shared<NATPMPNATTraversalGetPublicIPRequest>(this); + case NotYetDecided: + case None: + break; + } + return boost::shared_ptr<PlatformNATTraversalGetPublicIPRequest>(); +} + +boost::shared_ptr<PlatformNATTraversalForwardPortRequest> PlatformNATTraversalWorker::createForwardPortRequest(unsigned int localPort, unsigned int publicPort) { + PlatformNATTraversalForwardPortRequest::PortMapping mapping; + mapping.protocol = PlatformNATTraversalForwardPortRequest::PortMapping::TCP; + mapping.leaseInSeconds = 60 * 60 * 24; + mapping.localPort = localPort; + mapping.publicPort = publicPort; + + switch(backendType) { + case UPnP: + return boost::make_shared<UPnPNATTraversalForwardPortRequest>(mapping, this); + case NATPMP: + return boost::make_shared<NATPMPNATTraversalForwardPortRequest>(mapping, this); + case NotYetDecided: + case None: + break; + } + return boost::shared_ptr<PlatformNATTraversalForwardPortRequest>(); +} + +boost::shared_ptr<PlatformNATTraversalRemovePortForwardingRequest> PlatformNATTraversalWorker::createRemovePortForwardingRequest(unsigned int localPort, unsigned int publicPort) { + PlatformNATTraversalRemovePortForwardingRequest::PortMapping mapping; + mapping.protocol = PlatformNATTraversalRemovePortForwardingRequest::PortMapping::TCP; + mapping.leaseInSeconds = 60 * 60 * 24; + mapping.localPort = localPort; + mapping.publicPort = publicPort; + + switch(backendType) { + case UPnP: + return boost::make_shared<UPnPNATTraversalRemovePortForwardingRequest>(mapping, this); + case NATPMP: + return boost::make_shared<NATPMPNATTraversalRemovePortForwardingRequest>(mapping, this); + case NotYetDecided: + case None: + break; + } + return boost::shared_ptr<PlatformNATTraversalRemovePortForwardingRequest>(); +} + +void PlatformNATTraversalWorker::run() { + while (!stopRequested) { + PlatformNATTraversalRequest::ref request; + { + boost::unique_lock<boost::mutex> lock(queueMutex); + while (queue.empty()) { + queueNonEmpty.wait(lock); + } + request = queue.front(); + queue.pop_front(); + } + // Check whether we don't have a non-null request (used to stop the + // worker) + if (request) { + request->runBlocking(); + } + } +} + +void PlatformNATTraversalWorker::addRequestToQueue(PlatformNATTraversalRequest::ref request) { + { + boost::lock_guard<boost::mutex> lock(queueMutex); + queue.push_back(request); + } + queueNonEmpty.notify_one(); +} + +void PlatformNATTraversalWorker::checkAvailableNATTraversalProtocols() { + boost::shared_ptr<UPnPNATTraversalGetPublicIPRequest> upnpRequest = boost::make_shared<UPnPNATTraversalGetPublicIPRequest>(this); + upnpRequest->onResult.connect(boost::bind(&PlatformNATTraversalWorker::handleUPnPGetPublicIPResult, this, _1)); + + boost::shared_ptr<NATPMPNATTraversalGetPublicIPRequest> natpmpRequest = boost::make_shared<NATPMPNATTraversalGetPublicIPRequest>(this); + natpmpRequest->onResult.connect(boost::bind(&PlatformNATTraversalWorker::handleNATPMPGetPublicIPResult, this, _1)); + + upnpRequest->run(); + natpmpRequest->run(); +} + +void PlatformNATTraversalWorker::handleUPnPGetPublicIPResult(boost::optional<HostAddress> address) { + if (backendType == NotYetDecided || backendType == None) { + if (address) { + SWIFT_LOG(debug) << "Found UPnP IGD in the local network." << std::endl; + backendType = UPnP; + } + } +} + +void PlatformNATTraversalWorker::handleNATPMPGetPublicIPResult(boost::optional<HostAddress> address) { + if (backendType == NotYetDecided || backendType == None) { + if (address) { + SWIFT_LOG(debug) << "Found NAT-PMP device in the local network." << std::endl; + backendType = NATPMP; + } + } +} + +} diff --git a/Swiften/Network/PlatformNATTraversalWorker.h b/Swiften/Network/PlatformNATTraversalWorker.h new file mode 100644 index 0000000..7c249cc --- /dev/null +++ b/Swiften/Network/PlatformNATTraversalWorker.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <deque> +#include <boost/optional.hpp> +#include <boost/thread/thread.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/thread/condition_variable.hpp> + +#include <Swiften/Network/HostAddressPort.h> +#include <Swiften/Network/PlatformNATTraversalRequest.h> + +namespace Swift { + +class EventLoop; +class PlatformNATTraversalGetPublicIPRequest; +class PlatformNATTraversalForwardPortRequest; +class PlatformNATTraversalRemovePortForwardingRequest; + +class PlatformNATTraversalWorker { +private: + enum BackendType { + NotYetDecided, + UPnP, + NATPMP, + None, + }; + +public: + PlatformNATTraversalWorker(EventLoop* eventLoop); + ~PlatformNATTraversalWorker(); + + boost::shared_ptr<PlatformNATTraversalGetPublicIPRequest> createGetPublicIPRequest(); + boost::shared_ptr<PlatformNATTraversalForwardPortRequest> createForwardPortRequest(unsigned int localPort, unsigned int publicPort); + boost::shared_ptr<PlatformNATTraversalRemovePortForwardingRequest> createRemovePortForwardingRequest(unsigned int localPort, unsigned int publicPort); + + void run(); + void addRequestToQueue(PlatformNATTraversalRequest::ref); + +private: + void checkAvailableNATTraversalProtocols(); + void handleUPnPGetPublicIPResult(boost::optional<HostAddress> address); + void handleNATPMPGetPublicIPResult(boost::optional<HostAddress> address); + +private: + BackendType backendType; + EventLoop* eventLoop; + bool stopRequested; + boost::thread* thread; + std::deque<PlatformNATTraversalRequest::ref> queue; + boost::mutex queueMutex; + boost::condition_variable queueNonEmpty; +}; + +} diff --git a/Swiften/Network/PlatformNetworkEnvironment.h b/Swiften/Network/PlatformNetworkEnvironment.h new file mode 100644 index 0000000..c6b945e --- /dev/null +++ b/Swiften/Network/PlatformNetworkEnvironment.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Base/Platform.h> + +#if defined(SWIFTEN_PLATFORM_MACOSX) +#include <Swiften/Network/UnixNetworkEnvironment.h> +namespace Swift { + typedef UnixNetworkEnvironment PlatformNetworkEnvironment; +} +#elif defined(SWIFTEN_PLATFORM_WIN32) +#include <Swiften/Network/WindowsNetworkEnvironment.h> +namespace Swift { + typedef WindowsNetworkEnvironment PlatformNetworkEnvironment; +} +#else +#include <Swiften/Network/UnixNetworkEnvironment.h> +namespace Swift { + typedef UnixNetworkEnvironment PlatformNetworkEnvironment; +} +#endif diff --git a/Swiften/Network/SConscript b/Swiften/Network/SConscript index 965361b..e9853dd 100644 --- a/Swiften/Network/SConscript +++ b/Swiften/Network/SConscript @@ -40,7 +40,18 @@ sourceList = [ "Timer.cpp", "BoostTimer.cpp", "ProxyProvider.cpp", - "NullProxyProvider.cpp" + "NullProxyProvider.cpp", + "PlatformNATTraversalWorker.cpp", + "PlatformNATTraversalGetPublicIPRequest.cpp", + "PlatformNATTraversalForwardPortRequest.cpp", + "PlatformNATTraversalRemovePortForwardingRequest.cpp", + "PlatformNATTraversalRequest.cpp", + "UPnPNATTraversalGetPublicIPRequest.cpp", + "UPnPNATTraversalForwardPortRequest.cpp", + "UPnPNATTraversalRemovePortForwardingRequest.cpp", + "NATPMPNATTraversalGetPublicIPRequest.cpp", + "NATPMPNATTraversalForwardPortRequest.cpp", + "NATPMPNATTraversalRemovePortForwardingRequest.cpp", ] if myenv.get("HAVE_CARES", False) : @@ -49,9 +60,12 @@ if myenv.get("HAVE_CARES", False) : if myenv["PLATFORM"] == "darwin" : myenv.Append(FRAMEWORKS = ["CoreServices", "SystemConfiguration"]) sourceList += [ "MacOSXProxyProvider.cpp" ] + sourceList += [ "UnixNetworkEnvironment.cpp" ] elif myenv["PLATFORM"] == "win32" : sourceList += [ "WindowsProxyProvider.cpp" ] + sourceList += [ "WindowsNetworkEnvironment.cpp" ] else : + sourceList += [ "UnixNetworkEnvironment.cpp" ] sourceList += [ "UnixProxyProvider.cpp" ] sourceList += [ "EnvironmentProxyProvider.cpp" ] if myenv.get("HAVE_GCONF", 0) : diff --git a/Swiften/Network/UPnPNATTraversalForwardPortRequest.cpp b/Swiften/Network/UPnPNATTraversalForwardPortRequest.cpp new file mode 100644 index 0000000..c95066e --- /dev/null +++ b/Swiften/Network/UPnPNATTraversalForwardPortRequest.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "UPnPNATTraversalForwardPortRequest.h" + +#include <boost/format.hpp> + +#include <miniupnpc.h> +#include <upnpcommands.h> +#include <upnperrors.h> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Network/NetworkInterface.h> +#include <Swiften/Network/PlatformNetworkEnvironment.h> + +namespace Swift { + +UPnPNATTraversalForwardPortRequest::UPnPNATTraversalForwardPortRequest(PlatformNATTraversalForwardPortRequest::PortMapping mapping, PlatformNATTraversalWorker* worker) : PlatformNATTraversalForwardPortRequest(worker), mapping(mapping) { + +} + +UPnPNATTraversalForwardPortRequest::~UPnPNATTraversalForwardPortRequest() { + +} + +void UPnPNATTraversalForwardPortRequest::runBlocking() { + boost::optional<PortMapping> result; + + UPNPDev* deviceList = 0; + int error = 0; + char lanAddrress[64]; + + std::string publicPort = str(boost::format("%d") % mapping.publicPort); + std::string localPort = str(boost::format("%d") % mapping.localPort); + std::string internalClient = getLocalClient().toString(); + std::string leaseSeconds = str(boost::format("%d") % mapping.leaseInSeconds); + UPNPUrls urls; + IGDdatas data; + + do { + // find valid IGD + deviceList = upnpDiscover(1500 /* timeout in ms */, 0, 0, 0, 0 /* do IPv6? */, &error); + if (!deviceList) { + break; + } + + if (!UPNP_GetValidIGD(deviceList, &urls, &data, lanAddrress, sizeof(lanAddrress))) { + break; + } + + /* + int ret = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, externalIPAddress); + if (ret != UPNPCOMMAND_SUCCESS) { + break; + }*/ + + int ret = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, publicPort.c_str(), localPort.c_str(), internalClient.c_str(), 0, mapping.protocol == PlatformNATTraversalForwardPortRequest::PortMapping::TCP ? "TCP" : "UDP", 0, leaseSeconds.c_str()); + if (ret == UPNPCOMMAND_SUCCESS) { + result = boost::optional<PlatformNATTraversalForwardPortRequest::PortMapping>(mapping); + } + } while(false); + + freeUPNPDevlist(deviceList); deviceList = 0; + + onResult(result); +} + +HostAddress UPnPNATTraversalForwardPortRequest::getLocalClient() { + PlatformNetworkEnvironment env; + + foreach (NetworkInterface::ref iface, env.getNetworkInterfaces()) { + if (!iface->isLoopback()) { + foreach (HostAddress address, iface->getAddresses()) { + if (address.getRawAddress().is_v4()) { + return address; + } + } + } + } + return HostAddress(); +} + +} diff --git a/Swiften/Network/UPnPNATTraversalForwardPortRequest.h b/Swiften/Network/UPnPNATTraversalForwardPortRequest.h new file mode 100644 index 0000000..931efee --- /dev/null +++ b/Swiften/Network/UPnPNATTraversalForwardPortRequest.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Network/PlatformNATTraversalForwardPortRequest.h> + +namespace Swift { + +class UPnPNATTraversalForwardPortRequest : public PlatformNATTraversalForwardPortRequest { +public: + UPnPNATTraversalForwardPortRequest(PlatformNATTraversalForwardPortRequest::PortMapping, PlatformNATTraversalWorker*); + virtual ~UPnPNATTraversalForwardPortRequest(); + + virtual void runBlocking(); + +private: + HostAddress getLocalClient(); + +private: + PlatformNATTraversalForwardPortRequest::PortMapping mapping; +}; + +} diff --git a/Swiften/Network/UPnPNATTraversalGetPublicIPRequest.cpp b/Swiften/Network/UPnPNATTraversalGetPublicIPRequest.cpp new file mode 100644 index 0000000..4a7c247 --- /dev/null +++ b/Swiften/Network/UPnPNATTraversalGetPublicIPRequest.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "UPnPNATTraversalGetPublicIPRequest.h" + +#include <miniupnpc.h> +#include <upnpcommands.h> +#include <upnperrors.h> + +namespace Swift { + +UPnPNATTraversalGetPublicIPRequest::UPnPNATTraversalGetPublicIPRequest(PlatformNATTraversalWorker* worker) : PlatformNATTraversalGetPublicIPRequest(worker) { + +} + +UPnPNATTraversalGetPublicIPRequest::~UPnPNATTraversalGetPublicIPRequest() { + +} + +void UPnPNATTraversalGetPublicIPRequest::runBlocking() { + boost::optional<HostAddress> result; + + UPNPDev* deviceList = 0; + int error = 0; + char lanAddrress[64]; + char externalIPAddress[40]; + UPNPUrls urls; + IGDdatas data; + + do { + // find valid IGD + deviceList = upnpDiscover(1500 /* timeout in ms */, 0, 0, 0, 0 /* do IPv6? */, &error); + if (!deviceList) { + break; + } + + if (!UPNP_GetValidIGD(deviceList, &urls, &data, lanAddrress, sizeof(lanAddrress))) { + break; + } + + int ret = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, externalIPAddress); + if (ret != UPNPCOMMAND_SUCCESS) { + break; + } else { + result = HostAddress(std::string(externalIPAddress)); + } + } while(false); + + freeUPNPDevlist(deviceList); deviceList = 0; + + onResult(result); +} + +} diff --git a/Swiften/Network/UPnPNATTraversalGetPublicIPRequest.h b/Swiften/Network/UPnPNATTraversalGetPublicIPRequest.h new file mode 100644 index 0000000..9d50001 --- /dev/null +++ b/Swiften/Network/UPnPNATTraversalGetPublicIPRequest.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Network/PlatformNATTraversalGetPublicIPRequest.h> + +namespace Swift { + +class UPnPNATTraversalGetPublicIPRequest : public PlatformNATTraversalGetPublicIPRequest { +public: + UPnPNATTraversalGetPublicIPRequest(PlatformNATTraversalWorker*); + virtual ~UPnPNATTraversalGetPublicIPRequest(); + + virtual void runBlocking(); +}; + +} diff --git a/Swiften/Network/UPnPNATTraversalRemovePortForwardingRequest.cpp b/Swiften/Network/UPnPNATTraversalRemovePortForwardingRequest.cpp new file mode 100644 index 0000000..2026880 --- /dev/null +++ b/Swiften/Network/UPnPNATTraversalRemovePortForwardingRequest.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "UPnPNATTraversalRemovePortForwardingRequest.h" + +#include <boost/format.hpp> + +#include <miniupnpc.h> +#include <upnpcommands.h> +#include <upnperrors.h> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/Log.h> +#include <Swiften/Network/NetworkInterface.h> +#include <Swiften/Network/PlatformNetworkEnvironment.h> + +namespace Swift { + +UPnPNATTraversalRemovePortForwardingRequest::UPnPNATTraversalRemovePortForwardingRequest(PlatformNATTraversalRemovePortForwardingRequest::PortMapping mapping, PlatformNATTraversalWorker* worker) : PlatformNATTraversalRemovePortForwardingRequest(worker), mapping(mapping) { + +} + +UPnPNATTraversalRemovePortForwardingRequest::~UPnPNATTraversalRemovePortForwardingRequest() { + +} + +void UPnPNATTraversalRemovePortForwardingRequest::runBlocking() { + boost::optional<bool> result; + + UPNPDev* deviceList = 0; + int error = 0; + char lanAddrress[64]; + + std::string publicPort = str(boost::format("%d") % mapping.publicPort); + std::string localPort = str(boost::format("%d") % mapping.localPort); + std::string internalClient = getLocalClient().toString(); + std::string leaseSeconds = str(boost::format("%d") % mapping.leaseInSeconds); + UPNPUrls urls; + IGDdatas data; + + do { + // find valid IGD + deviceList = upnpDiscover(1500 /* timeout in ms */, 0, 0, 0, 0 /* do IPv6? */, &error); + if (!deviceList) { + break; + } + + if (!UPNP_GetValidIGD(deviceList, &urls, &data, lanAddrress, sizeof(lanAddrress))) { + break; + } + + /* + int ret = UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, externalIPAddress); + if (ret != UPNPCOMMAND_SUCCESS) { + break; + }*/ + SWIFT_LOG(debug) << "Start removing port forwarding..." << std::endl; + int ret = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, publicPort.c_str(), mapping.protocol == PlatformNATTraversalRemovePortForwardingRequest::PortMapping::TCP ? "TCP" : "UDP", 0); + + if (ret == UPNPCOMMAND_SUCCESS) { + SWIFT_LOG(debug) << "Removing port " << publicPort << " successfull." << std::endl; + result = true; + } else { + SWIFT_LOG(debug) << "Removing port " << publicPort << " failed." << std::endl; + result = false; + } + } while(false); + + freeUPNPDevlist(deviceList); deviceList = 0; + + onResult(result); +} + +HostAddress UPnPNATTraversalRemovePortForwardingRequest::getLocalClient() { + PlatformNetworkEnvironment env; + + foreach (NetworkInterface::ref iface, env.getNetworkInterfaces()) { + if (!iface->isLoopback()) { + foreach (HostAddress address, iface->getAddresses()) { + if (address.getRawAddress().is_v4()) { + return address; + } + } + } + } + return HostAddress(); +} + +} diff --git a/Swiften/Network/UPnPNATTraversalRemovePortForwardingRequest.h b/Swiften/Network/UPnPNATTraversalRemovePortForwardingRequest.h new file mode 100644 index 0000000..ad1e019 --- /dev/null +++ b/Swiften/Network/UPnPNATTraversalRemovePortForwardingRequest.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Network/PlatformNATTraversalRemovePortForwardingRequest.h> + +namespace Swift { + +class UPnPNATTraversalRemovePortForwardingRequest : public PlatformNATTraversalRemovePortForwardingRequest { +public: + UPnPNATTraversalRemovePortForwardingRequest(PlatformNATTraversalRemovePortForwardingRequest::PortMapping, PlatformNATTraversalWorker*); + virtual ~UPnPNATTraversalRemovePortForwardingRequest(); + + virtual void runBlocking(); + +private: + HostAddress getLocalClient(); + +private: + PlatformNATTraversalRemovePortForwardingRequest::PortMapping mapping; +}; + +} diff --git a/Swiften/Network/UnixNetworkEnvironment.cpp b/Swiften/Network/UnixNetworkEnvironment.cpp new file mode 100644 index 0000000..649855d --- /dev/null +++ b/Swiften/Network/UnixNetworkEnvironment.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "UnixNetworkEnvironment.h" + +#include <string> +#include <vector> +#include <map> + +#include <boost/shared_ptr.hpp> +#include <boost/smart_ptr/make_shared.hpp> +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/Network/HostAddress.h> +#include <Swiften/Network/NetworkInterface.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <ifaddrs.h> + +namespace Swift { + +std::vector<NetworkInterface::ref> UnixNetworkEnvironment::getNetworkInterfaces() { + std::map<std::string, UnixNetworkInterface::ref> interfaces; + std::vector<NetworkInterface::ref> result; + + ifaddrs *addrs = 0; + int ret = getifaddrs(&addrs); + if (ret != 0) { + return result; + } + + for (ifaddrs *a = addrs; a != 0; a = a->ifa_next) { + std::string name(a->ifa_name); + std::string ip; + if (a->ifa_addr->sa_family == PF_INET) { + sockaddr_in* sa = reinterpret_cast<sockaddr_in*>(a->ifa_addr); + char str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &(sa->sin_addr), str, INET_ADDRSTRLEN); + ip.assign(str); + } + else if (a->ifa_addr->sa_family == PF_INET6) { + sockaddr_in6* sa = reinterpret_cast<sockaddr_in6*>(a->ifa_addr); + char str[INET6_ADDRSTRLEN]; + /*if (IN6_IS_ADDR_LINKLOCAL(sa)) { + continue; + }*/ + inet_ntop(AF_INET6, &(sa->sin6_addr), str, INET6_ADDRSTRLEN); + ip.assign(str); + } + if (!ip.empty()) { + if (interfaces.find(name) == interfaces.end()) { + interfaces[name] = boost::make_shared<UnixNetworkInterface>(name); + if (a->ifa_flags & IFF_LOOPBACK) { + interfaces[name]->setLoopback(true); + } + } + interfaces[name]->addHostAddress(HostAddress(ip)); + } + } + + freeifaddrs(addrs); + + for(std::map<std::string, UnixNetworkInterface::ref>::iterator i = interfaces.begin(); i != interfaces.end(); ++i) { + result.push_back(i->second); + } + return result; +} + +} diff --git a/Swiften/Network/UnixNetworkEnvironment.h b/Swiften/Network/UnixNetworkEnvironment.h new file mode 100644 index 0000000..e4b2f37 --- /dev/null +++ b/Swiften/Network/UnixNetworkEnvironment.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <vector> + +#include <Swiften/Base/boost_bsignals.h> + +#include <Swiften/Network/NetworkEnvironment.h> +#include <Swiften/Network/NetworkInterface.h> + +namespace Swift { + +class UnixNetworkEnvironment : public NetworkEnvironment { + class UnixNetworkInterface : public NetworkInterface { + public: + typedef boost::shared_ptr<UnixNetworkInterface> ref; + + public: + UnixNetworkInterface(std::string name) : name(name), loopback(false) { } + + std::vector<HostAddress> getAddresses() { + return addresses; + } + + std::string getName() { + return name; + } + + bool isLoopback() { + return loopback; + } + + private: + void addHostAddress(HostAddress address) { + addresses.push_back(address); + } + + void setLoopback(bool loopback) { + this->loopback = loopback; + } + + private: + friend class UnixNetworkEnvironment; + std::vector<HostAddress> addresses; + std::string name; + InterfaceType type; + bool loopback; + }; + +public: + std::vector<NetworkInterface::ref> getNetworkInterfaces(); +}; + +} diff --git a/Swiften/Network/WindowsNetworkEnvironment.cpp b/Swiften/Network/WindowsNetworkEnvironment.cpp new file mode 100644 index 0000000..5163f43 --- /dev/null +++ b/Swiften/Network/WindowsNetworkEnvironment.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "WindowsNetworkEnvironment.h" + +#include <string> +#include <vector> +#include <map> + +#include <boost/shared_ptr.hpp> +#include <boost/smart_ptr/make_shared.hpp> +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/Network/HostAddress.h> +#include <Swiften/Network/NetworkInterface.h> + +#include <winsock2.h> +#include <iphlpapi.h> + +namespace Swift { + +std::string winSocketAddressToStdString(const SOCKET_ADDRESS& socketAddress) { + char text[46]; + ULONG bufferSize = sizeof(text); + std::string result; + + int ret = WSAAddressToString(socketAddress.lpSockaddr, socketAddress.iSockaddrLength, NULL, text, &bufferSize); + if (ret == 0) { + result.assign(text, sizeof(text)); + } + return result; +} + +std::vector<NetworkInterface::ref> WindowsNetworkEnvironment::getNetworkInterfaces() { + std::map<std::string, WindowsNetworkInterface::ref> interfaces; + std::vector<NetworkInterface::ref> result; + + IP_ADAPTER_ADDRESSES preBuffer[5]; + PIP_ADAPTER_ADDRESSES adapterStart = preBuffer; + + ULONG bufferSize = sizeof(preBuffer); + + ULONG flags = GAA_FLAG_INCLUDE_ALL_INTERFACES | GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER; + + ULONG ret = GetAdaptersAddresses( AF_UNSPEC, flags, NULL, adapterStart, &bufferSize); + if (ret == ERROR_BUFFER_OVERFLOW) { + adapterStart = new IP_ADAPTER_ADDRESSES[bufferSize / sizeof(IP_ADAPTER_ADDRESSES)]; + if (!adapterStart) { + return result; + } + ret = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, adapterStart, &bufferSize); + } + if (ret != ERROR_SUCCESS) { + if (adapterStart != preBuffer) { + delete adapterStart; + } + return result; + } + + for (PIP_ADAPTER_ADDRESSES adapter = adapterStart; adapter; adapter = adapter->Next) { + std::string name(adapter->AdapterName); + + if (adapter->OperStatus != IfOperStatusUp) { + continue; + } + + // iterate over addresses + for (PIP_ADAPTER_UNICAST_ADDRESS address = adapter->FirstUnicastAddress; address; address = address->Next) { + std::string ip; + + if (address->Address.lpSockaddr->sa_family == PF_INET || + address->Address.lpSockaddr->sa_family == PF_INET6) { + ip = winSocketAddressToStdString(address->Address); + if (!ip.empty()) { + if (interfaces.find(name) == interfaces.end()) { + interfaces[name] = boost::make_shared<WindowsNetworkInterface>(); + interfaces[name]->setName(name); + } + interfaces[name]->addHostAddress(HostAddress(ip)); + } + } + } + } + + if (adapterStart != preBuffer) { + //delete adapterStart; + } + + for(std::map<std::string, WindowsNetworkInterface::ref>::iterator i = interfaces.begin(); i != interfaces.end(); ++i) { + result.push_back(i->second); + } + return result; +} + +} diff --git a/Swiften/Network/WindowsNetworkEnvironment.h b/Swiften/Network/WindowsNetworkEnvironment.h new file mode 100644 index 0000000..2b79504 --- /dev/null +++ b/Swiften/Network/WindowsNetworkEnvironment.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <vector> + +#include <Swiften/Base/boost_bsignals.h> + +#include <Swiften/Network/NetworkEnvironment.h> +#include <Swiften/Network/NetworkInterface.h> + +namespace Swift { + +class WindowsNetworkEnvironment : public NetworkEnvironment { + class WindowsNetworkInterface : public NetworkInterface { + public: + typedef boost::shared_ptr<WindowsNetworkInterface> ref; + + public: + virtual ~WindowsNetworkInterface() { } + virtual std::vector<HostAddress> getAddresses() { + return addresses; + } + + virtual std::string getName() { + return name; + } + + virtual bool isLoopback() { + return false; + } + + public: + void addHostAddress(HostAddress address) { + addresses.push_back(address); + } + + void setName(const std::string& name) { + this->name = name; + } + + private: + std::vector<HostAddress> addresses; + InterfaceType type; + std::string name; + }; + +public: + std::vector<NetworkInterface::ref> getNetworkInterfaces(); +}; + +} diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp index 1b59f6f..9eafcba 100644 --- a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp +++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp @@ -48,6 +48,19 @@ #include <Swiften/Parser/PayloadParsers/NicknameParserFactory.h> #include <Swiften/Parser/PayloadParsers/ReplaceParser.h> #include <Swiften/Parser/PayloadParsers/LastParser.h> +#include <Swiften/Parser/PayloadParsers/JingleParserFactory.h> +#include <Swiften/Parser/PayloadParsers/JingleReasonParser.h> +#include <Swiften/Parser/PayloadParsers/JingleContentPayloadParserFactory.h> +#include <Swiften/Parser/PayloadParsers/JingleIBBTransportMethodPayloadParser.h> +#include <Swiften/Parser/PayloadParsers/JingleS5BTransportMethodPayloadParser.h> +#include <Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParserFactory.h> +#include <Swiften/Parser/PayloadParsers/StreamInitiationFileInfoParser.h> +#include <Swiften/Parser/PayloadParsers/JingleFileTransferReceivedParser.h> +#include <Swiften/Parser/PayloadParsers/JingleFileTransferHashParser.h> +#include <Swiften/Parser/PayloadParsers/S5BProxyRequestParser.h> + +#include "JingleIBBTransportMethodPayloadParser.h" +#include "JingleFileTransferDescriptionParser.h" using namespace boost; @@ -91,6 +104,16 @@ FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() { factories_.push_back(shared_ptr<PayloadParserFactory>(new MUCUserPayloadParserFactory())); factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<MUCAdminPayloadParser>("query", "http://jabber.org/protocol/muc#admin"))); factories_.push_back(shared_ptr<PayloadParserFactory>(new NicknameParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new JingleParserFactory(this))); + factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<JingleReasonParser>("reason", "urn:xmpp:jingle:1"))); + factories_.push_back(shared_ptr<PayloadParserFactory>(new JingleContentPayloadParserFactory(this))); + factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<JingleIBBTransportMethodPayloadParser>("transport", "urn:xmpp:jingle:transports:ibb:1"))); + factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<JingleS5BTransportMethodPayloadParser>("transport", "urn:xmpp:jingle:transports:s5b:1"))); + factories_.push_back(shared_ptr<PayloadParserFactory>(new JingleFileTransferDescriptionParserFactory(this))); + factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<StreamInitiationFileInfoParser>("file", "http://jabber.org/protocol/si/profile/file-transfer"))); + factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<JingleFileTransferReceivedParser>("received", "urn:xmpp:jingle:apps:file-transfer:3"))); + factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<JingleFileTransferHashParser>("checksum"))); + factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<S5BProxyRequestParser>("query", "http://jabber.org/protocol/bytestreams"))); foreach(shared_ptr<PayloadParserFactory> factory, factories_) { addFactory(factory.get()); } diff --git a/Swiften/Parser/PayloadParsers/IBBParser.cpp b/Swiften/Parser/PayloadParsers/IBBParser.cpp index 2705c75..20a1ce9 100644 --- a/Swiften/Parser/PayloadParsers/IBBParser.cpp +++ b/Swiften/Parser/PayloadParsers/IBBParser.cpp @@ -60,7 +60,7 @@ void IBBParser::handleEndElement(const std::string& element, const std::string&) std::vector<char> data; for (size_t i = 0; i < currentText.size(); ++i) { char c = currentText[i]; - if (c >= 48 && c <= 122) { + if ((c >= 48 && c <= 122) || c == 47 || c == 43) { data.push_back(c); } } diff --git a/Swiften/Parser/PayloadParsers/JingleContentPayloadParser.cpp b/Swiften/Parser/PayloadParsers/JingleContentPayloadParser.cpp new file mode 100644 index 0000000..1431b9e --- /dev/null +++ b/Swiften/Parser/PayloadParsers/JingleContentPayloadParser.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "JingleContentPayloadParser.h" +#include <Swiften/Parser/PayloadParserFactoryCollection.h> +#include <Swiften/Parser/PayloadParserFactory.h> +#include <Swiften/Elements/JinglePayload.h> + +#include <Swiften/Base/Log.h> + +namespace Swift { + JingleContentPayloadParser::JingleContentPayloadParser(PayloadParserFactoryCollection* factories) : factories(factories), level(0) { + + } + + void JingleContentPayloadParser::handleStartElement(const std::string& element, const std::string& ns, const AttributeMap& attributes) { + if (level == 0) { + std::string creator = attributes.getAttributeValue("creator").get_value_or(""); + if (creator == "initiator") { + getPayloadInternal()->setCreator(JingleContentPayload::InitiatorCreator); + } else if (creator == "responder") { + getPayloadInternal()->setCreator(JingleContentPayload::ResponderCreator); + } else { + getPayloadInternal()->setCreator(JingleContentPayload::UnknownCreator); + } + + getPayloadInternal()->setName(attributes.getAttributeValue("name").get_value_or("")); + } + + if (level == 1) { + PayloadParserFactory* payloadParserFactory = factories->getPayloadParserFactory(element, ns, attributes); + if (payloadParserFactory) { + currentPayloadParser.reset(payloadParserFactory->createPayloadParser()); + } + } + + if (level >= 1 && currentPayloadParser) { + currentPayloadParser->handleStartElement(element, ns, attributes); + } + + ++level; + } + + void JingleContentPayloadParser::handleEndElement(const std::string& element, const std::string& ns) { + --level; + + if (currentPayloadParser) { + if (level >= 1) { + currentPayloadParser->handleEndElement(element, ns); + } + + if (level == 1) { + boost::shared_ptr<JingleTransportPayload> transport = boost::dynamic_pointer_cast<JingleTransportPayload>(currentPayloadParser->getPayload()); + if (transport) { + getPayloadInternal()->addTransport(transport); + } + + boost::shared_ptr<JingleDescription> description = boost::dynamic_pointer_cast<JingleDescription>(currentPayloadParser->getPayload()); + if (description) { + getPayloadInternal()->addDescription(description); + } + } + } + } + + void JingleContentPayloadParser::handleCharacterData(const std::string& data) { + if (level > 1 && currentPayloadParser) { + currentPayloadParser->handleCharacterData(data); + } + } +} diff --git a/Swiften/Parser/PayloadParsers/JingleContentPayloadParser.h b/Swiften/Parser/PayloadParsers/JingleContentPayloadParser.h new file mode 100644 index 0000000..a871cc4 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/JingleContentPayloadParser.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Elements/JingleContentPayload.h> +#include <Swiften/Parser/GenericPayloadParser.h> + +namespace Swift { + +class PayloadParserFactoryCollection; + +class JingleContentPayloadParser : public GenericPayloadParser<JingleContentPayload> { + public: + JingleContentPayloadParser(PayloadParserFactoryCollection* factories); + + virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes); + virtual void handleEndElement(const std::string& element, const std::string&); + virtual void handleCharacterData(const std::string& data); + + private: + PayloadParserFactoryCollection* factories; + int level; + boost::shared_ptr<PayloadParser> currentPayloadParser; +}; + +} diff --git a/Swiften/Parser/PayloadParsers/JingleContentPayloadParserFactory.h b/Swiften/Parser/PayloadParsers/JingleContentPayloadParserFactory.h new file mode 100644 index 0000000..6d66e74 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/JingleContentPayloadParserFactory.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Parser/GenericPayloadParserFactory.h> +#include <Swiften/Parser/PayloadParsers/JingleContentPayloadParser.h> + +namespace Swift { + + class PayloadParserFactoryCollection; + + class JingleContentPayloadParserFactory : public PayloadParserFactory { + public: + JingleContentPayloadParserFactory(PayloadParserFactoryCollection* factories) : factories(factories) { + } + + virtual bool canParse(const std::string& element, const std::string& ns, const AttributeMap&) const { + return element == "content" && ns == "urn:xmpp:jingle:1"; + } + + virtual PayloadParser* createPayloadParser() { + return new JingleContentPayloadParser(factories); + } + + private: + PayloadParserFactoryCollection* factories; + + }; +} + + diff --git a/Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParser.cpp b/Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParser.cpp new file mode 100644 index 0000000..b394115 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParser.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "JingleFileTransferDescriptionParser.h" + +#include <Swiften/Parser/PayloadParserFactoryCollection.h> +#include <Swiften/Parser/PayloadParserFactory.h> +#include <Swiften/Base/Log.h> + +namespace Swift { + +JingleFileTransferDescriptionParser::JingleFileTransferDescriptionParser(PayloadParserFactoryCollection* factories) : factories(factories), level(0), + currentElement(UnknownElement) { + +} + +void JingleFileTransferDescriptionParser::handleStartElement(const std::string& element, const std::string& ns, const AttributeMap& attributes) { + if (level == 0) { + + } + + if (level == 1) { + if (element == "offer") { + currentElement = OfferElement; + } else if (element == "request") { + currentElement = RequestElement; + } else { + currentElement = UnknownElement; + } + } + + if (level == 2) { + PayloadParserFactory* payloadParserFactory = factories->getPayloadParserFactory(element, ns, attributes); + if (payloadParserFactory) { + currentPayloadParser.reset(payloadParserFactory->createPayloadParser()); + } + } + + if (level >= 2 && currentPayloadParser) { + currentPayloadParser->handleStartElement(element, ns, attributes); + } + + ++level; +} + +void JingleFileTransferDescriptionParser::handleEndElement(const std::string& element, const std::string& ns) { + --level; + if (currentPayloadParser) { + if (level >= 2) { + currentPayloadParser->handleEndElement(element, ns); + } + + if (level == 2) { + boost::shared_ptr<StreamInitiationFileInfo> info = boost::dynamic_pointer_cast<StreamInitiationFileInfo>(currentPayloadParser->getPayload()); + if (info) { + if (currentElement == OfferElement) { + getPayloadInternal()->addOffer(*info); + } else if (currentElement == RequestElement) { + getPayloadInternal()->addRequest(*info); + } + } + } + } +} + +void JingleFileTransferDescriptionParser::handleCharacterData(const std::string& data) { + if (level >= 2 && currentPayloadParser) { + currentPayloadParser->handleCharacterData(data); + } +} + +} diff --git a/Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParser.h b/Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParser.h new file mode 100644 index 0000000..93560c6 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParser.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Elements/JingleFileTransferDescription.h> +#include <Swiften/Parser/GenericPayloadParser.h> +#include <Swiften/Parser/PayloadParser.h> + +namespace Swift { + +class PayloadParserFactoryCollection; + +class JingleFileTransferDescriptionParser : public GenericPayloadParser<JingleFileTransferDescription> { + public: + JingleFileTransferDescriptionParser(PayloadParserFactoryCollection* factories); + + virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes); + virtual void handleEndElement(const std::string& element, const std::string&); + virtual void handleCharacterData(const std::string& data); + + private: + enum CurrentParseElement { + UnknownElement, + RequestElement, + OfferElement, + }; + + PayloadParserFactoryCollection* factories; + int level; + CurrentParseElement currentElement; + boost::shared_ptr<PayloadParser> currentPayloadParser; +}; + +} diff --git a/Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParserFactory.h b/Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParserFactory.h new file mode 100644 index 0000000..b997c1d --- /dev/null +++ b/Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParserFactory.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Parser/GenericPayloadParserFactory.h> +#include <Swiften/Parser/PayloadParsers/JingleFileTransferDescriptionParser.h> + +namespace Swift { + + class PayloadParserFactoryCollection; + + class JingleFileTransferDescriptionParserFactory : public PayloadParserFactory { + public: + JingleFileTransferDescriptionParserFactory(PayloadParserFactoryCollection* factories) : factories(factories) { + } + + virtual bool canParse(const std::string& element, const std::string& ns, const AttributeMap&) const { + return element == "description" && ns == "urn:xmpp:jingle:apps:file-transfer:3"; + } + + virtual PayloadParser* createPayloadParser() { + return new JingleFileTransferDescriptionParser(factories); + } + + private: + PayloadParserFactoryCollection* factories; + + }; +} + + diff --git a/Swiften/Parser/PayloadParsers/JingleFileTransferHashParser.cpp b/Swiften/Parser/PayloadParsers/JingleFileTransferHashParser.cpp new file mode 100644 index 0000000..87f8317 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/JingleFileTransferHashParser.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "JingleFileTransferHashParser.h" + +#include <boost/shared_ptr.hpp> +#include <boost/algorithm/string.hpp> + +#include <Swiften/Parser/PayloadParsers/StreamInitiationFileInfoParser.h> +#include <Swiften/Parser/GenericPayloadParserFactory.h> +#include <Swiften/Parser/PayloadParserFactory.h> + +namespace Swift { + +JingleFileTransferHashParser::JingleFileTransferHashParser() { +} + +void JingleFileTransferHashParser::handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes) { + if (element == "hash") { + algo = attributes.getAttribute("algo"); + } +} + +void JingleFileTransferHashParser::handleEndElement(const std::string& element, const std::string& ) { + if (element == "hash" && !algo.empty() && !hash.empty()) { + getPayloadInternal()->setHash(algo, hash); + algo.clear(); + hash.clear(); + } +} + +void JingleFileTransferHashParser::handleCharacterData(const std::string& data) { + if (!algo.empty()) { + std::string new_data(data); + boost::trim(new_data); + hash += new_data; + } +} + +} diff --git a/Swiften/Parser/PayloadParsers/JingleFileTransferHashParser.h b/Swiften/Parser/PayloadParsers/JingleFileTransferHashParser.h new file mode 100644 index 0000000..35e4a05 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/JingleFileTransferHashParser.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Elements/JingleFileTransferHash.h> +#include <Swiften/Parser/GenericPayloadParser.h> + +namespace Swift { + +class JingleFileTransferHashParser : public GenericPayloadParser<JingleFileTransferHash> { +public: + JingleFileTransferHashParser(); + + virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes); + virtual void handleEndElement(const std::string& element, const std::string&); + virtual void handleCharacterData(const std::string& data); + +private: + std::string algo; + std::string hash; +}; + +} diff --git a/Swiften/Parser/PayloadParsers/JingleFileTransferReceivedParser.cpp b/Swiften/Parser/PayloadParsers/JingleFileTransferReceivedParser.cpp new file mode 100644 index 0000000..20ad73e --- /dev/null +++ b/Swiften/Parser/PayloadParsers/JingleFileTransferReceivedParser.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "JingleFileTransferReceivedParser.h" +#include "StreamInitiationFileInfoParser.h" + +#include <boost/shared_ptr.hpp> +#include <Swiften/Parser/PayloadParsers/StreamInitiationFileInfoParser.h> +#include <Swiften/Parser/GenericPayloadParserFactory.h> +#include <Swiften/Parser/PayloadParserFactory.h> + +namespace Swift { + +JingleFileTransferReceivedParser::JingleFileTransferReceivedParser() : level(0) { +} + +void JingleFileTransferReceivedParser::handleStartElement(const std::string& element, const std::string& ns, const AttributeMap& attributes) { + if (level == 1 && element == "file") { + PayloadParserFactory* payloadParserFactory = new GenericPayloadParserFactory<StreamInitiationFileInfoParser>("file", "http://jabber.org/protocol/si/profile/file-transfer"); + if (payloadParserFactory) { + currentPayloadParser.reset(payloadParserFactory->createPayloadParser()); + } + } + + if (currentPayloadParser && level >= 1) { + currentPayloadParser->handleStartElement(element, ns, attributes); + } + + ++level; +} + +void JingleFileTransferReceivedParser::handleEndElement(const std::string& element, const std::string& ) { + --level; + if (element == "file") { + boost::shared_ptr<StreamInitiationFileInfo> fileInfo = boost::dynamic_pointer_cast<StreamInitiationFileInfo>(currentPayloadParser->getPayload()); + if (fileInfo) { + getPayloadInternal()->setFileInfo(*fileInfo); + } + } +} + +void JingleFileTransferReceivedParser::handleCharacterData(const std::string& ) { + +} + +} diff --git a/Swiften/Parser/PayloadParsers/JingleFileTransferReceivedParser.h b/Swiften/Parser/PayloadParsers/JingleFileTransferReceivedParser.h new file mode 100644 index 0000000..824b06d --- /dev/null +++ b/Swiften/Parser/PayloadParsers/JingleFileTransferReceivedParser.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Elements/JingleFileTransferReceived.h> +#include <Swiften/Parser/GenericPayloadParser.h> + +namespace Swift { + +class JingleFileTransferReceivedParser : public GenericPayloadParser<JingleFileTransferReceived> { +public: + JingleFileTransferReceivedParser(); + + virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes); + virtual void handleEndElement(const std::string& element, const std::string&); + virtual void handleCharacterData(const std::string& data); + +private: + boost::shared_ptr<PayloadParser> currentPayloadParser; + int level; +}; + +}
\ No newline at end of file diff --git a/Swiften/Parser/PayloadParsers/JingleIBBTransportMethodPayloadParser.cpp b/Swiften/Parser/PayloadParsers/JingleIBBTransportMethodPayloadParser.cpp new file mode 100644 index 0000000..a3dfd12 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/JingleIBBTransportMethodPayloadParser.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <boost/lexical_cast.hpp> +#include <boost/optional.hpp> + +#include "JingleIBBTransportMethodPayloadParser.h" + +#include <Swiften/Base/Log.h> + +namespace Swift { + JingleIBBTransportMethodPayloadParser::JingleIBBTransportMethodPayloadParser() : level(0) { + + } + + void JingleIBBTransportMethodPayloadParser::handleStartElement(const std::string&, const std::string&, const AttributeMap& attributes) { + try { + getPayloadInternal()->setBlockSize(boost::lexical_cast<int>(attributes.getAttributeValue("block-size").get_value_or("0"))); + } catch (boost::bad_lexical_cast &) { + getPayloadInternal()->setBlockSize(0); + } + getPayloadInternal()->setSessionID(attributes.getAttributeValue("sid").get_value_or("")); + ++level; + } + + void JingleIBBTransportMethodPayloadParser::handleEndElement(const std::string&, const std::string&) { + --level; + + + } + + void JingleIBBTransportMethodPayloadParser::handleCharacterData(const std::string&) { + + } +} diff --git a/Swiften/Parser/PayloadParsers/JingleIBBTransportMethodPayloadParser.h b/Swiften/Parser/PayloadParsers/JingleIBBTransportMethodPayloadParser.h new file mode 100644 index 0000000..311cc5b --- /dev/null +++ b/Swiften/Parser/PayloadParsers/JingleIBBTransportMethodPayloadParser.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Elements/JingleIBBTransportPayload.h> +#include <Swiften/Parser/GenericPayloadParser.h> + +namespace Swift { + +class JingleIBBTransportMethodPayloadParser : public GenericPayloadParser<JingleIBBTransportPayload> { + public: + JingleIBBTransportMethodPayloadParser(); + + virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes); + virtual void handleEndElement(const std::string& element, const std::string&); + virtual void handleCharacterData(const std::string& data); + + private: + int level; +}; + +} diff --git a/Swiften/Parser/PayloadParsers/JingleParser.cpp b/Swiften/Parser/PayloadParsers/JingleParser.cpp new file mode 100644 index 0000000..dd34458 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/JingleParser.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swiften/Parser/PayloadParsers/JingleParser.h> +#include <Swiften/Parser/PayloadParserFactory.h> +#include <Swiften/Elements/JingleContentPayload.h> +#include <Swiften/Elements/JingleFileTransferReceived.h> +#include <Swiften/Elements/JingleFileTransferHash.h> +#include <Swiften/Base/Log.h> + +#include <boost/intrusive_ptr.hpp> + +namespace Swift { + + JingleParser::JingleParser(PayloadParserFactoryCollection* factories) : factories(factories), level(0) { + + } + + void JingleParser::handleStartElement(const std::string& element, const std::string &ns, const AttributeMap& attributes) { + if (level == 0) { + // <jingle > tag + JinglePayload::ref payload = getPayloadInternal(); + payload->setAction(stringToAction(attributes.getAttributeValue("action").get_value_or(""))); + payload->setInitiator(JID(attributes.getAttributeValue("initiator").get_value_or(""))); + payload->setResponder(JID(attributes.getAttributeValue("responder").get_value_or(""))); + payload->setSessionID(attributes.getAttributeValue("sid").get_value_or("")); + } + + if (level == 1) { + PayloadParserFactory* payloadParserFactory = factories->getPayloadParserFactory(element, ns, attributes); + if (payloadParserFactory) { + currentPayloadParser.reset(payloadParserFactory->createPayloadParser()); + } + } + + if (level >= 1 && currentPayloadParser) { + currentPayloadParser->handleStartElement(element, ns, attributes); + } + + ++level; + } + + void JingleParser::handleEndElement(const std::string& element, const std::string &ns) { + --level; + if (currentPayloadParser) { + if (level >= 1) { + currentPayloadParser->handleEndElement(element, ns); + } + + if (level == 1) { + boost::shared_ptr<JinglePayload::Reason> reason = boost::dynamic_pointer_cast<JinglePayload::Reason>(currentPayloadParser->getPayload()); + if (reason) { + getPayloadInternal()->setReason(*reason); + } + + boost::shared_ptr<JingleContentPayload> payload = boost::dynamic_pointer_cast<JingleContentPayload>(currentPayloadParser->getPayload()); + if (payload) { + getPayloadInternal()->addContent(payload); + } + + boost::shared_ptr<JingleFileTransferReceived> received = boost::dynamic_pointer_cast<JingleFileTransferReceived>(currentPayloadParser->getPayload()); + if (received) { + getPayloadInternal()->addPayload(received); + } + + boost::shared_ptr<JingleFileTransferHash> hash = boost::dynamic_pointer_cast<JingleFileTransferHash>(currentPayloadParser->getPayload()); + if (hash) { + getPayloadInternal()->addPayload(hash); + } + } + } + } + + void JingleParser::handleCharacterData(const std::string& data) { + if (level > 1 && currentPayloadParser) { + currentPayloadParser->handleCharacterData(data); + } + } + + JinglePayload::Action JingleParser::stringToAction(const std::string &str) const { + if (str == "content-accept") { + return JinglePayload::ContentAccept; + } else if (str == "content-add") { + return JinglePayload::ContentAdd; + } else if (str == "content-modify") { + return JinglePayload::ContentModify; + } else if (str == "content-reject") { + return JinglePayload::ContentReject; + } else if (str == "content-remove") { + return JinglePayload::ContentRemove; + } else if (str == "description-info") { + return JinglePayload::DescriptionInfo; + } else if (str == "security-info") { + return JinglePayload::SecurityInfo; + } else if (str == "session-accept") { + return JinglePayload::SessionAccept; + } else if (str == "session-info") { + return JinglePayload::SessionInfo; + } else if (str == "session-initiate") { + return JinglePayload::SessionInitiate; + } else if (str == "session-terminate") { + return JinglePayload::SessionTerminate; + } else if (str == "transport-accept") { + return JinglePayload::TransportAccept; + } else if (str == "transport-info") { + return JinglePayload::TransportInfo; + } else if (str == "transport-reject") { + return JinglePayload::TransportReject; + } else if (str == "transport-replace") { + return JinglePayload::TransportReplace; + } else { + return JinglePayload::UnknownAction; + } + + } +} diff --git a/Swiften/Parser/PayloadParsers/JingleParser.h b/Swiften/Parser/PayloadParsers/JingleParser.h new file mode 100644 index 0000000..ecaca3c --- /dev/null +++ b/Swiften/Parser/PayloadParsers/JingleParser.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Elements/JinglePayload.h> +#include <Swiften/Parser/GenericPayloadParser.h> +#include <Swiften/Parser/PayloadParserFactoryCollection.h> + +namespace Swift { + +class JingleParser : public GenericPayloadParser<JinglePayload> { + public: + JingleParser(PayloadParserFactoryCollection* factories); + + virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes); + virtual void handleEndElement(const std::string& element, const std::string&); + virtual void handleCharacterData(const std::string& data); + + private: + JinglePayload::Action stringToAction(const std::string &str) const; + + private: + PayloadParserFactoryCollection* factories; + int level; + boost::shared_ptr<PayloadParser> currentPayloadParser; +}; + +};
\ No newline at end of file diff --git a/Swiften/Parser/PayloadParsers/JingleParserFactory.h b/Swiften/Parser/PayloadParsers/JingleParserFactory.h new file mode 100644 index 0000000..fa25aeb --- /dev/null +++ b/Swiften/Parser/PayloadParsers/JingleParserFactory.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Parser/GenericPayloadParserFactory.h> +#include <Swiften/Parser/PayloadParsers/JingleParser.h> + +namespace Swift { + + class PayloadParserFactoryCollection; + + class JingleParserFactory : public PayloadParserFactory { + public: + JingleParserFactory(PayloadParserFactoryCollection* factories) : factories(factories) { + } + + virtual bool canParse(const std::string& element, const std::string& ns, const AttributeMap&) const { + return element == "jingle" && ns == "urn:xmpp:jingle:1"; + } + + virtual PayloadParser* createPayloadParser() { + return new JingleParser(factories); + } + + private: + PayloadParserFactoryCollection* factories; + + }; +} + + diff --git a/Swiften/Parser/PayloadParsers/JingleReasonParser.cpp b/Swiften/Parser/PayloadParsers/JingleReasonParser.cpp new file mode 100644 index 0000000..3df82ae --- /dev/null +++ b/Swiften/Parser/PayloadParsers/JingleReasonParser.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "JingleReasonParser.h" + +#include <Swiften/Base/Log.h> + +namespace Swift { + JingleReasonParser::JingleReasonParser() : level(0), parseText(false) { + + } + + void JingleReasonParser::handleStartElement(const std::string& element, const std::string&, const AttributeMap&) { + if (level == 1) { + if (element == "text") { + parseText = true; + } else { + // reason type + getPayloadInternal()->type = stringToReasonType(element); + } + } + ++level; + } + + void JingleReasonParser::handleEndElement(const std::string& element, const std::string&) { + --level; + if (element == "text") { + parseText = false; + getPayloadInternal()->text = text; + } + } + + void JingleReasonParser::handleCharacterData(const std::string& data) { + if (parseText) { + text += data; + } + } + + JinglePayload::Reason::Type JingleReasonParser::stringToReasonType(const std::string& type) const { + if (type == "alternative-session") { + return JinglePayload::Reason::AlternativeSession; + } else if (type == "busy") { + return JinglePayload::Reason::Busy; + } else if (type == "cancel") { + return JinglePayload::Reason::Cancel; + } else if (type == "connectivity-error") { + return JinglePayload::Reason::ConnectivityError; + } else if (type == "decline") { + return JinglePayload::Reason::Decline; + } else if (type == "expired") { + return JinglePayload::Reason::Expired; + } else if (type == "failed-application") { + return JinglePayload::Reason::FailedApplication; + } else if (type == "failed-transport") { + return JinglePayload::Reason::FailedTransport; + } else if (type == "general-error") { + return JinglePayload::Reason::GeneralError; + } else if (type == "gone") { + return JinglePayload::Reason::Gone; + } else if (type == "incompatible-parameters") { + return JinglePayload::Reason::IncompatibleParameters; + } else if (type == "media-error") { + return JinglePayload::Reason::MediaError; + } else if (type == "security-error") { + return JinglePayload::Reason::SecurityError; + } else if (type == "success") { + return JinglePayload::Reason::Success; + } else if (type == "timeout") { + return JinglePayload::Reason::Timeout; + } else if (type == "unsupported-applications") { + return JinglePayload::Reason::UnsupportedApplications; + } else if (type == "unsupported-transports") { + return JinglePayload::Reason::UnsupportedTransports; + } else { + return JinglePayload::Reason::UnknownType; + } + } +} diff --git a/Swiften/Parser/PayloadParsers/JingleReasonParser.h b/Swiften/Parser/PayloadParsers/JingleReasonParser.h new file mode 100644 index 0000000..08af31a --- /dev/null +++ b/Swiften/Parser/PayloadParsers/JingleReasonParser.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Elements/JinglePayload.h> +#include <Swiften/Parser/GenericPayloadParser.h> + +namespace Swift { + +class JingleReasonParser : public GenericPayloadParser<JinglePayload::Reason> { + public: + JingleReasonParser(); + + virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes); + virtual void handleEndElement(const std::string& element, const std::string&); + virtual void handleCharacterData(const std::string& data); + private: + JinglePayload::Reason::Type stringToReasonType(const std::string& type) const; + + private: + int level; + bool parseText; + std::string text; +}; + +} diff --git a/Swiften/Parser/PayloadParsers/JingleS5BTransportMethodPayloadParser.cpp b/Swiften/Parser/PayloadParsers/JingleS5BTransportMethodPayloadParser.cpp new file mode 100644 index 0000000..14a80e6 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/JingleS5BTransportMethodPayloadParser.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <boost/lexical_cast.hpp> +#include <boost/optional.hpp> + +#include "JingleS5BTransportMethodPayloadParser.h" + +#include <Swiften/Base/Log.h> + +namespace Swift { + JingleS5BTransportMethodPayloadParser::JingleS5BTransportMethodPayloadParser() : level(0) { + + } + + void JingleS5BTransportMethodPayloadParser::handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes) { + if (level == 0) { + getPayloadInternal()->setSessionID(attributes.getAttributeValue("sid").get_value_or("")); + std::string mode = attributes.getAttributeValue("mode").get_value_or("tcp"); + if (mode == "tcp") { + getPayloadInternal()->setMode(JingleS5BTransportPayload::TCPMode); + } else if(mode == "udp") { + getPayloadInternal()->setMode(JingleS5BTransportPayload::UDPMode); + } else { + std::cerr << "Unknown S5B mode; falling back to defaul!" << std::endl; + getPayloadInternal()->setMode(JingleS5BTransportPayload::TCPMode); + } + } else if (level == 1) { + if (element == "candidate") { + JingleS5BTransportPayload::Candidate candidate; + candidate.cid = attributes.getAttributeValue("cid").get_value_or(""); + + int port = -1; + try { + port = boost::lexical_cast<int>(attributes.getAttributeValue("port").get_value_or("-1")); + } catch(boost::bad_lexical_cast &) { } + candidate.hostPort = HostAddressPort(HostAddress(attributes.getAttributeValue("host").get_value_or("")), port); + candidate.jid = JID(attributes.getAttributeValue("jid").get_value_or("")); + int priority = -1; + try { + priority = boost::lexical_cast<int>(attributes.getAttributeValue("priority").get_value_or("-1")); + } catch(boost::bad_lexical_cast &) { } + candidate.priority = priority; + candidate.type = stringToType(attributes.getAttributeValue("type").get_value_or("direct")); + + getPayloadInternal()->addCandidate(candidate); + } else if (element == "candidate-used") { + getPayloadInternal()->setCandidateUsed(attributes.getAttributeValue("cid").get_value_or("")); + } else if (element == "candidate-error") { + getPayloadInternal()->setCandidateError(true); + } else if (element == "activated") { + getPayloadInternal()->setActivated(attributes.getAttributeValue("cid").get_value_or("")); + } else if (element == "proxy-error") { + getPayloadInternal()->setProxyError(true); + } + } + + ++level; + } + + void JingleS5BTransportMethodPayloadParser::handleEndElement(const std::string&, const std::string&) { + --level; + + + } + + void JingleS5BTransportMethodPayloadParser::handleCharacterData(const std::string&) { + + } + + JingleS5BTransportPayload::Candidate::Type JingleS5BTransportMethodPayloadParser::stringToType(const std::string &str) const { + if (str == "direct") { + return JingleS5BTransportPayload::Candidate::DirectType; + } else if (str == "assisted") { + return JingleS5BTransportPayload::Candidate::AssistedType; + } else if (str == "tunnel") { + return JingleS5BTransportPayload::Candidate::TunnelType; + } else if (str == "proxy") { + return JingleS5BTransportPayload::Candidate::ProxyType; + } else { + std::cerr << "Unknown candidate type; falling back to default!" << std::endl; + return JingleS5BTransportPayload::Candidate::DirectType; + } + } +} diff --git a/Swiften/Parser/PayloadParsers/JingleS5BTransportMethodPayloadParser.h b/Swiften/Parser/PayloadParsers/JingleS5BTransportMethodPayloadParser.h new file mode 100644 index 0000000..1987d3f --- /dev/null +++ b/Swiften/Parser/PayloadParsers/JingleS5BTransportMethodPayloadParser.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Elements/JingleS5BTransportPayload.h> +#include <Swiften/Parser/GenericPayloadParser.h> + +namespace Swift { + +class JingleS5BTransportMethodPayloadParser : public GenericPayloadParser<JingleS5BTransportPayload> { + public: + JingleS5BTransportMethodPayloadParser(); + + virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes); + virtual void handleEndElement(const std::string& element, const std::string&); + virtual void handleCharacterData(const std::string& data); + + private: + JingleS5BTransportPayload::Candidate::Type stringToType(const std::string &str) const; + + private: + int level; +}; + +} diff --git a/Swiften/Parser/PayloadParsers/S5BProxyRequestParser.cpp b/Swiften/Parser/PayloadParsers/S5BProxyRequestParser.cpp new file mode 100644 index 0000000..6e33f16 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/S5BProxyRequestParser.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "S5BProxyRequestParser.h" + +#include <boost/lexical_cast.hpp> +#include <boost/optional.hpp> + +namespace Swift { + +S5BProxyRequestParser::S5BProxyRequestParser() : parseActivate(false) { +} + +S5BProxyRequestParser::~S5BProxyRequestParser() { +} + +void S5BProxyRequestParser::handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes) { + if (element == "streamhost") { + if (attributes.getAttributeValue("host") && attributes.getAttributeValue("jid") && attributes.getAttributeValue("port")) { + HostAddress address = attributes.getAttributeValue("host").get_value_or(""); + int port = -1; + JID jid = attributes.getAttributeValue("jid").get_value_or(""); + + try { + port = boost::lexical_cast<int>(attributes.getAttributeValue("port").get()); + } catch (boost::bad_lexical_cast &) { + port = -1; + } + if (address.isValid() && port != -1 && jid.isValid()) { + S5BProxyRequest::StreamHost streamHost; + streamHost.addressPort = HostAddressPort(address, port); + streamHost.jid = jid; + getPayloadInternal()->setStreamHost(streamHost); + } + } + } else if (element == "activate") { + parseActivate = true; + } else if (element == "query") { + if (attributes.getAttributeValue("sid")) { + getPayloadInternal()->setSID(attributes.getAttributeValue("sid").get()); + } + } +} + +void S5BProxyRequestParser::handleEndElement(const std::string& element, const std::string&) { + if (element == "activate") { + JID activate = JID(activateJID); + if (activate.isValid()) { + getPayloadInternal()->setActivate(activate); + } + parseActivate = false; + } +} + +void S5BProxyRequestParser::handleCharacterData(const std::string& data) { + if (parseActivate) { + activateJID = activateJID + data; + } +} + +} diff --git a/Swiften/Parser/PayloadParsers/S5BProxyRequestParser.h b/Swiften/Parser/PayloadParsers/S5BProxyRequestParser.h new file mode 100644 index 0000000..0bf1a26 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/S5BProxyRequestParser.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <string> + +#include <Swiften/Elements/S5BProxyRequest.h> +#include <Swiften/Parser/GenericPayloadParser.h> + +namespace Swift { + +class S5BProxyRequestParser : public GenericPayloadParser<S5BProxyRequest> { +public: + S5BProxyRequestParser(); + virtual ~S5BProxyRequestParser(); + + virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes); + virtual void handleEndElement(const std::string& element, const std::string&); + virtual void handleCharacterData(const std::string& data); + +private: + bool parseActivate; + std::string activateJID; +}; + +} diff --git a/Swiften/Parser/PayloadParsers/StreamInitiationFileInfoParser.cpp b/Swiften/Parser/PayloadParsers/StreamInitiationFileInfoParser.cpp new file mode 100644 index 0000000..0a13844 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StreamInitiationFileInfoParser.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "StreamInitiationFileInfoParser.h" + +#include <boost/optional.hpp> +#include <boost/lexical_cast.hpp> + +#include <Swiften/Base/DateTime.h> +#include <Swiften/Base/Log.h> + +namespace Swift { + +StreamInitiationFileInfoParser::StreamInitiationFileInfoParser() : level(0), parseDescription(false) { + +} + +void StreamInitiationFileInfoParser::handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes) { + if (level == 0) { + getPayloadInternal()->setName(attributes.getAttributeValue("name").get_value_or("")); + getPayloadInternal()->setHash(attributes.getAttributeValue("hash").get_value_or("")); + getPayloadInternal()->setAlgo(attributes.getAttributeValue("algo").get_value_or("md5")); + try { + getPayloadInternal()->setSize(boost::lexical_cast<boost::uintmax_t>(attributes.getAttributeValue("size").get_value_or("0"))); + } catch (boost::bad_lexical_cast &) { + getPayloadInternal()->setSize(0); + } + getPayloadInternal()->setDate(stringToDateTime(attributes.getAttributeValue("date").get_value_or(""))); + } else if (level == 1) { + if (element == "desc") { + parseDescription = true; + } else { + parseDescription = false; + if (element == "range") { + int offset = 0; + try { + offset = boost::lexical_cast<boost::uintmax_t>(attributes.getAttributeValue("offset").get_value_or("0")); + } catch (boost::bad_lexical_cast &) { + offset = 0; + } + if (offset == 0) { + getPayloadInternal()->setSupportsRangeRequests(true); + } else { + getPayloadInternal()->setRangeOffset(offset); + } + } + } + } + ++level; +} + +void StreamInitiationFileInfoParser::handleEndElement(const std::string& element, const std::string&) { + --level; + if (parseDescription && element == "desc") { + parseDescription = false; + getPayloadInternal()->setDescription(desc); + } +} + +void StreamInitiationFileInfoParser::handleCharacterData(const std::string& data) { + if (parseDescription) { + desc += data; + } +} + +} diff --git a/Swiften/Parser/PayloadParsers/StreamInitiationFileInfoParser.h b/Swiften/Parser/PayloadParsers/StreamInitiationFileInfoParser.h new file mode 100644 index 0000000..6d3591d --- /dev/null +++ b/Swiften/Parser/PayloadParsers/StreamInitiationFileInfoParser.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Elements/StreamInitiationFileInfo.h> +#include <Swiften/Parser/GenericPayloadParser.h> + +namespace Swift { + +class StreamInitiationFileInfoParser : public GenericPayloadParser<StreamInitiationFileInfo> { + public: + StreamInitiationFileInfoParser(); + + virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes); + virtual void handleEndElement(const std::string& element, const std::string&); + virtual void handleCharacterData(const std::string& data); + + private: + int level; + bool parseDescription; + std::string desc; +}; + +} diff --git a/Swiften/Parser/PayloadParsers/StreamInitiationParser.cpp b/Swiften/Parser/PayloadParsers/StreamInitiationParser.cpp index 9ea8089..fd3d019 100644 --- a/Swiften/Parser/PayloadParsers/StreamInitiationParser.cpp +++ b/Swiften/Parser/PayloadParsers/StreamInitiationParser.cpp @@ -38,9 +38,9 @@ void StreamInitiationParser::handleStartElement(const std::string& element, cons if (element == "file") { inFile = true; currentFile = StreamInitiationFileInfo(); - currentFile.name = attributes.getAttribute("name"); + currentFile.setName(attributes.getAttribute("name")); try { - currentFile.size = boost::lexical_cast<int>(attributes.getAttribute("size")); + currentFile.setSize(boost::lexical_cast<int>(attributes.getAttribute("size"))); } catch (boost::bad_lexical_cast&) { } @@ -83,7 +83,7 @@ void StreamInitiationParser::handleEndElement(const std::string& element, const } else if (level == FileOrFeatureLevel) { if (inFile && element == "desc") { - currentFile.description = currentText; + currentFile.setDescription(currentText); } else if (formParser) { Form::ref form = formParser->getPayloadInternal(); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/JingleParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/JingleParserTest.cpp new file mode 100644 index 0000000..d03ba8b --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/JingleParserTest.cpp @@ -0,0 +1,709 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include <Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h> +#include <Swiften/Elements/JinglePayload.h> +#include <Swiften/Elements/JingleIBBTransportPayload.h> +#include <Swiften/Elements/JingleS5BTransportPayload.h> +#include <Swiften/Elements/JingleFileTransferDescription.h> +#include <Swiften/Elements/StreamInitiationFileInfo.h> +#include <Swiften/Elements/JingleFileTransferReceived.h> +#include <Swiften/Elements/JingleFileTransferHash.h> +#include <Swiften/Base/DateTime.h> + +#include <Swiften/Base/Log.h> + +using namespace Swift; + +class JingleParserTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(JingleParserTest); + CPPUNIT_TEST(testParse_Xep0166_Example3); + CPPUNIT_TEST(testParse_Xep0166_Example8); + + CPPUNIT_TEST(testParse_Xep0261_Example1); + CPPUNIT_TEST(testParse_Xep0261_Example3); + CPPUNIT_TEST(testParse_Xep0261_Example9); + CPPUNIT_TEST(testParse_Xep0261_Example13); + + CPPUNIT_TEST(testParse_Xep0234_Example1); + CPPUNIT_TEST(testParse_Xep0234_Example3); + CPPUNIT_TEST(testParse_Xep0234_Example5); + CPPUNIT_TEST(testParse_Xep0234_Example8); + CPPUNIT_TEST(testParse_Xep0234_Example10); + CPPUNIT_TEST(testParse_Xep0234_Example11); + CPPUNIT_TEST(testParse_Xep0234_Example12); + + CPPUNIT_TEST(testParse_Xep0260_Example1); + CPPUNIT_TEST(testParse_Xep0260_Example3); + CPPUNIT_TEST_SUITE_END(); + + public: + //http://xmpp.org/extensions/xep-0166.html#example-3 + void testParse_Xep0166_Example3() { + PayloadsParserTester parser; + CPPUNIT_ASSERT(parser.parse( + "<jingle xmlns='urn:xmpp:jingle:1'\n" + " action='session-terminate'\n" + " sid='a73sjjvkla37jfea'>\n" + " <reason>\n" + " <success/>\n" + " </reason>\n" + "</jingle>\n" + )); + + JinglePayload::ref jingle = parser.getPayload<JinglePayload>(); + CPPUNIT_ASSERT(jingle); + CPPUNIT_ASSERT_EQUAL(JinglePayload::SessionTerminate, jingle->getAction()); + CPPUNIT_ASSERT_EQUAL(std::string("a73sjjvkla37jfea"), jingle->getSessionID()); + CPPUNIT_ASSERT_EQUAL(JinglePayload::Reason::Success, + jingle->getReason().get_value_or(JinglePayload::Reason(JinglePayload::Reason::UnknownType, "")).type); + } + + //http://xmpp.org/extensions/xep-0166.html#example-8 + void testParse_Xep0166_Example8() { + PayloadsParserTester parser; + CPPUNIT_ASSERT(parser.parse( + "<jingle xmlns='urn:xmpp:jingle:1'\n" + " action='session-terminate'\n" + " sid='a73sjjvkla37jfea'>\n" + " <reason>\n" + " <success/>\n" + " <text>Sorry, gotta go!</text>\n" + " </reason>\n" + "</jingle>\n" + )); + JinglePayload::ref jingle = parser.getPayload<JinglePayload>(); + CPPUNIT_ASSERT(jingle); + CPPUNIT_ASSERT_EQUAL(JinglePayload::SessionTerminate, jingle->getAction()); + CPPUNIT_ASSERT_EQUAL(std::string("a73sjjvkla37jfea"), jingle->getSessionID()); + CPPUNIT_ASSERT_EQUAL(JinglePayload::Reason::Success, + jingle->getReason().get_value_or(JinglePayload::Reason(JinglePayload::Reason::UnknownType, "")).type); + CPPUNIT_ASSERT_EQUAL(std::string("Sorry, gotta go!"), + jingle->getReason().get_value_or(JinglePayload::Reason(JinglePayload::Reason::UnknownType, "")).text); + } + + // IBB Transport Method Examples + + // http://xmpp.org/extensions/xep-0261.html#example-1 + void testParse_Xep0261_Example1() { + PayloadsParserTester parser; + CPPUNIT_ASSERT(parser.parse( + "<jingle xmlns='urn:xmpp:jingle:1'\n" + " action='session-initiate'\n" + " initiator='romeo@montague.lit/orchard'\n" + " sid='a73sjjvkla37jfea'>\n" + " <content creator='initiator' name='ex'>\n" + " <description xmlns='urn:xmpp:example'/>\n" + " <transport xmlns='urn:xmpp:jingle:transports:ibb:1'\n" + " block-size='4096'\n" + " sid='ch3d9s71'/>\n" + " </content>\n" + "</jingle>\n" + )); + JinglePayload::ref jingle = parser.getPayload<JinglePayload>(); + CPPUNIT_ASSERT(jingle); + CPPUNIT_ASSERT_EQUAL(JinglePayload::SessionInitiate, jingle->getAction()); + CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.lit/orchard"), jingle->getInitiator()); + CPPUNIT_ASSERT_EQUAL(std::string("a73sjjvkla37jfea"), jingle->getSessionID()); + + std::vector<JingleContentPayload::ref> payloads = jingle->getContents(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), payloads.size()); + JingleContentPayload::ref payload = payloads[0]; + CPPUNIT_ASSERT_EQUAL(JingleContentPayload::InitiatorCreator, payload->getCreator()); + CPPUNIT_ASSERT_EQUAL(std::string("ex"), payload->getName()); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), payload->getTransports().size()); + + JingleIBBTransportPayload::ref transportPaylod = payload->getTransport<JingleIBBTransportPayload>(); + CPPUNIT_ASSERT(transportPaylod); + CPPUNIT_ASSERT_EQUAL(4096, transportPaylod->getBlockSize()); + CPPUNIT_ASSERT_EQUAL(std::string("ch3d9s71"), transportPaylod->getSessionID()); + } + + // http://xmpp.org/extensions/xep-0261.html#example-1 + void testParse_Xep0261_Example3() { + PayloadsParserTester parser; + CPPUNIT_ASSERT(parser.parse( + "<jingle xmlns='urn:xmpp:jingle:1'\n" + " action='session-accept'\n" + " initiator='romeo@montague.lit/orchard'\n" + " responder='juliet@capulet.lit/balcony'\n" + " sid='a73sjjvkla37jfea'>\n" + " <content creator='initiator' name='ex'>\n" + " <description xmlns='urn:xmpp:example'/>\n" + " <transport xmlns='urn:xmpp:jingle:transports:ibb:1'\n" + " block-size='2048'\n" + " sid='ch3d9s71'/>\n" + " </content>\n" + " </jingle>\n" + )); + JinglePayload::ref jingle = parser.getPayload<JinglePayload>(); + CPPUNIT_ASSERT(jingle); + CPPUNIT_ASSERT_EQUAL(JinglePayload::SessionAccept, jingle->getAction()); + CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.lit/orchard"), jingle->getInitiator()); + CPPUNIT_ASSERT_EQUAL(JID("juliet@capulet.lit/balcony"), jingle->getResponder()); + CPPUNIT_ASSERT_EQUAL(std::string("a73sjjvkla37jfea"), jingle->getSessionID()); + + std::vector<JingleContentPayload::ref> payloads = jingle->getContents(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), payloads.size()); + JingleContentPayload::ref payload = payloads[0]; + CPPUNIT_ASSERT_EQUAL(JingleContentPayload::InitiatorCreator, payload->getCreator()); + CPPUNIT_ASSERT_EQUAL(std::string("ex"), payload->getName()); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), payload->getTransports().size()); + + JingleIBBTransportPayload::ref transportPaylod = payload->getTransport<JingleIBBTransportPayload>(); + CPPUNIT_ASSERT(transportPaylod); + CPPUNIT_ASSERT_EQUAL(2048, transportPaylod->getBlockSize()); + CPPUNIT_ASSERT_EQUAL(std::string("ch3d9s71"), transportPaylod->getSessionID()); + } + + // http://xmpp.org/extensions/xep-0261.html#example-9 + void testParse_Xep0261_Example9() { + PayloadsParserTester parser; + CPPUNIT_ASSERT(parser.parse( + "<jingle xmlns='urn:xmpp:jingle:1'\n" + " action='transport-info'\n" + " initiator='romeo@montague.lit/orchard'\n" + " sid='a73sjjvkla37jfea'>\n" + " <content creator='initiator' name='ex'>\n" + " <transport xmlns='urn:xmpp:jingle:transports:ibb:1'\n" + " block-size='2048'\n" + " sid='bt8a71h6'/>\n" + " </content>\n" + "</jingle>\n" + )); + JinglePayload::ref jingle = parser.getPayload<JinglePayload>(); + CPPUNIT_ASSERT(jingle); + CPPUNIT_ASSERT_EQUAL(JinglePayload::TransportInfo, jingle->getAction()); + CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.lit/orchard"), jingle->getInitiator()); + CPPUNIT_ASSERT_EQUAL(std::string("a73sjjvkla37jfea"), jingle->getSessionID()); + + std::vector<JingleContentPayload::ref> payloads = jingle->getContents(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), payloads.size()); + JingleContentPayload::ref payload = payloads[0]; + CPPUNIT_ASSERT_EQUAL(JingleContentPayload::InitiatorCreator, payload->getCreator()); + CPPUNIT_ASSERT_EQUAL(std::string("ex"), payload->getName()); + + JingleIBBTransportPayload::ref transportPaylod = payload->getTransport<JingleIBBTransportPayload>(); + CPPUNIT_ASSERT(transportPaylod); + CPPUNIT_ASSERT_EQUAL(2048, transportPaylod->getBlockSize()); + CPPUNIT_ASSERT_EQUAL(std::string("bt8a71h6"), transportPaylod->getSessionID()); + } + + // http://xmpp.org/extensions/xep-0261.html#example-13 + void testParse_Xep0261_Example13() { + PayloadsParserTester parser; + CPPUNIT_ASSERT(parser.parse( + "<jingle xmlns='urn:xmpp:jingle:1'\n" + " action='session-terminate'\n" + " initiator='romeo@montague.lit/orchard'\n" + " sid='a73sjjvkla37jfea'>\n" + " <reason><success/></reason>\n" + " </jingle>\n" + )); + + JinglePayload::ref jingle = parser.getPayload<JinglePayload>(); + CPPUNIT_ASSERT(jingle); + CPPUNIT_ASSERT_EQUAL(JinglePayload::SessionTerminate, jingle->getAction()); + CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.lit/orchard"), jingle->getInitiator()); + CPPUNIT_ASSERT_EQUAL(std::string("a73sjjvkla37jfea"), jingle->getSessionID()); + CPPUNIT_ASSERT_EQUAL(JinglePayload::Reason::Success, jingle->getReason().get_value_or(JinglePayload::Reason()).type); + + } + + // Jingle File Transfer Examples + + // http://xmpp.org/extensions/xep-0234.html#example-1 + void testParse_Xep0234_Example1() { + PayloadsParserTester parser; + CPPUNIT_ASSERT(parser.parse( + "<jingle xmlns='urn:xmpp:jingle:1'\n" + " action='session-initiate'\n" + " initiator='romeo@montague.lit/orchard'\n" + " sid='851ba2'>\n" + " <content creator='initiator' name='a-file-offer'>\n" + " <description xmlns='urn:xmpp:jingle:apps:file-transfer:3'>\n" + " <offer>\n" + " <file xmlns='http://jabber.org/protocol/si/profile/file-transfer'\n" + " date='1969-07-21T02:56:15Z'\n" + " hash='552da749930852c69ae5d2141d3766b1'\n" + " name='test.txt'\n" + " size='1022'>\n" + " <desc>This is a test. If this were a real file...</desc>\n" + " <range/>\n" + " </file>\n" + " </offer>\n" + " </description>\n" + " <transport xmlns='urn:xmpp:jingle:transports:s5b:1'\n" + " mode='tcp'\n" + " sid='vj3hs98y'>\n" + " <candidate cid='hft54dqy'\n" + " host='192.168.4.1'\n" + " jid='romeo@montague.lit/orchard'\n" + " port='5086'\n" + " priority='8257636'\n" + " type='direct'/>\n" + " <candidate cid='hutr46fe'\n" + " host='24.24.24.1'\n" + " jid='romeo@montague.lit/orchard'\n" + " port='5087'\n" + " priority='8258636'\n" + " type='direct'/>\n" + " </transport>\n" + " </content>\n" + " </jingle>\n" + )); + + JinglePayload::ref jingle = parser.getPayload<JinglePayload>(); + CPPUNIT_ASSERT(jingle); + CPPUNIT_ASSERT_EQUAL(JinglePayload::SessionInitiate, jingle->getAction()); + CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.lit/orchard"), jingle->getInitiator()); + CPPUNIT_ASSERT_EQUAL(std::string("851ba2"), jingle->getSessionID()); + + std::vector<JingleContentPayload::ref> contents = jingle->getContents(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), contents.size()); + + JingleFileTransferDescription::ref description = contents[0]->getDescription<JingleFileTransferDescription>(); + + + std::vector<StreamInitiationFileInfo> offers = description->getOffers(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), offers.size()); + CPPUNIT_ASSERT_EQUAL(std::string("test.txt"), offers[0].getName()); + CPPUNIT_ASSERT_EQUAL(std::string("552da749930852c69ae5d2141d3766b1"), offers[0].getHash()); + CPPUNIT_ASSERT(1022 == offers[0].getSize()); + CPPUNIT_ASSERT_EQUAL(std::string("This is a test. If this were a real file..."), offers[0].getDescription()); + CPPUNIT_ASSERT_EQUAL(true, offers[0].getSupportsRangeRequests()); + CPPUNIT_ASSERT(stringToDateTime("1969-07-21T02:56:15Z") == offers[0].getDate()); + CPPUNIT_ASSERT_EQUAL(std::string("md5"), offers[0].getAlgo()); + } + + // http://xmpp.org/extensions/xep-0234.html#example-3 + void testParse_Xep0234_Example3() { + PayloadsParserTester parser; + CPPUNIT_ASSERT(parser.parse( + "<jingle xmlns='urn:xmpp:jingle:1'\n" + " action='session-accept'\n" + " initiator='romeo@montague.lit/orchard'\n" + " sid='851ba2'>\n" + " <content creator='initiator' name='a-file-offer'>\n" + " <description xmlns='urn:xmpp:jingle:apps:file-transfer:3'>\n" + " <offer>\n" + " <file xmlns='http://jabber.org/protocol/si/profile/file-transfer'\n" + " name='test.txt'\n" + " size='1022'\n" + " hash='552da749930852c69ae5d2141d3766b1'\n" + " date='1969-07-21T02:56:15Z'>\n" + " <desc>This is a test. If this were a real file...</desc>\n" + " <range/>\n" + " </file>\n" + " </offer>\n" + " </description>\n" + " <transport xmlns='urn:xmpp:jingle:transports:s5b:1'\n" + " mode='tcp'\n" + " sid='vj3hs98y'>\n" + " <candidate cid='ht567dq'\n" + " host='192.169.1.10'\n" + " jid='juliet@capulet.lit/balcony'\n" + " port='6539'\n" + " priority='8257636'\n" + " type='direct'/>\n" + " <candidate cid='hr65dqyd'\n" + " host='134.102.201.180'\n" + " jid='juliet@capulet.lit/balcony'\n" + " port='16453'\n" + " priority='7929856'\n" + " type='assisted'/>\n" + " <candidate cid='grt654q2'\n" + " host='2001:638:708:30c9:219:d1ff:fea4:a17d'\n" + " jid='juliet@capulet.lit/balcony'\n" + " port='6539'\n" + " priority='8257606'\n" + " type='direct'/>\n" + " </transport>\n" + " </content>\n" + "</jingle>\n" + )); + + JinglePayload::ref jingle = parser.getPayload<JinglePayload>(); + CPPUNIT_ASSERT(jingle); + CPPUNIT_ASSERT_EQUAL(JinglePayload::SessionAccept, jingle->getAction()); + CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.lit/orchard"), jingle->getInitiator()); + CPPUNIT_ASSERT_EQUAL(std::string("851ba2"), jingle->getSessionID()); + + std::vector<JingleContentPayload::ref> contents = jingle->getContents(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), contents.size()); + + JingleFileTransferDescription::ref description = contents[0]->getDescription<JingleFileTransferDescription>(); + + + std::vector<StreamInitiationFileInfo> offers = description->getOffers(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), offers.size()); + CPPUNIT_ASSERT_EQUAL(std::string("test.txt"), offers[0].getName()); + CPPUNIT_ASSERT_EQUAL(std::string("552da749930852c69ae5d2141d3766b1"), offers[0].getHash()); + CPPUNIT_ASSERT(1022 == offers[0].getSize()); + CPPUNIT_ASSERT_EQUAL(std::string("This is a test. If this were a real file..."), offers[0].getDescription()); + CPPUNIT_ASSERT_EQUAL(true, offers[0].getSupportsRangeRequests()); + CPPUNIT_ASSERT(stringToDateTime("1969-07-21T02:56:15Z") == offers[0].getDate()); + CPPUNIT_ASSERT_EQUAL(std::string("md5"), offers[0].getAlgo()); + } + + // http://xmpp.org/extensions/xep-0234.html#example-5 + void testParse_Xep0234_Example5() { + PayloadsParserTester parser; + CPPUNIT_ASSERT(parser.parse( + "<jingle xmlns='urn:xmpp:jingle:1'\n" + " action='transport-info'\n" + " initiator='romeo@montague.lit/orchard'\n" + " sid='a73sjjvkla37jfea'>\n" + " <content creator='initiator' name='ex'>\n" + " <transport xmlns='urn:xmpp:jingle:transports:s5b:1'\n" + " sid='vj3hs98y'>\n" + " <candidate-used cid='hr65dqyd'/>\n" + " </transport>\n" + " </content>\n" + "</jingle>\n" + )); + + JinglePayload::ref jingle = parser.getPayload<JinglePayload>(); + CPPUNIT_ASSERT(jingle); + CPPUNIT_ASSERT_EQUAL(JinglePayload::TransportInfo, jingle->getAction()); + CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.lit/orchard"), jingle->getInitiator()); + CPPUNIT_ASSERT_EQUAL(std::string("a73sjjvkla37jfea"), jingle->getSessionID()); + + std::vector<JingleContentPayload::ref> contents = jingle->getContents(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), contents.size()); + + JingleS5BTransportPayload::ref transport = contents[0]->getTransport<JingleS5BTransportPayload>(); + CPPUNIT_ASSERT(transport); + + CPPUNIT_ASSERT_EQUAL(std::string("vj3hs98y"), transport->getSessionID()); + CPPUNIT_ASSERT_EQUAL(std::string("hr65dqyd"), transport->getCandidateUsed()); + } + + // http://xmpp.org/extensions/xep-0234.html#example-8 + void testParse_Xep0234_Example8() { + PayloadsParserTester parser; + CPPUNIT_ASSERT(parser.parse( + "<jingle xmlns='urn:xmpp:jingle:1'\n" + " action='session-info'\n" + " initiator='romeo@montague.lit/orchard'\n" + " sid='a73sjjvkla37jfea'>\n" + " <checksum xmlns='urn:xmpp:jingle:apps:file-transfer:3'>\n" + " <file>\n" + " <hashes xmlns='urn:xmpp:hashes:0'>\n" + " <hash algo='sha-1'>552da749930852c69ae5d2141d3766b1</hash>\n" + " </hashes>\n" + " </file>\n" + " </checksum>\n" + "</jingle>\n" + )); + JinglePayload::ref jingle = parser.getPayload<JinglePayload>(); + CPPUNIT_ASSERT(jingle); + CPPUNIT_ASSERT_EQUAL(JinglePayload::SessionInfo, jingle->getAction()); + CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.lit/orchard"), jingle->getInitiator()); + CPPUNIT_ASSERT_EQUAL(std::string("a73sjjvkla37jfea"), jingle->getSessionID()); + + JingleFileTransferHash::ref hash = jingle->getPayload<JingleFileTransferHash>(); + CPPUNIT_ASSERT(hash); + CPPUNIT_ASSERT_EQUAL(std::string("552da749930852c69ae5d2141d3766b1"), hash->getHashes().at("sha-1")); + + } + + // http://xmpp.org/extensions/xep-0234.html#example-10 + void testParse_Xep0234_Example10() { + PayloadsParserTester parser; + CPPUNIT_ASSERT(parser.parse( + "<jingle xmlns='urn:xmpp:jingle:1'\n" + " action='session-initiate'\n" + " initiator='romeo@montague.lit/orchard'\n" + " sid='uj3b2'>\n" + " <content creator='initiator' name='a-file-request'>\n" + " <description xmlns='urn:xmpp:jingle:apps:file-transfer:3'>\n" + " <request>\n" + " <file xmlns='http://jabber.org/protocol/si/profile/file-transfer'\n" + " hash='552da749930852c69ae5d2141d3766b1'>\n" + " <range offset='270336'/>\n" + " </file>\n" + " </request>\n" + " </description>\n" + " <transport xmlns='urn:xmpp:jingle:transports:s5b:1'\n" + " mode='tcp'\n" + " sid='xig361fj'>\n" + " <candidate cid='ht567dq'\n" + " host='192.169.1.10'\n" + " jid='juliet@capulet.lit/balcony'\n" + " port='6539'\n" + " priority='8257636'\n" + " type='direct'/>\n" + " <candidate cid='hr65dqyd'\n" + " host='134.102.201.180'\n" + " jid='juliet@capulet.lit/balcony'\n" + " port='16453'\n" + " priority='7929856'\n" + " type='assisted'/>\n" + " <candidate cid='grt654q2'\n" + " host='2001:638:708:30c9:219:d1ff:fea4:a17d'\n" + " jid='juliet@capulet.lit/balcony'\n" + " port='6539'\n" + " priority='8257606'\n" + " type='direct'/>\n" + " </transport>\n" + " </content>\n" + "</jingle>\n" + )); + + JinglePayload::ref jingle = parser.getPayload<JinglePayload>(); + CPPUNIT_ASSERT(jingle); + CPPUNIT_ASSERT_EQUAL(JinglePayload::SessionInitiate, jingle->getAction()); + CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.lit/orchard"), jingle->getInitiator()); + CPPUNIT_ASSERT_EQUAL(std::string("uj3b2"), jingle->getSessionID()); + + JingleContentPayload::ref content = jingle->getPayload<JingleContentPayload>(); + CPPUNIT_ASSERT(content); + + StreamInitiationFileInfo file = content->getDescription<JingleFileTransferDescription>()->getRequests()[0]; + CPPUNIT_ASSERT_EQUAL(std::string("552da749930852c69ae5d2141d3766b1"), file.getHash()); + CPPUNIT_ASSERT_EQUAL(270336, file.getRangeOffset()); + CPPUNIT_ASSERT_EQUAL(true, file.getSupportsRangeRequests()); + } + + // http://xmpp.org/extensions/xep-0234.html#example-11 + void testParse_Xep0234_Example11() { + PayloadsParserTester parser; + CPPUNIT_ASSERT(parser.parse( + "<jingle xmlns='urn:xmpp:jingle:1'\n" + " action='session-initiate'\n" + " initiator='romeo@montague.lit/orchard'\n" + " sid='h2va419i'>\n" + " <content creator='initiator' name='a-file-offer'>\n" + " <description xmlns='urn:xmpp:jingle:apps:file-transfer:3'>\n" + " <offer>\n" + " <file xmlns='http://jabber.org/protocol/si/profile/file-transfer'\n" + " date='2011-06-01T15:58:15Z'\n" + " hash='a749930852c69ae5d2141d3766b1552d'\n" + " name='somefile.txt'\n" + " size='1234'/>\n" + " <file xmlns='http://jabber.org/protocol/si/profile/file-transfer'\n" + " date='2011-06-01T15:58:15Z'\n" + " hash='930852c69ae5d2141d3766b1552da749'\n" + " name='anotherfile.txt'\n" + " size='2345'/>\n" + " <file xmlns='http://jabber.org/protocol/si/profile/file-transfer'\n" + " date='2011-06-01T15:58:15Z'\n" + " hash='52c69ae5d2141d3766b1552da7499308'\n" + " name='yetanotherfile.txt'\n" + " size='3456'/>\n" + " </offer>\n" + " </description>\n" + " <transport xmlns='urn:xmpp:jingle:transports:s5b:1'\n" + " mode='tcp'\n" + " sid='vj3hs98y'>\n" + " <candidate cid='hft54dqy'\n" + " host='192.168.4.1'\n" + " jid='romeo@montague.lit/orchard'\n" + " port='5086'\n" + " priority='8257636'\n" + " type='direct'/>\n" + " <candidate cid='hutr46fe'\n" + " host='24.24.24.1'\n" + " jid='romeo@montague.lit/orchard'\n" + " port='5087'\n" + " priority='8258636'\n" + " type='direct'/>\n" + " </transport>\n" + " </content>\n" + "</jingle>\n" + )); + + JinglePayload::ref jingle = parser.getPayload<JinglePayload>(); + CPPUNIT_ASSERT(jingle); + CPPUNIT_ASSERT_EQUAL(JinglePayload::SessionInitiate, jingle->getAction()); + CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.lit/orchard"), jingle->getInitiator()); + CPPUNIT_ASSERT_EQUAL(std::string("h2va419i"), jingle->getSessionID()); + + JingleContentPayload::ref content = jingle->getPayload<JingleContentPayload>(); + CPPUNIT_ASSERT(content); + CPPUNIT_ASSERT_EQUAL(JingleContentPayload::InitiatorCreator, content->getCreator()); + CPPUNIT_ASSERT_EQUAL(std::string("a-file-offer"), content->getName()); + + std::vector<StreamInitiationFileInfo> offers = content->getDescription<JingleFileTransferDescription>()->getOffers(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), offers.size()); + } + + // http://xmpp.org/extensions/xep-0234.html#example-12 + void testParse_Xep0234_Example12() { + PayloadsParserTester parser; + CPPUNIT_ASSERT(parser.parse( + "<jingle xmlns='urn:xmpp:jingle:1'\n" + " action='session-info'\n" + " initiator='romeo@montague.lit/orchard'\n" + " sid='a73sjjvkla37jfea'>\n" + " <received xmlns='urn:xmpp:jingle:apps:file-transfer:3'>\n" + " <file xmlns='http://jabber.org/protocol/si/profile/file-transfer'\n" + " hash='a749930852c69ae5d2141d3766b1552d'/>\n" + " </received>\n" + "</jingle>\n" + )); + + JinglePayload::ref jingle = parser.getPayload<JinglePayload>(); + CPPUNIT_ASSERT(jingle); + CPPUNIT_ASSERT_EQUAL(JinglePayload::SessionInfo, jingle->getAction()); + CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.lit/orchard"), jingle->getInitiator()); + CPPUNIT_ASSERT_EQUAL(std::string("a73sjjvkla37jfea"), jingle->getSessionID()); + + boost::shared_ptr<JingleFileTransferReceived> received = jingle->getPayload<JingleFileTransferReceived>(); + CPPUNIT_ASSERT(received); + CPPUNIT_ASSERT_EQUAL(std::string("a749930852c69ae5d2141d3766b1552d"), received->getFileInfo().getHash()); + } + + // http://xmpp.org/extensions/xep-0260.html#example-1 + void testParse_Xep0260_Example1() { + PayloadsParserTester parser; + CPPUNIT_ASSERT(parser.parse( + "<jingle xmlns='urn:xmpp:jingle:1'\n" + " action='session-initiate'\n" + " initiator='romeo@montague.lit/orchard'\n" + " sid='a73sjjvkla37jfea'>\n" + " <content creator='initiator' name='ex'>\n" + " <description xmlns='urn:xmpp:example'/>\n" + " <transport xmlns='urn:xmpp:jingle:transports:s5b:1'\n" + " mode='tcp'\n" + " sid='vj3hs98y'>\n" + " <candidate cid='hft54dqy'\n" + " host='192.168.4.1'\n" + " jid='romeo@montague.lit/orchard'\n" + " port='5086'\n" + " priority='8257636'\n" + " type='direct'/>\n" + " <candidate cid='hutr46fe'\n" + " host='24.24.24.1'\n" + " jid='romeo@montague.lit/orchard'\n" + " port='5087'\n" + " priority='8258636'\n" + " type='direct'/>\n" + " </transport>\n" + " </content>\n" + "</jingle>\n" + )); + JinglePayload::ref jingle = parser.getPayload<JinglePayload>(); + CPPUNIT_ASSERT(jingle); + CPPUNIT_ASSERT_EQUAL(JinglePayload::SessionInitiate, jingle->getAction()); + CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.lit/orchard"), jingle->getInitiator()); + CPPUNIT_ASSERT_EQUAL(std::string("a73sjjvkla37jfea"), jingle->getSessionID()); + + JingleContentPayload::ref content = jingle->getPayload<JingleContentPayload>(); + CPPUNIT_ASSERT(content); + + JingleS5BTransportPayload::ref s5bPayload = content->getTransport<JingleS5BTransportPayload>(); + CPPUNIT_ASSERT(s5bPayload); + + CPPUNIT_ASSERT_EQUAL(std::string("vj3hs98y"), s5bPayload->getSessionID()); + CPPUNIT_ASSERT_EQUAL(JingleS5BTransportPayload::TCPMode, s5bPayload->getMode()); + CPPUNIT_ASSERT_EQUAL(false, s5bPayload->hasCandidateError()); + CPPUNIT_ASSERT_EQUAL(false, s5bPayload->hasProxyError()); + CPPUNIT_ASSERT_EQUAL(std::string(), s5bPayload->getActivated()); + CPPUNIT_ASSERT_EQUAL(std::string(), s5bPayload->getCandidateUsed()); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), s5bPayload->getCandidates().size()); + + JingleS5BTransportPayload::Candidate candidate; + candidate = s5bPayload->getCandidates()[0]; + CPPUNIT_ASSERT_EQUAL(std::string("hft54dqy"), candidate.cid); + CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.lit/orchard"), candidate.jid); + CPPUNIT_ASSERT(HostAddressPort(HostAddress("192.168.4.1"), 5086) == candidate.hostPort); + CPPUNIT_ASSERT_EQUAL(8257636, candidate.priority); + CPPUNIT_ASSERT_EQUAL(JingleS5BTransportPayload::Candidate::DirectType, candidate.type); + + candidate = s5bPayload->getCandidates()[1]; + CPPUNIT_ASSERT_EQUAL(std::string("hutr46fe"), candidate.cid); + CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.lit/orchard"), candidate.jid); + CPPUNIT_ASSERT(HostAddressPort(HostAddress("24.24.24.1"), 5087) == candidate.hostPort); + CPPUNIT_ASSERT_EQUAL(8258636, candidate.priority); + CPPUNIT_ASSERT_EQUAL(JingleS5BTransportPayload::Candidate::DirectType, candidate.type); + } + + // http://xmpp.org/extensions/xep-0260.html#example-3 + void testParse_Xep0260_Example3() { + PayloadsParserTester parser; + CPPUNIT_ASSERT(parser.parse( + "<jingle xmlns='urn:xmpp:jingle:1'\n" + " action='session-accept'\n" + " initiator='romeo@montague.lit/orchard'\n" + " sid='a73sjjvkla37jfea'>\n" + " <content creator='initiator' name='ex'>\n" + " <description xmlns='urn:xmpp:example'/>\n" + " <transport xmlns='urn:xmpp:jingle:transports:s5b:1'\n" + " mode='tcp'\n" + " sid='vj3hs98y'>\n" + " <candidate cid='ht567dq'\n" + " host='192.169.1.10'\n" + " jid='juliet@capulet.lit/balcony'\n" + " port='6539'\n" + " priority='8257636'\n" + " type='direct'/>\n" + " <candidate cid='hr65dqyd'\n" + " host='134.102.201.180'\n" + " jid='juliet@capulet.lit/balcony'\n" + " port='16453'\n" + " priority='7929856'\n" + " type='assisted'/>\n" + " <candidate cid='grt654q2'\n" + " host='2001:638:708:30c9:219:d1ff:fea4:a17d'\n" + " jid='juliet@capulet.lit/balcony'\n" + " port='6539'\n" + " priority='8257606'\n" + " type='direct'/>\n" + " </transport>\n" + " </content>\n" + "</jingle>\n" + )); + + JinglePayload::ref jingle = parser.getPayload<JinglePayload>(); + CPPUNIT_ASSERT(jingle); + CPPUNIT_ASSERT_EQUAL(JinglePayload::SessionAccept, jingle->getAction()); + CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.lit/orchard"), jingle->getInitiator()); + CPPUNIT_ASSERT_EQUAL(std::string("a73sjjvkla37jfea"), jingle->getSessionID()); + + JingleContentPayload::ref content = jingle->getPayload<JingleContentPayload>(); + CPPUNIT_ASSERT(content); + + JingleS5BTransportPayload::ref s5bPayload = content->getTransport<JingleS5BTransportPayload>(); + CPPUNIT_ASSERT(s5bPayload); + + CPPUNIT_ASSERT_EQUAL(std::string("vj3hs98y"), s5bPayload->getSessionID()); + CPPUNIT_ASSERT_EQUAL(JingleS5BTransportPayload::TCPMode, s5bPayload->getMode()); + CPPUNIT_ASSERT_EQUAL(false, s5bPayload->hasCandidateError()); + CPPUNIT_ASSERT_EQUAL(false, s5bPayload->hasProxyError()); + CPPUNIT_ASSERT_EQUAL(std::string(), s5bPayload->getActivated()); + CPPUNIT_ASSERT_EQUAL(std::string(), s5bPayload->getCandidateUsed()); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), s5bPayload->getCandidates().size()); + + JingleS5BTransportPayload::Candidate candidate; + candidate = s5bPayload->getCandidates()[0]; + CPPUNIT_ASSERT_EQUAL(std::string("ht567dq"), candidate.cid); + CPPUNIT_ASSERT_EQUAL(JID("juliet@capulet.lit/balcony"), candidate.jid); + CPPUNIT_ASSERT(HostAddressPort(HostAddress("192.169.1.10"), 6539) == candidate.hostPort); + CPPUNIT_ASSERT_EQUAL(8257636, candidate.priority); + CPPUNIT_ASSERT_EQUAL(JingleS5BTransportPayload::Candidate::DirectType, candidate.type); + + candidate = s5bPayload->getCandidates()[1]; + CPPUNIT_ASSERT_EQUAL(std::string("hr65dqyd"), candidate.cid); + CPPUNIT_ASSERT_EQUAL(JID("juliet@capulet.lit/balcony"), candidate.jid); + CPPUNIT_ASSERT(HostAddressPort(HostAddress("134.102.201.180"), 16453) == candidate.hostPort); + CPPUNIT_ASSERT_EQUAL(7929856, candidate.priority); + CPPUNIT_ASSERT_EQUAL(JingleS5BTransportPayload::Candidate::AssistedType, candidate.type); + + candidate = s5bPayload->getCandidates()[2]; + CPPUNIT_ASSERT_EQUAL(std::string("grt654q2"), candidate.cid); + CPPUNIT_ASSERT_EQUAL(JID("juliet@capulet.lit/balcony"), candidate.jid); + CPPUNIT_ASSERT(HostAddressPort(HostAddress("2001:638:708:30c9:219:d1ff:fea4:a17d"), 6539) == candidate.hostPort); + CPPUNIT_ASSERT_EQUAL(8257606, candidate.priority); + CPPUNIT_ASSERT_EQUAL(JingleS5BTransportPayload::Candidate::DirectType, candidate.type); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(JingleParserTest); diff --git a/Swiften/Parser/PayloadParsers/UnitTest/StreamInitiationParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/StreamInitiationParserTest.cpp index 47b2816..63a6c9b 100644 --- a/Swiften/Parser/PayloadParsers/UnitTest/StreamInitiationParserTest.cpp +++ b/Swiften/Parser/PayloadParsers/UnitTest/StreamInitiationParserTest.cpp @@ -42,9 +42,9 @@ class StreamInitiationParserTest : public CppUnit::TestFixture { StreamInitiation::ref si = parser.getPayload<StreamInitiation>(); CPPUNIT_ASSERT(si->getIsFileTransfer()); CPPUNIT_ASSERT(si->getFileInfo()); - CPPUNIT_ASSERT_EQUAL(std::string("test.txt"), si->getFileInfo()->name); - CPPUNIT_ASSERT_EQUAL(1022, si->getFileInfo()->size); - CPPUNIT_ASSERT_EQUAL(std::string("This is info about the file."), si->getFileInfo()->description); + CPPUNIT_ASSERT_EQUAL(std::string("test.txt"), si->getFileInfo()->getName()); + CPPUNIT_ASSERT(1022 == si->getFileInfo()->getSize()); + CPPUNIT_ASSERT_EQUAL(std::string("This is info about the file."), si->getFileInfo()->getDescription()); CPPUNIT_ASSERT_EQUAL(3, static_cast<int>(si->getProvidedMethods().size())); CPPUNIT_ASSERT_EQUAL(std::string("http://jabber.org/protocol/bytestreams"), si->getProvidedMethods()[0]); CPPUNIT_ASSERT_EQUAL(std::string("jabber:iq:oob"), si->getProvidedMethods()[1]); diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript index e6c16d1..3dbfbee 100644 --- a/Swiften/Parser/SConscript +++ b/Swiften/Parser/SConscript @@ -29,6 +29,15 @@ sources = [ "PayloadParsers/ErrorParser.cpp", "PayloadParsers/FormParser.cpp", "PayloadParsers/IBBParser.cpp", + "PayloadParsers/JingleParser.cpp", + "PayloadParsers/JingleReasonParser.cpp", + "PayloadParsers/JingleContentPayloadParser.cpp", + "PayloadParsers/JingleIBBTransportMethodPayloadParser.cpp", + "PayloadParsers/JingleS5BTransportMethodPayloadParser.cpp", + "PayloadParsers/JingleFileTransferDescriptionParser.cpp", + "PayloadParsers/JingleFileTransferReceivedParser.cpp", + "PayloadParsers/JingleFileTransferHashParser.cpp", + "PayloadParsers/StreamInitiationFileInfoParser.cpp", "PayloadParsers/CommandParser.cpp", "PayloadParsers/InBandRegistrationPayloadParser.cpp", "PayloadParsers/SearchPayloadParser.cpp", @@ -56,6 +65,7 @@ sources = [ "PayloadParsers/NicknameParser.cpp", "PayloadParsers/ReplaceParser.cpp", "PayloadParsers/LastParser.cpp", + "PayloadParsers/S5BProxyRequestParser.cpp", "PlatformXMLParserFactory.cpp", "PresenceParser.cpp", "SerializingParser.cpp", diff --git a/Swiften/SConscript b/Swiften/SConscript index e20e5e6..ca8dee5 100644 --- a/Swiften/SConscript +++ b/Swiften/SConscript @@ -6,7 +6,7 @@ Import("env") # Flags ################################################################################ -swiften_dep_modules = ["BOOST", "GCONF", "LIBIDN", "ZLIB", "OPENSSL", "LIBXML", "EXPAT", "AVAHI"] +swiften_dep_modules = ["BOOST", "GCONF", "LIBIDN", "ZLIB", "OPENSSL", "LIBXML", "EXPAT", "AVAHI", "LIBMINIUPNPC", "LIBNATPMP"] if env["SCONS_STAGE"] == "flags" : env["SWIFTEN_VERSION"] = Version.getBuildVersion(env.Dir("#").abspath, "swift") @@ -21,6 +21,10 @@ if env["SCONS_STAGE"] == "flags" : env["SWIFTEN_LIBRARY"] = "Swiften" env["SWIFTEN_LIBRARY_FILE"] = "Swiften" env["SWIFTEN_LIBRARY_ALIASES"] = [] + + if env["PLATFORM"] == "win32" : + env.Append(CCFLAGS = ["-DSTATICLIB"]) + if ARGUMENTS.get("swiften_dll", False) : if env["PLATFORM"] == "win32" : pass @@ -87,6 +91,7 @@ if env["SCONS_STAGE"] == "build" : "Client/NickManager.cpp", "Client/NickManagerImpl.cpp", "Client/Storages.cpp", + "Client/XMLBeautifier.cpp", "Compress/ZLibCodecompressor.cpp", "Compress/ZLibDecompressor.cpp", "Compress/ZLibCompressor.cpp", @@ -166,6 +171,14 @@ if env["SCONS_STAGE"] == "build" : "Serializer/PayloadSerializers/SearchPayloadSerializer.cpp", "Serializer/PayloadSerializers/FormSerializer.cpp", "Serializer/PayloadSerializers/NicknameSerializer.cpp", + "Serializer/PayloadSerializers/JingleFileTransferDescriptionSerializer.cpp", + "Serializer/PayloadSerializers/JinglePayloadSerializer.cpp", + "Serializer/PayloadSerializers/JingleContentPayloadSerializer.cpp", + "Serializer/PayloadSerializers/JingleFileTransferHashSerializer.cpp", + "Serializer/PayloadSerializers/JingleFileTransferReceivedSerializer.cpp", + "Serializer/PayloadSerializers/JingleIBBTransportPayloadSerializer.cpp", + "Serializer/PayloadSerializers/JingleS5BTransportPayloadSerializer.cpp", + "Serializer/PayloadSerializers/StreamInitiationFileInfoSerializer.cpp", "Serializer/PresenceSerializer.cpp", "Serializer/StanzaSerializer.cpp", "Serializer/StreamErrorSerializer.cpp", @@ -281,6 +294,7 @@ if env["SCONS_STAGE"] == "build" : File("Parser/PayloadParsers/UnitTest/RosterItemExchangeParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/RosterParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/IBBParserTest.cpp"), + File("Parser/PayloadParsers/UnitTest/JingleParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/SearchPayloadParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/SecurityLabelParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/SecurityLabelsCatalogParserTest.cpp"), @@ -340,6 +354,7 @@ if env["SCONS_STAGE"] == "build" : File("Serializer/PayloadSerializers/UnitTest/PrivateStorageSerializerTest.cpp"), File("Serializer/PayloadSerializers/UnitTest/ReplaceSerializerTest.cpp"), File("Serializer/PayloadSerializers/UnitTest/MUCAdminPayloadSerializerTest.cpp"), + File("Serializer/PayloadSerializers/UnitTest/JingleSerializersTest.cpp"), File("Serializer/UnitTest/StreamFeaturesSerializerTest.cpp"), File("Serializer/UnitTest/AuthSuccessSerializerTest.cpp"), File("Serializer/UnitTest/AuthChallengeSerializerTest.cpp"), diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp index 0ddd445..55c39c7 100644 --- a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp +++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp @@ -49,6 +49,16 @@ #include <Swiften/Serializer/PayloadSerializers/ReplaceSerializer.h> #include <Swiften/Serializer/PayloadSerializers/LastSerializer.h> +#include <Swiften/Serializer/PayloadSerializers/StreamInitiationFileInfoSerializer.h> +#include <Swiften/Serializer/PayloadSerializers/JingleContentPayloadSerializer.h> +#include <Swiften/Serializer/PayloadSerializers/JingleFileTransferDescriptionSerializer.h> +#include <Swiften/Serializer/PayloadSerializers/JingleFileTransferHashSerializer.h> +#include <Swiften/Serializer/PayloadSerializers/JingleFileTransferReceivedSerializer.h> +#include <Swiften/Serializer/PayloadSerializers/JingleIBBTransportPayloadSerializer.h> +#include <Swiften/Serializer/PayloadSerializers/JingleS5BTransportPayloadSerializer.h> +#include <Swiften/Serializer/PayloadSerializers/JinglePayloadSerializer.h> +#include <Swiften/Serializer/PayloadSerializers/S5BProxyRequestSerializer.h> + namespace Swift { FullPayloadSerializerCollection::FullPayloadSerializerCollection() { @@ -92,6 +102,17 @@ FullPayloadSerializerCollection::FullPayloadSerializerCollection() { serializers_.push_back(new SearchPayloadSerializer()); serializers_.push_back(new ReplaceSerializer()); serializers_.push_back(new LastSerializer()); + + serializers_.push_back(new StreamInitiationFileInfoSerializer()); + serializers_.push_back(new JingleContentPayloadSerializer()); + serializers_.push_back(new JingleFileTransferDescriptionSerializer()); + serializers_.push_back(new JingleFileTransferHashSerializer()); + serializers_.push_back(new JingleFileTransferReceivedSerializer()); + serializers_.push_back(new JingleIBBTransportPayloadSerializer()); + serializers_.push_back(new JingleS5BTransportPayloadSerializer()); + serializers_.push_back(new JinglePayloadSerializer(this)); + serializers_.push_back(new S5BProxyRequestSerializer()); + foreach(PayloadSerializer* serializer, serializers_) { addSerializer(serializer); } diff --git a/Swiften/Serializer/PayloadSerializers/JingleContentPayloadSerializer.cpp b/Swiften/Serializer/PayloadSerializers/JingleContentPayloadSerializer.cpp new file mode 100644 index 0000000..90bd940 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/JingleContentPayloadSerializer.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swiften/Serializer/PayloadSerializers/JingleContentPayloadSerializer.h> + +#include <boost/shared_ptr.hpp> +#include <boost/smart_ptr/make_shared.hpp> +#include <boost/smart_ptr/intrusive_ptr.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Serializer/XML/XMLNode.h> +#include <Swiften/Serializer/XML/XMLElement.h> +#include <Swiften/Serializer/XML/XMLRawTextNode.h> + +#include <Swiften/Serializer/PayloadSerializers/JingleFileTransferDescriptionSerializer.h> + +#include <Swiften/Serializer/PayloadSerializers/JingleIBBTransportPayloadSerializer.h> +#include <Swiften/Serializer/PayloadSerializers/JingleS5BTransportPayloadSerializer.h> + +#include "Swiften/FileTransfer/JingleTransport.h" + +namespace Swift { + +JingleContentPayloadSerializer::JingleContentPayloadSerializer() { +} + +std::string JingleContentPayloadSerializer::serializePayload(boost::shared_ptr<JingleContentPayload> payload) const { + XMLElement payloadXML("content"); + payloadXML.setAttribute("creator", creatorToString(payload->getCreator())); + payloadXML.setAttribute("name", payload->getName()); + + if (!payload->getDescriptions().empty()) { + // JingleFileTransferDescription + JingleFileTransferDescriptionSerializer ftSerializer; + JingleFileTransferDescription::ref filetransfer; + + foreach(JingleDescription::ref desc, payload->getDescriptions()) { + if ((filetransfer = boost::dynamic_pointer_cast<JingleFileTransferDescription>(desc))) { + payloadXML.addNode(boost::make_shared<XMLRawTextNode>(ftSerializer.serializePayload(filetransfer))); + } + } + } + + if (!payload->getTransports().empty()) { + // JingleIBBTransportPayload + JingleIBBTransportPayloadSerializer ibbSerializer; + JingleIBBTransportPayload::ref ibb; + + // JingleS5BTransportPayload + JingleS5BTransportPayloadSerializer s5bSerializer; + JingleS5BTransportPayload::ref s5b; + + foreach(JingleTransportPayload::ref transport, payload->getTransports()) { + if ((ibb = boost::dynamic_pointer_cast<JingleIBBTransportPayload>(transport))) { + payloadXML.addNode(boost::make_shared<XMLRawTextNode>(ibbSerializer.serializePayload(ibb))); + } else if ((s5b = boost::dynamic_pointer_cast<JingleS5BTransportPayload>(transport))) { + payloadXML.addNode(boost::make_shared<XMLRawTextNode>(s5bSerializer.serializePayload(s5b))); + } + } + } + return payloadXML.serialize(); +} + +std::string JingleContentPayloadSerializer::creatorToString(JingleContentPayload::Creator creator) const { + switch(creator) { + case JingleContentPayload::InitiatorCreator: + return "initiator"; + case JingleContentPayload::ResponderCreator: + return "responder"; + case JingleContentPayload::UnknownCreator: + std::cerr << "Serializing unknown creator value." << std::endl; + return "ERROR ERROR ERROR"; + } + assert(false); +} +} diff --git a/Swiften/Serializer/PayloadSerializers/JingleContentPayloadSerializer.h b/Swiften/Serializer/PayloadSerializers/JingleContentPayloadSerializer.h new file mode 100644 index 0000000..2de0064 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/JingleContentPayloadSerializer.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + + +#pragma once + +#include <Swiften/Serializer/GenericPayloadSerializer.h> +#include <Swiften/Elements/JingleContentPayload.h> + +namespace Swift { + class PayloadSerializerCollection; + + class JingleContentPayloadSerializer : public GenericPayloadSerializer<JingleContentPayload> { + public: + JingleContentPayloadSerializer(); + + virtual std::string serializePayload(boost::shared_ptr<JingleContentPayload>) const; + + private: + std::string creatorToString(JingleContentPayload::Creator creator) const; + }; +} diff --git a/Swiften/Serializer/PayloadSerializers/JingleFileTransferDescriptionSerializer.cpp b/Swiften/Serializer/PayloadSerializers/JingleFileTransferDescriptionSerializer.cpp new file mode 100644 index 0000000..16337ff --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/JingleFileTransferDescriptionSerializer.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swiften/Serializer/PayloadSerializers/JingleFileTransferDescriptionSerializer.h> + +#include <boost/shared_ptr.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Serializer/XML/XMLNode.h> +#include <Swiften/Serializer/XML/XMLElement.h> +#include <Swiften/Serializer/XML/XMLRawTextNode.h> + +#include <Swiften/Serializer/PayloadSerializers/StreamInitiationFileInfoSerializer.h> + +namespace Swift { + +JingleFileTransferDescriptionSerializer::JingleFileTransferDescriptionSerializer() { +} + +std::string JingleFileTransferDescriptionSerializer::serializePayload(boost::shared_ptr<JingleFileTransferDescription> payload) const { + XMLElement description("description", "urn:xmpp:jingle:apps:file-transfer:3"); + StreamInitiationFileInfoSerializer fileInfoSerializer; + if (!payload->getOffers().empty()) { + boost::shared_ptr<XMLElement> offers = boost::make_shared<XMLElement>("offer"); + foreach(const StreamInitiationFileInfo &fileInfo, payload->getOffers()) { + boost::shared_ptr<XMLRawTextNode> fileInfoXML = boost::make_shared<XMLRawTextNode>(fileInfoSerializer.serialize(boost::make_shared<StreamInitiationFileInfo>(fileInfo))); + offers->addNode(fileInfoXML); + } + description.addNode(offers); + } + if (!payload->getRequests().empty()) { + boost::shared_ptr<XMLElement> requests = boost::make_shared<XMLElement>("request"); + foreach(const StreamInitiationFileInfo &fileInfo, payload->getRequests()) { + boost::shared_ptr<XMLRawTextNode> fileInfoXML = boost::make_shared<XMLRawTextNode>(fileInfoSerializer.serialize(boost::make_shared<StreamInitiationFileInfo>(fileInfo))); + requests->addNode(fileInfoXML); + } + description.addNode(requests); + } + return description.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/JingleFileTransferDescriptionSerializer.h b/Swiften/Serializer/PayloadSerializers/JingleFileTransferDescriptionSerializer.h new file mode 100644 index 0000000..5131435 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/JingleFileTransferDescriptionSerializer.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + + +#pragma once + +#include <Swiften/Serializer/GenericPayloadSerializer.h> +#include <Swiften/Elements/JingleFileTransferDescription.h> + + + +namespace Swift { + class PayloadSerializerCollection; + class XMLElement; + + class JingleFileTransferDescriptionSerializer : public GenericPayloadSerializer<JingleFileTransferDescription> { + public: + JingleFileTransferDescriptionSerializer(); + + virtual std::string serializePayload(boost::shared_ptr<JingleFileTransferDescription>) const; + }; +} diff --git a/Swiften/Serializer/PayloadSerializers/JingleFileTransferHashSerializer.cpp b/Swiften/Serializer/PayloadSerializers/JingleFileTransferHashSerializer.cpp new file mode 100644 index 0000000..2bd3afa --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/JingleFileTransferHashSerializer.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swiften/Serializer/PayloadSerializers/JingleFileTransferHashSerializer.h> + +#include <string> +#include <map> + +#include <boost/shared_ptr.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Serializer/XML/XMLNode.h> +#include <Swiften/Serializer/XML/XMLElement.h> +#include <Swiften/Serializer/XML/XMLRawTextNode.h> + + +namespace Swift { + +JingleFileTransferHashSerializer::JingleFileTransferHashSerializer() { +} + +std::string JingleFileTransferHashSerializer::serializePayload(boost::shared_ptr<JingleFileTransferHash> payload) const { + // code for version urn:xmpp:jingle:apps:file-transfer:2 + //XMLElement hash("hash", "urn:xmpp:jingle:apps:file-transfer:info:2", payload->getHash()); + + // code for version urn:xmpp:jingle:apps:file-transfer:3 + XMLElement checksum("checksum", "urn:xmpp:jingle:apps:file-transfer:3"); + boost::shared_ptr<XMLElement> file = boost::make_shared<XMLElement>("file"); + checksum.addNode(file); + boost::shared_ptr<XMLElement> hashes = boost::make_shared<XMLElement>("hashes", "urn:xmpp:hashes:0"); + file->addNode(hashes); + foreach(const JingleFileTransferHash::HashesMap::value_type& pair, payload->getHashes()) { + boost::shared_ptr<XMLElement> hash = boost::make_shared<XMLElement>("hash", "", pair.second); + hash->setAttribute("algo", pair.first); + hashes->addNode(hash); + } + + return checksum.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/JingleFileTransferHashSerializer.h b/Swiften/Serializer/PayloadSerializers/JingleFileTransferHashSerializer.h new file mode 100644 index 0000000..7fa6ac5 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/JingleFileTransferHashSerializer.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + + +#pragma once + +#include <Swiften/Serializer/GenericPayloadSerializer.h> +#include <Swiften/Elements/JingleFileTransferHash.h> + +namespace Swift { + class PayloadSerializerCollection; + class XMLElement; + + class JingleFileTransferHashSerializer : public GenericPayloadSerializer<JingleFileTransferHash> { + public: + JingleFileTransferHashSerializer(); + + virtual std::string serializePayload(boost::shared_ptr<JingleFileTransferHash>) const; + }; +} diff --git a/Swiften/Serializer/PayloadSerializers/JingleFileTransferReceivedSerializer.cpp b/Swiften/Serializer/PayloadSerializers/JingleFileTransferReceivedSerializer.cpp new file mode 100644 index 0000000..40be70e --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/JingleFileTransferReceivedSerializer.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swiften/Serializer/PayloadSerializers/JingleFileTransferReceivedSerializer.h> + +#include <boost/shared_ptr.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Serializer/XML/XMLNode.h> +#include <Swiften/Serializer/XML/XMLElement.h> +#include <Swiften/Serializer/XML/XMLRawTextNode.h> + +#include <Swiften/Serializer/XML/XMLRawTextNode.h> + +namespace Swift { + +JingleFileTransferReceivedSerializer::JingleFileTransferReceivedSerializer() { +} + +std::string JingleFileTransferReceivedSerializer::serializePayload(boost::shared_ptr<JingleFileTransferReceived> payload) const { + XMLElement receivedElement("received", "urn:xmpp:jingle:apps:file-transfer:3"); + XMLElement::ref fileElement = boost::make_shared<XMLElement>("file", "http://jabber.org/protocol/si/profile/file-transfer"); + fileElement->setAttribute("hash", payload->getFileInfo().getHash()); + if (payload->getFileInfo().getAlgo() != "md5") { + fileElement->setAttribute("algo", payload->getFileInfo().getAlgo()); + } + receivedElement.addNode(fileElement); + return receivedElement.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/JingleFileTransferReceivedSerializer.h b/Swiften/Serializer/PayloadSerializers/JingleFileTransferReceivedSerializer.h new file mode 100644 index 0000000..4151dd0 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/JingleFileTransferReceivedSerializer.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + + +#pragma once + +#include <Swiften/Serializer/GenericPayloadSerializer.h> +#include <Swiften/Elements/JingleFileTransferReceived.h> + +namespace Swift { + class PayloadSerializerCollection; + class XMLElement; + + class JingleFileTransferReceivedSerializer : public GenericPayloadSerializer<JingleFileTransferReceived> { + public: + JingleFileTransferReceivedSerializer(); + + virtual std::string serializePayload(boost::shared_ptr<JingleFileTransferReceived>) const; + }; +} diff --git a/Swiften/Serializer/PayloadSerializers/JingleIBBTransportPayloadSerializer.cpp b/Swiften/Serializer/PayloadSerializers/JingleIBBTransportPayloadSerializer.cpp new file mode 100644 index 0000000..029a5b4 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/JingleIBBTransportPayloadSerializer.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swiften/Serializer/PayloadSerializers/JingleIBBTransportPayloadSerializer.h> + +#include <boost/shared_ptr.hpp> +#include <boost/smart_ptr/make_shared.hpp> +#include <boost/lexical_cast.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Serializer/XML/XMLNode.h> +#include <Swiften/Serializer/XML/XMLElement.h> +#include <Swiften/Serializer/XML/XMLRawTextNode.h> + +namespace Swift { + +JingleIBBTransportPayloadSerializer::JingleIBBTransportPayloadSerializer() { +} + +std::string JingleIBBTransportPayloadSerializer::serializePayload(boost::shared_ptr<JingleIBBTransportPayload> payload) const { + XMLElement payloadXML("transport", "urn:xmpp:jingle:transports:ibb:1"); + payloadXML.setAttribute("block-size", boost::lexical_cast<std::string>(payload->getBlockSize())); + payloadXML.setAttribute("sid", payload->getSessionID()); + + return payloadXML.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/JingleIBBTransportPayloadSerializer.h b/Swiften/Serializer/PayloadSerializers/JingleIBBTransportPayloadSerializer.h new file mode 100644 index 0000000..ac9cba9 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/JingleIBBTransportPayloadSerializer.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + + +#pragma once + +#include <Swiften/Serializer/GenericPayloadSerializer.h> +#include <Swiften/Elements/JingleIBBTransportPayload.h> + + + +namespace Swift { + class PayloadSerializerCollection; + class XMLElement; + + class JingleIBBTransportPayloadSerializer : public GenericPayloadSerializer<JingleIBBTransportPayload> { + public: + JingleIBBTransportPayloadSerializer(); + + virtual std::string serializePayload(boost::shared_ptr<JingleIBBTransportPayload>) const; + }; +} diff --git a/Swiften/Serializer/PayloadSerializers/JinglePayloadSerializer.cpp b/Swiften/Serializer/PayloadSerializers/JinglePayloadSerializer.cpp new file mode 100644 index 0000000..25d35ff --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/JinglePayloadSerializer.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swiften/Serializer/PayloadSerializers/JinglePayloadSerializer.h> + +#include <boost/shared_ptr.hpp> +#include <boost/smart_ptr/make_shared.hpp> +#include <boost/smart_ptr/intrusive_ptr.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Serializer/XML/XMLNode.h> +#include <Swiften/Serializer/XML/XMLElement.h> +#include <Swiften/Serializer/XML/XMLRawTextNode.h> +#include <Swiften/Serializer/PayloadSerializers/JingleContentPayloadSerializer.h> +#include <Swiften/Serializer/PayloadSerializers/JingleFileTransferHashSerializer.h> +#include <Swiften/Serializer/PayloadSerializers/JingleFileTransferReceivedSerializer.h> + +#include <Swiften/Serializer/PayloadSerializerCollection.h> + +#include <Swiften/Elements/JinglePayload.h> +#include <Swiften/Elements/JingleContentPayload.h> +#include <Swiften/Elements/JingleIBBTransportPayload.h> +#include <Swiften/Elements/JingleFileTransferDescription.h> +#include <Swiften/Elements/JingleFileTransferHash.h> +#include <Swiften/Elements/JingleFileTransferReceived.h> + +namespace Swift { + +JinglePayloadSerializer::JinglePayloadSerializer(PayloadSerializerCollection* serializers) : serializers(serializers) { +} + +std::string JinglePayloadSerializer::serializePayload(boost::shared_ptr<JinglePayload> payload) const { + XMLElement jinglePayload("jingle", "urn:xmpp:jingle:1"); + jinglePayload.setAttribute("action", actionToString(payload->getAction())); + jinglePayload.setAttribute("initiator", payload->getInitiator()); + jinglePayload.setAttribute("sid", payload->getSessionID()); + + if (!payload->getPayloads().empty()) { + foreach(boost::shared_ptr<Payload> subPayload, payload->getPayloads()) { + PayloadSerializer* serializer = serializers->getPayloadSerializer(subPayload); + if (serializer) { + jinglePayload.addNode(boost::shared_ptr<XMLRawTextNode>(new XMLRawTextNode(serializer->serialize(subPayload)))); + } + } + } + + if (payload->getReason().is_initialized()) { + boost::shared_ptr<XMLElement> reason = boost::make_shared<XMLElement>("reason"); + reason->addNode(boost::make_shared<XMLElement>(reasonTypeToString(payload->getReason()->type))); + if (!payload->getReason()->text.empty()) { + reason->addNode(boost::make_shared<XMLElement>("desc", "", payload->getReason()->text)); + } + jinglePayload.addNode(reason); + } + + return jinglePayload.serialize(); +} + +std::string JinglePayloadSerializer::actionToString(JinglePayload::Action action) const { + switch(action) { + case JinglePayload::ContentAccept: + return "content-accept"; + case JinglePayload::ContentAdd: + return "content-add"; + case JinglePayload::ContentModify: + return "content-modify"; + case JinglePayload::ContentReject: + return "content-reject"; + case JinglePayload::ContentRemove: + return "content-remove"; + case JinglePayload::DescriptionInfo: + return "description-info"; + case JinglePayload::SecurityInfo: + return "security-info"; + case JinglePayload::SessionAccept: + return "session-accept"; + case JinglePayload::SessionInfo: + return "session-info"; + case JinglePayload::SessionInitiate: + return "session-initiate"; + case JinglePayload::SessionTerminate: + return "session-terminate"; + case JinglePayload::TransportAccept: + return "transport-accept"; + case JinglePayload::TransportInfo: + return "transport-info"; + case JinglePayload::TransportReject: + return "transport-reject"; + case JinglePayload::TransportReplace: + return "transport-replace"; + case JinglePayload::UnknownAction: + std::cerr << "Serializing unknown action value." << std::endl; + return ""; + } + assert(false); +} + +std::string JinglePayloadSerializer::reasonTypeToString(JinglePayload::Reason::Type type) const { + switch(type) { + case JinglePayload::Reason::UnknownType: + std::cerr << "Unknown jingle reason type!" << std::endl; + return ""; + case JinglePayload::Reason::AlternativeSession: + return "alternative-session"; + case JinglePayload::Reason::Busy: + return "busy"; + case JinglePayload::Reason::Cancel: + return "cancel"; + case JinglePayload::Reason::ConnectivityError: + return "connectivity-error"; + case JinglePayload::Reason::Decline: + return "decline"; + case JinglePayload::Reason::Expired: + return "expired"; + case JinglePayload::Reason::FailedApplication: + return "failed-application"; + case JinglePayload::Reason::FailedTransport: + return "failed-transport"; + case JinglePayload::Reason::GeneralError: + return "general-error"; + case JinglePayload::Reason::Gone: + return "gone"; + case JinglePayload::Reason::IncompatibleParameters: + return "incompatible-parameters"; + case JinglePayload::Reason::MediaError: + return "media-error"; + case JinglePayload::Reason::SecurityError: + return "security-error"; + case JinglePayload::Reason::Success: + return "success"; + case JinglePayload::Reason::Timeout: + return "timeout"; + case JinglePayload::Reason::UnsupportedApplications: + return "unsupported-applications"; + case JinglePayload::Reason::UnsupportedTransports: + return "unsupported-transports"; + } + assert(false); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/JinglePayloadSerializer.h b/Swiften/Serializer/PayloadSerializers/JinglePayloadSerializer.h new file mode 100644 index 0000000..ccdb6d0 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/JinglePayloadSerializer.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + + +#pragma once + +#include <Swiften/Serializer/GenericPayloadSerializer.h> +#include <Swiften/Elements/JinglePayload.h> + +namespace Swift { + class PayloadSerializerCollection; + class XMLElement; + + class JinglePayloadSerializer : public GenericPayloadSerializer<JinglePayload> { + public: + JinglePayloadSerializer(PayloadSerializerCollection*); + + virtual std::string serializePayload(boost::shared_ptr<JinglePayload>) const; + + private: + std::string actionToString(JinglePayload::Action action) const; + std::string reasonTypeToString(JinglePayload::Reason::Type type) const; + + private: + PayloadSerializerCollection* serializers; + }; +} diff --git a/Swiften/Serializer/PayloadSerializers/JingleS5BTransportPayloadSerializer.cpp b/Swiften/Serializer/PayloadSerializers/JingleS5BTransportPayloadSerializer.cpp new file mode 100644 index 0000000..c5b40d5 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/JingleS5BTransportPayloadSerializer.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swiften/Serializer/PayloadSerializers/JingleS5BTransportPayloadSerializer.h> + +#include <boost/shared_ptr.hpp> +#include <boost/smart_ptr/make_shared.hpp> +#include <boost/lexical_cast.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Serializer/XML/XMLNode.h> +#include <Swiften/Serializer/XML/XMLElement.h> +#include <Swiften/Serializer/XML/XMLRawTextNode.h> +#include <Swiften/Base/Log.h> + +namespace Swift { + +JingleS5BTransportPayloadSerializer::JingleS5BTransportPayloadSerializer() { +} + +std::string JingleS5BTransportPayloadSerializer::serializePayload(boost::shared_ptr<JingleS5BTransportPayload> payload) const { + XMLElement payloadXML("transport", "urn:xmpp:jingle:transports:s5b:1"); + payloadXML.setAttribute("sid", payload->getSessionID()); + payloadXML.setAttribute("mode", modeToString(payload->getMode())); + + foreach(JingleS5BTransportPayload::Candidate candidate, payload->getCandidates()) { + boost::shared_ptr<XMLElement> candidateXML = boost::make_shared<XMLElement>("candidate"); + candidateXML->setAttribute("cid", candidate.cid); + candidateXML->setAttribute("host", candidate.hostPort.getAddress().toString()); + candidateXML->setAttribute("jid", candidate.jid.toString()); + candidateXML->setAttribute("port", boost::lexical_cast<std::string>(candidate.hostPort.getPort())); + candidateXML->setAttribute("priority", boost::lexical_cast<std::string>(candidate.priority)); + candidateXML->setAttribute("type", typeToString(candidate.type)); + payloadXML.addNode(candidateXML); + } + + if (payload->hasCandidateError()) { + payloadXML.addNode(boost::make_shared<XMLElement>("candidate-error")); + } + if (payload->hasProxyError()) { + payloadXML.addNode(boost::make_shared<XMLElement>("proxy-error")); + } + + if (!payload->getActivated().empty()) { + boost::shared_ptr<XMLElement> activatedXML = boost::make_shared<XMLElement>("activated"); + activatedXML->setAttribute("cid", payload->getActivated()); + payloadXML.addNode(activatedXML); + } + if (!payload->getCandidateUsed().empty()) { + boost::shared_ptr<XMLElement> candusedXML = boost::make_shared<XMLElement>("candidate-used"); + candusedXML->setAttribute("cid", payload->getCandidateUsed()); + payloadXML.addNode(candusedXML); + } + + return payloadXML.serialize(); +} + +std::string JingleS5BTransportPayloadSerializer::modeToString(JingleS5BTransportPayload::Mode mode) const { + switch(mode) { + case JingleS5BTransportPayload::TCPMode: + return "tcp"; + case JingleS5BTransportPayload::UDPMode: + return "udp"; + } + assert(false); +} + +std::string JingleS5BTransportPayloadSerializer::typeToString(JingleS5BTransportPayload::Candidate::Type type) const { + switch(type) { + case JingleS5BTransportPayload::Candidate::AssistedType: + return "assisted"; + case JingleS5BTransportPayload::Candidate::DirectType: + return "direct"; + case JingleS5BTransportPayload::Candidate::ProxyType: + return "proxy"; + case JingleS5BTransportPayload::Candidate::TunnelType: + return "tunnel"; + } + assert(false); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/JingleS5BTransportPayloadSerializer.h b/Swiften/Serializer/PayloadSerializers/JingleS5BTransportPayloadSerializer.h new file mode 100644 index 0000000..210688d --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/JingleS5BTransportPayloadSerializer.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + + +#pragma once + +#include <Swiften/Serializer/GenericPayloadSerializer.h> +#include <Swiften/Elements/JingleS5BTransportPayload.h> + +namespace Swift { + class PayloadSerializerCollection; + class XMLElement; + + class JingleS5BTransportPayloadSerializer : public GenericPayloadSerializer<JingleS5BTransportPayload> { + public: + JingleS5BTransportPayloadSerializer(); + + virtual std::string serializePayload(boost::shared_ptr<JingleS5BTransportPayload>) const; + + private: + std::string modeToString(JingleS5BTransportPayload::Mode) const; + std::string typeToString(JingleS5BTransportPayload::Candidate::Type) const; + }; +} diff --git a/Swiften/Serializer/PayloadSerializers/S5BProxyRequestSerializer.h b/Swiften/Serializer/PayloadSerializers/S5BProxyRequestSerializer.h new file mode 100644 index 0000000..b523588 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/S5BProxyRequestSerializer.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + + +#pragma once + +#include <boost/lexical_cast.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Elements/S5BProxyRequest.h> +#include <Swiften/Serializer/GenericPayloadSerializer.h> +#include <Swiften/Serializer/XML/XMLElement.h> + +namespace Swift { + class PayloadSerializerCollection; + + class S5BProxyRequestSerializer : public GenericPayloadSerializer<S5BProxyRequest> { + public: + virtual std::string serializePayload(boost::shared_ptr<S5BProxyRequest> s5bProxyRequest) const { + XMLElement queryElement("query", "http://jabber.org/protocol/bytestreams"); + if (s5bProxyRequest && s5bProxyRequest->getStreamHost()) { + boost::shared_ptr<XMLElement> streamHost = boost::make_shared<XMLElement>("streamhost"); + streamHost->setAttribute("host", s5bProxyRequest->getStreamHost().get().addressPort.getAddress().toString()); + streamHost->setAttribute("port", boost::lexical_cast<std::string>(s5bProxyRequest->getStreamHost().get().addressPort.getPort())); + streamHost->setAttribute("jid", s5bProxyRequest->getStreamHost().get().jid.toString()); + queryElement.addNode(streamHost); + } else if (s5bProxyRequest && s5bProxyRequest->getActivate()) { + queryElement.setAttribute("sid", s5bProxyRequest->getSID()); + boost::shared_ptr<XMLElement> activate = boost::make_shared<XMLElement>("activate", "", s5bProxyRequest->getActivate().get().toString()); + queryElement.addNode(activate); + } + return queryElement.serialize(); + } + }; +} diff --git a/Swiften/Serializer/PayloadSerializers/StreamInitiationFileInfoSerializer.cpp b/Swiften/Serializer/PayloadSerializers/StreamInitiationFileInfoSerializer.cpp new file mode 100644 index 0000000..7b0cad8 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/StreamInitiationFileInfoSerializer.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swiften/Serializer/PayloadSerializers/StreamInitiationFileInfoSerializer.h> + +#include <boost/shared_ptr.hpp> +#include <boost/smart_ptr/make_shared.hpp> +#include <boost/lexical_cast.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/DateTime.h> +#include <Swiften/Serializer/XML/XMLElement.h> +#include <Swiften/Serializer/XML/XMLRawTextNode.h> +#include <Swiften/Serializer/XML/XMLTextNode.h> + + + +namespace Swift { + +StreamInitiationFileInfoSerializer::StreamInitiationFileInfoSerializer() { +} + +std::string StreamInitiationFileInfoSerializer::serializePayload(boost::shared_ptr<StreamInitiationFileInfo> fileInfo) const { + XMLElement fileElement("file", "http://jabber.org/protocol/si/profile/file-transfer"); + + if (fileInfo->getDate() != stringToDateTime("")) { + fileElement.setAttribute("date", dateTimeToString(fileInfo->getDate())); + } + fileElement.setAttribute("hash", fileInfo->getHash()); + if (fileInfo->getAlgo() != "md5") { + fileElement.setAttribute("algo", fileInfo->getAlgo()); + } + if (!fileInfo->getName().empty()) { + fileElement.setAttribute("name", fileInfo->getName()); + } + if (fileInfo->getSize() != 0) { + fileElement.setAttribute("size", boost::lexical_cast<std::string>(fileInfo->getSize())); + } + if (!fileInfo->getDescription().empty()) { + boost::shared_ptr<XMLElement> desc = boost::make_shared<XMLElement>("desc", "", fileInfo->getDescription()); + fileElement.addNode(desc); + } + if (fileInfo->getSupportsRangeRequests()) { + boost::shared_ptr<XMLElement> range = boost::make_shared<XMLElement>("range"); + if (fileInfo->getRangeOffset() != 0) { + range->setAttribute("offset", boost::lexical_cast<std::string>(fileInfo->getRangeOffset())); + } + fileElement.addNode(range); + } + return fileElement.serialize(); +} + +} diff --git a/Swiften/Serializer/PayloadSerializers/StreamInitiationFileInfoSerializer.h b/Swiften/Serializer/PayloadSerializers/StreamInitiationFileInfoSerializer.h new file mode 100644 index 0000000..4ac0a0d --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/StreamInitiationFileInfoSerializer.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + + +#pragma once + +#include <Swiften/Serializer/GenericPayloadSerializer.h> +#include <Swiften/Elements/StreamInitiationFileInfo.h> + +#include <Swiften/Serializer/XML/XMLElement.h> + +namespace Swift { + class PayloadSerializerCollection; + + class StreamInitiationFileInfoSerializer : public GenericPayloadSerializer<StreamInitiationFileInfo> { + public: + StreamInitiationFileInfoSerializer(); + + virtual std::string serializePayload(boost::shared_ptr<StreamInitiationFileInfo>) const; + }; +} diff --git a/Swiften/Serializer/PayloadSerializers/StreamInitiationSerializer.cpp b/Swiften/Serializer/PayloadSerializers/StreamInitiationSerializer.cpp index 3b71bfb..9ccfab2 100644 --- a/Swiften/Serializer/PayloadSerializers/StreamInitiationSerializer.cpp +++ b/Swiften/Serializer/PayloadSerializers/StreamInitiationSerializer.cpp @@ -36,13 +36,13 @@ std::string StreamInitiationSerializer::serializePayload(boost::shared_ptr<Strea if (streamInitiation->getFileInfo()) { StreamInitiationFileInfo file = *streamInitiation->getFileInfo(); boost::shared_ptr<XMLElement> fileElement(new XMLElement("file", "http://jabber.org/protocol/si/profile/file-transfer")); - fileElement->setAttribute("name", file.name); - if (file.size != -1) { - fileElement->setAttribute("size", boost::lexical_cast<std::string>(file.size)); + fileElement->setAttribute("name", file.getName()); + if (file.getSize() != 0) { + fileElement->setAttribute("size", boost::lexical_cast<std::string>(file.getSize())); } - if (!file.description.empty()) { + if (!file.getDescription().empty()) { boost::shared_ptr<XMLElement> descElement(new XMLElement("desc")); - descElement->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(file.description))); + descElement->addNode(boost::shared_ptr<XMLTextNode>(new XMLTextNode(file.getDescription()))); fileElement->addNode(descElement); } siElement.addNode(fileElement); diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/JingleSerializersTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/JingleSerializersTest.cpp new file mode 100644 index 0000000..e3ec8fc --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/JingleSerializersTest.cpp @@ -0,0 +1,512 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <boost/shared_ptr.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include <Swiften/Serializer/PayloadSerializers/JingleFileTransferDescriptionSerializer.h> +#include <Swiften/Serializer/PayloadSerializers/StreamInitiationFileInfoSerializer.h> +#include <Swiften/Serializer/PayloadSerializers/JinglePayloadSerializer.h> +#include <Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h> +#include <Swiften/Elements/JingleFileTransferDescription.h> +#include <Swiften/Elements/StreamInitiationFileInfo.h> +#include <Swiften/Elements/JingleIBBTransportPayload.h> +#include <Swiften/Elements/JingleS5BTransportPayload.h> +#include <Swiften/Elements/JingleFileTransferHash.h> +#include <Swiften/Elements/JinglePayload.h> +#include <Swiften/Elements/JingleFileTransferReceived.h> +#include <Swiften/Base/DateTime.h> + +using namespace Swift; + +class JingleSerializersTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(JingleSerializersTest); + CPPUNIT_TEST(testSerialize_StreamInitiationFileInfo); + CPPUNIT_TEST(testSerialize_StreamInitiationFileInfoRange); + + CPPUNIT_TEST(testSerialize_Xep0261_Example1); + CPPUNIT_TEST(testSerialize_Xep0261_Example9); + CPPUNIT_TEST(testSerialize_Xep0261_Example13); + + CPPUNIT_TEST(testSerialize_Xep0234_Example1); + CPPUNIT_TEST(testSerialize_Xep0234_Example3); + CPPUNIT_TEST(testSerialize_Xep0234_Example5); + CPPUNIT_TEST(testSerialize_Xep0234_Example8); + CPPUNIT_TEST(testSerialize_Xep0234_Example10); + CPPUNIT_TEST(testSerialize_Xep0234_Example13); + + CPPUNIT_TEST(testSerialize_Xep0260_Example1); + + CPPUNIT_TEST_SUITE_END(); + + boost::shared_ptr<JinglePayloadSerializer> createTestling() { + return boost::make_shared<JinglePayloadSerializer>(&collection); + } + + + public: + void testSerialize_StreamInitiationFileInfo() { + std::string expected = "<file" + " date=\"1969-07-21T02:56:15Z\"" + " hash=\"552da749930852c69ae5d2141d3766b1\"" + " name=\"test.txt\"" + " size=\"1022\"" + " xmlns=\"http://jabber.org/protocol/si/profile/file-transfer\">" + "<desc>This is a test. If this were a real file...</desc>" + "<range/>" + "</file>"; + + StreamInitiationFileInfo::ref fileInfo = boost::make_shared<StreamInitiationFileInfo>(); + fileInfo->setDate(stringToDateTime("1969-07-21T02:56:15Z")); + fileInfo->setHash("552da749930852c69ae5d2141d3766b1"); + fileInfo->setSize(1022); + fileInfo->setName("test.txt"); + fileInfo->setDescription("This is a test. If this were a real file..."); + fileInfo->setSupportsRangeRequests(true); + + boost::shared_ptr<StreamInitiationFileInfoSerializer> serializer = boost::make_shared<StreamInitiationFileInfoSerializer>(); + CPPUNIT_ASSERT_EQUAL(expected, serializer->serializePayload(fileInfo)); + } + + void testSerialize_StreamInitiationFileInfoRange() { + std::string expected = "<file hash=\"552da749930852c69ae5d2141d3766b1\"" + " xmlns=\"http://jabber.org/protocol/si/profile/file-transfer\">" + "<range offset=\"270336\"/>" + "</file>"; + + StreamInitiationFileInfo::ref fileInfo = boost::make_shared<StreamInitiationFileInfo>(); + fileInfo->setHash("552da749930852c69ae5d2141d3766b1"); + fileInfo->setSupportsRangeRequests(true); + fileInfo->setRangeOffset(270336); + + boost::shared_ptr<StreamInitiationFileInfoSerializer> serializer = boost::make_shared<StreamInitiationFileInfoSerializer>(); + CPPUNIT_ASSERT_EQUAL(expected, serializer->serializePayload(fileInfo)); + } + + + // IBB Transport Method Examples + + // http://xmpp.org/extensions/xep-0261.html#example-1 + void testSerialize_Xep0261_Example1() { + std::string expected = + "<jingle action=\"session-initiate\"" + " initiator=\"romeo@montague.lit/orchard\"" + " sid=\"a73sjjvkla37jfea\"" + " xmlns=\"urn:xmpp:jingle:1\">" + "<content creator=\"initiator\" name=\"ex\">" + "<transport block-size=\"4096\"" + " sid=\"ch3d9s71\"" + " xmlns=\"urn:xmpp:jingle:transports:ibb:1\"/>" + "</content>" + "</jingle>"; + + JinglePayload::ref payload = boost::make_shared<JinglePayload>(); + payload->setAction(JinglePayload::SessionInitiate); + payload->setSessionID("a73sjjvkla37jfea"); + payload->setInitiator(JID("romeo@montague.lit/orchard")); + + JingleIBBTransportPayload::ref transport = boost::make_shared<JingleIBBTransportPayload>(); + transport->setBlockSize(4096); + transport->setSessionID("ch3d9s71"); + + JingleContentPayload::ref content = boost::make_shared<JingleContentPayload>(); + content->setCreator(JingleContentPayload::InitiatorCreator); + content->setName("ex"); + content->addTransport(transport); + + payload->addPayload(content); + + CPPUNIT_ASSERT_EQUAL(expected, createTestling()->serialize(payload)); + } + + // http://xmpp.org/extensions/xep-0261.html#example-9 + void testSerialize_Xep0261_Example9() { + std::string expected = + "<jingle action=\"transport-info\"" + " initiator=\"romeo@montague.lit/orchard\"" + " sid=\"a73sjjvkla37jfea\"" + " xmlns=\"urn:xmpp:jingle:1\">" + "<content creator=\"initiator\" name=\"ex\">" + "<transport block-size=\"2048\"" + " sid=\"bt8a71h6\"" + " xmlns=\"urn:xmpp:jingle:transports:ibb:1\"/>" + "</content>" + "</jingle>"; + + JinglePayload::ref payload = boost::make_shared<JinglePayload>(); + payload->setAction(JinglePayload::TransportInfo); + payload->setInitiator(JID("romeo@montague.lit/orchard")); + payload->setSessionID("a73sjjvkla37jfea"); + + JingleContentPayload::ref content = boost::make_shared<JingleContentPayload>(); + content->setCreator(JingleContentPayload::InitiatorCreator); + content->setName("ex"); + + JingleIBBTransportPayload::ref transport = boost::make_shared<JingleIBBTransportPayload>(); + transport->setBlockSize(2048); + transport->setSessionID("bt8a71h6"); + + content->addTransport(transport); + payload->addPayload(content); + + CPPUNIT_ASSERT_EQUAL(expected, createTestling()->serialize(payload)); + } + + // http://xmpp.org/extensions/xep-0261.html#example-13 + void testSerialize_Xep0261_Example13() { + std::string expected = + "<jingle action=\"session-terminate\"" + " initiator=\"romeo@montague.lit/orchard\"" + " sid=\"a73sjjvkla37jfea\"" + " xmlns=\"urn:xmpp:jingle:1\">" + "<reason><success/></reason>" + "</jingle>"; + + JinglePayload::ref payload = boost::make_shared<JinglePayload>(); + payload->setAction(JinglePayload::SessionTerminate); + payload->setInitiator(JID("romeo@montague.lit/orchard")); + payload->setSessionID("a73sjjvkla37jfea"); + payload->setReason(JinglePayload::Reason(JinglePayload::Reason::Success)); + + CPPUNIT_ASSERT_EQUAL(expected, createTestling()->serialize(payload)); + } + + // http://xmpp.org/extensions/xep-0234.html#example-1 + void testSerialize_Xep0234_Example1() { + std::string expected = "<description xmlns=\"urn:xmpp:jingle:apps:file-transfer:3\">" + "<offer>" + "<file" + " date=\"1969-07-21T02:56:15Z\"" + " hash=\"552da749930852c69ae5d2141d3766b1\"" + " name=\"test.txt\"" + " size=\"1022\"" + " xmlns=\"http://jabber.org/protocol/si/profile/file-transfer\">" + "<desc>This is a test. If this were a real file...</desc>" + "<range/>" + "</file>" + "</offer>" + "</description>"; + JingleFileTransferDescription::ref desc = boost::make_shared<JingleFileTransferDescription>(); + StreamInitiationFileInfo fileInfo; + + fileInfo.setDate(stringToDateTime("1969-07-21T02:56:15Z")); + fileInfo.setHash("552da749930852c69ae5d2141d3766b1"); + fileInfo.setSize(1022); + fileInfo.setName("test.txt"); + fileInfo.setDescription("This is a test. If this were a real file..."); + fileInfo.setSupportsRangeRequests(true); + + desc->addOffer(fileInfo); + + CPPUNIT_ASSERT_EQUAL(expected, boost::make_shared<JingleFileTransferDescriptionSerializer>()->serialize(desc)); + } + + // http://xmpp.org/extensions/xep-0234.html#example-3 + void testSerialize_Xep0234_Example3() { + std::string expected = + "<jingle action=\"session-accept\"" + " initiator=\"romeo@montague.lit/orchard\"" + " sid=\"851ba2\"" + " xmlns=\"urn:xmpp:jingle:1\">" + "<content creator=\"initiator\" name=\"a-file-offer\">" + "<description xmlns=\"urn:xmpp:jingle:apps:file-transfer:3\">" + "<offer>" + "<file" + " date=\"1969-07-21T02:56:15Z\"" + " hash=\"552da749930852c69ae5d2141d3766b1\"" + " name=\"test.txt\"" + " size=\"1022\"" + " xmlns=\"http://jabber.org/protocol/si/profile/file-transfer\">" + "<desc>This is a test. If this were a real file...</desc>" + "<range/>" + "</file>" + "</offer>" + "</description>" + /*"<transport xmlns=\"urn:xmpp:jingle:transports:s5b:1\"" + " mode=\"tcp\"" + " sid=\"vj3hs98y\">" + "<candidate cid=\"ht567dq\"" + " host=\"192.169.1.10\"" + " jid=\"juliet@capulet.lit/balcony\"" + " port=\"6539\"" + " priority=\"8257636\"" + " type=\"direct\"/>" + "<candidate cid=\"hr65dqyd\"" + " host=\"134.102.201.180\"" + " jid=\"juliet@capulet.lit/balcony\"" + " port=\"16453\"" + " priority=\"7929856\"" + " type=\"assisted\"/>" + "<candidate cid=\"grt654q2\"" + " host=\"2001:638:708:30c9:219:d1ff:fea4:a17d\"" + " jid=\"juliet@capulet.lit/balcony\"" + " port=\"6539\"" + " priority=\"8257606\"" + " type=\"direct\"/>" + "</transport>"*/ + "</content>" + "</jingle>"; + + JinglePayload::ref payload = boost::make_shared<JinglePayload>(); + payload->setAction(JinglePayload::SessionAccept); + payload->setInitiator(JID("romeo@montague.lit/orchard")); + payload->setSessionID("851ba2"); + + JingleContentPayload::ref content = boost::make_shared<JingleContentPayload>(); + content->setCreator(JingleContentPayload::InitiatorCreator); + content->setName("a-file-offer"); + + JingleFileTransferDescription::ref description = boost::make_shared<JingleFileTransferDescription>(); + StreamInitiationFileInfo fileInfo; + fileInfo.setName("test.txt"); + fileInfo.setSize(1022); + fileInfo.setHash("552da749930852c69ae5d2141d3766b1"); + fileInfo.setDate(stringToDateTime("1969-07-21T02:56:15Z")); + fileInfo.setDescription("This is a test. If this were a real file..."); + fileInfo.setSupportsRangeRequests(true); + + description->addOffer(fileInfo); + content->addDescription(description); + payload->addPayload(content); + + CPPUNIT_ASSERT_EQUAL(expected, createTestling()->serialize(payload)); + } + + // http://xmpp.org/extensions/xep-0234.html#example-5 + void testSerialize_Xep0234_Example5() { + std::string expected = + "<jingle" + " action=\"transport-info\"" + " initiator=\"romeo@montague.lit/orchard\"" + " sid=\"a73sjjvkla37jfea\"" + " xmlns=\"urn:xmpp:jingle:1\">" + "<content creator=\"initiator\" name=\"ex\"/>" + /*"<transport" + " sid=\"vj3hs98y\"" + " xmlns=\"urn:xmpp:jingle:transports:s5b:1\">" + "<candidate-used cid=\"hr65dqyd\"/>" + "</transport>"*/ + //"</content>" + "</jingle>"; + + JinglePayload::ref payload = boost::make_shared<JinglePayload>(); + payload->setAction(JinglePayload::TransportInfo); + payload->setInitiator(JID("romeo@montague.lit/orchard")); + payload->setSessionID("a73sjjvkla37jfea"); + + JingleContentPayload::ref content = boost::make_shared<JingleContentPayload>(); + content->setCreator(JingleContentPayload::InitiatorCreator); + content->setName("ex"); + payload->addPayload(content); + + CPPUNIT_ASSERT_EQUAL(expected, createTestling()->serialize(payload)); + } + + // http://xmpp.org/extensions/xep-0234.html#example-8 + void testSerialize_Xep0234_Example8() { + std::string expected = + "<jingle" + " action=\"session-info\"" + " initiator=\"romeo@montague.lit/orchard\"" + " sid=\"a73sjjvkla37jfea\"" + " xmlns=\"urn:xmpp:jingle:1\">" + "<checksum xmlns=\"urn:xmpp:jingle:apps:file-transfer:3\">" + "<file>" + "<hashes xmlns=\"urn:xmpp:hashes:0\">" + "<hash algo=\"sha-1\">552da749930852c69ae5d2141d3766b1</hash>" + "</hashes>" + "</file>" + "</checksum>" + "</jingle>"; + + JinglePayload::ref payload = boost::make_shared<JinglePayload>(); + payload->setAction(JinglePayload::SessionInfo); + payload->setInitiator(JID("romeo@montague.lit/orchard")); + payload->setSessionID("a73sjjvkla37jfea"); + + JingleFileTransferHash::ref hash = boost::make_shared<JingleFileTransferHash>(); + hash->setHash("sha-1", "552da749930852c69ae5d2141d3766b1"); + + payload->addPayload(hash); + + CPPUNIT_ASSERT_EQUAL(expected, createTestling()->serialize(payload)); + } + + // http://xmpp.org/extensions/xep-0234.html#example-10 + void testSerialize_Xep0234_Example10() { + std::string expected = + "<jingle" + " action=\"session-initiate\"" + " initiator=\"romeo@montague.lit/orchard\"" + " sid=\"uj3b2\"" + " xmlns=\"urn:xmpp:jingle:1\">" + "<content creator=\"initiator\" name=\"a-file-request\">" + "<description" + " xmlns=\"urn:xmpp:jingle:apps:file-transfer:3\">" + "<request>" + "<file" + " hash=\"552da749930852c69ae5d2141d3766b1\"" + " xmlns=\"http://jabber.org/protocol/si/profile/file-transfer\">" + "<range offset=\"270336\"/>" + "</file>" + "</request>" + "</description>" + /*"<transport" + " mode=\"tcp\"" + " sid=\"xig361fj\"" + " xmlns=\"urn:xmpp:jingle:transports:s5b:1\">" + "<candidate" + " cid=\"ht567dq\"" + " host=\"192.169.1.10\"" + " jid=\"juliet@capulet.lit/balcony\"" + " port=\"6539\"" + " priority=\"8257636\"" + " type=\"direct\"/>" + "<candidate" + " cid=\"hr65dqyd\"" + " host=\"134.102.201.180\"" + " jid=\"juliet@capulet.lit/balcony\"" + " port=\"16453\"" + " priority=\"7929856\"" + " type=\"assisted\"/>" + "<candidate" + " cid=\"grt654q2\"" + " host=\"2001:638:708:30c9:219:d1ff:fea4:a17d\"" + " jid=\"juliet@capulet.lit/balcony\"" + " port=\"6539\"" + " priority=\"8257606\"" + " type=\"direct\"/>" + "</transport>"*/ + "</content>" + "</jingle>"; + + JinglePayload::ref payload = boost::make_shared<JinglePayload>(); + payload->setAction(JinglePayload::SessionInitiate); + payload->setInitiator(JID("romeo@montague.lit/orchard")); + payload->setSessionID("uj3b2"); + + StreamInitiationFileInfo fileInfo; + fileInfo.setHash("552da749930852c69ae5d2141d3766b1"); + fileInfo.setRangeOffset(270336); + + JingleFileTransferDescription::ref desc = boost::make_shared<JingleFileTransferDescription>(); + desc->addRequest(fileInfo); + + JingleContentPayload::ref content = boost::make_shared<JingleContentPayload>(); + content->setCreator(JingleContentPayload::InitiatorCreator); + content->setName("a-file-request"); + content->addDescription(desc); + + payload->addPayload(content); + + CPPUNIT_ASSERT_EQUAL(expected, createTestling()->serialize(payload)); + } + + // http://xmpp.org/extensions/xep-0234.html#example-10 + void testSerialize_Xep0234_Example13() { + std::string expected = + "<jingle" + " action=\"session-info\"" + " initiator=\"romeo@montague.lit/orchard\"" + " sid=\"a73sjjvkla37jfea\"" + " xmlns=\"urn:xmpp:jingle:1\">" + "<received xmlns=\"urn:xmpp:jingle:apps:file-transfer:3\">" + "<file" + " hash=\"a749930852c69ae5d2141d3766b1552d\"" + " xmlns=\"http://jabber.org/protocol/si/profile/file-transfer\"/>" + "</received>" + "</jingle>"; + + JinglePayload::ref payload = boost::make_shared<JinglePayload>(); + payload->setAction(JinglePayload::SessionInfo); + payload->setInitiator(JID("romeo@montague.lit/orchard")); + payload->setSessionID("a73sjjvkla37jfea"); + + JingleFileTransferReceived::ref received = boost::make_shared<JingleFileTransferReceived>(); + + StreamInitiationFileInfo fileInfo; + fileInfo.setHash("a749930852c69ae5d2141d3766b1552d"); + + received->setFileInfo(fileInfo); + payload->addPayload(received); + + CPPUNIT_ASSERT_EQUAL(expected, createTestling()->serialize(payload)); + } + + // http://xmpp.org/extensions/xep-0260.html#example-1 + void testSerialize_Xep0260_Example1() { + std::string expected = + "<jingle" + " action=\"session-initiate\"" + " initiator=\"romeo@montague.lit/orchard\"" + " sid=\"a73sjjvkla37jfea\"" + " xmlns=\"urn:xmpp:jingle:1\">" + "<content creator=\"initiator\" name=\"ex\">" + "<transport" + " mode=\"tcp\"" + " sid=\"vj3hs98y\"" + " xmlns=\"urn:xmpp:jingle:transports:s5b:1\">" + "<candidate cid=\"hft54dqy\"" + " host=\"192.168.4.1\"" + " jid=\"romeo@montague.lit/orchard\"" + " port=\"5086\"" + " priority=\"8257636\"" + " type=\"direct\"/>" + "<candidate cid=\"hutr46fe\"" + " host=\"24.24.24.1\"" + " jid=\"romeo@montague.lit/orchard\"" + " port=\"5087\"" + " priority=\"8258636\"" + " type=\"direct\"/>" + "</transport>" + "</content>" + "</jingle>"; + + JinglePayload::ref payload = boost::make_shared<JinglePayload>(); + payload->setAction(JinglePayload::SessionInitiate); + payload->setInitiator(JID("romeo@montague.lit/orchard")); + payload->setSessionID("a73sjjvkla37jfea"); + + JingleContentPayload::ref content = boost::make_shared<JingleContentPayload>(); + content->setCreator(JingleContentPayload::InitiatorCreator); + content->setName("ex"); + + JingleS5BTransportPayload::ref transport = boost::make_shared<JingleS5BTransportPayload>(); + transport->setMode(JingleS5BTransportPayload::TCPMode); + transport->setSessionID("vj3hs98y"); + + JingleS5BTransportPayload::Candidate candidate1; + candidate1.cid = "hft54dqy"; + candidate1.hostPort = HostAddressPort(HostAddress("192.168.4.1"), 5086); + candidate1.jid = JID("romeo@montague.lit/orchard"); + candidate1.priority = 8257636; + candidate1.type = JingleS5BTransportPayload::Candidate::DirectType; + transport->addCandidate(candidate1); + + JingleS5BTransportPayload::Candidate candidate2; + candidate2.cid = "hutr46fe"; + candidate2.hostPort = HostAddressPort(HostAddress("24.24.24.1"), 5087); + candidate2.jid = JID("romeo@montague.lit/orchard"); + candidate2.priority = 8258636; + candidate2.type = JingleS5BTransportPayload::Candidate::DirectType; + transport->addCandidate(candidate2); + + content->addTransport(transport); + + payload->addPayload(content); + + CPPUNIT_ASSERT_EQUAL(expected, createTestling()->serialize(payload)); + } + + private: + FullPayloadSerializerCollection collection; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(JingleSerializersTest); + diff --git a/Swiften/StringCodecs/Hexify.cpp b/Swiften/StringCodecs/Hexify.cpp index 367743c..668079b 100644 --- a/Swiften/StringCodecs/Hexify.cpp +++ b/Swiften/StringCodecs/Hexify.cpp @@ -31,4 +31,22 @@ std::string Hexify::hexify(const ByteArray& data) { return std::string(result.str()); } +ByteArray Hexify::unhexify(const std::string& hexstring) { + if (hexstring.size() % 2) { + return ByteArray(); + } + ByteArray result = ByteArray(hexstring.size() / 2); + for (size_t pos = 0; pos < hexstring.size() - 1; pos += 2) { + char c; + c = hexstring[pos]; + int a = (c>='0'&&c<='9') ? c-'0' : (c>='A'&&c<='Z') ? c-'A' + 10 : (c>='a'&&c<='z') ? c-'a' + 10 : -1; + c = hexstring[pos+1]; + int b = (c>='0'&&c<='9') ? c-'0' : (c>='A'&&c<='Z') ? c-'A' + 10 : (c>='a'&&c<='z') ? c-'a' + 10 : -1; + if (a == -1 || b == -1) return ByteArray(); // fail + result[pos/2] = (a<<4) | b; + + } + return result; +} + } diff --git a/Swiften/StringCodecs/Hexify.h b/Swiften/StringCodecs/Hexify.h index 9815e21..c016448 100644 --- a/Swiften/StringCodecs/Hexify.h +++ b/Swiften/StringCodecs/Hexify.h @@ -13,5 +13,6 @@ namespace Swift { public: static std::string hexify(unsigned char byte); static std::string hexify(const ByteArray& data); + static ByteArray unhexify(const std::string& hexstring); }; } diff --git a/Swiften/StringCodecs/MD5.cpp b/Swiften/StringCodecs/MD5.cpp index 0d36254..6871f79 100644 --- a/Swiften/StringCodecs/MD5.cpp +++ b/Swiften/StringCodecs/MD5.cpp @@ -366,6 +366,27 @@ namespace { } } +MD5::MD5() { + state = new md5_state_t; + md5_init(state); +} + +MD5::~MD5() { + delete state; +} + +MD5& MD5::update(const std::vector<unsigned char>& input) { + md5_append(state, reinterpret_cast<const md5_byte_t*>(vecptr(input)), input.size()); + return *this; +} + +std::vector<unsigned char> MD5::getHash() { + ByteArray digest; + digest.resize(16); + md5_finish(state, reinterpret_cast<md5_byte_t*>(vecptr(digest))); + return digest; +} + ByteArray MD5::getHash(const ByteArray& data) { return getMD5Hash(data); } diff --git a/Swiften/StringCodecs/MD5.h b/Swiften/StringCodecs/MD5.h index b1d610c..09473c2 100644 --- a/Swiften/StringCodecs/MD5.h +++ b/Swiften/StringCodecs/MD5.h @@ -10,9 +10,20 @@ #include <Swiften/Base/SafeByteArray.h> namespace Swift { + struct md5_state_s; + class MD5 { public: + MD5(); + ~MD5(); + + MD5& update(const std::vector<unsigned char>& data); + std::vector<unsigned char> getHash(); + static ByteArray getHash(const ByteArray& data); static ByteArray getHash(const SafeByteArray& data); + + private: + md5_state_s* state; }; } diff --git a/Swiften/StringCodecs/UnitTest/HexifyTest.cpp b/Swiften/StringCodecs/UnitTest/HexifyTest.cpp index 9cbd0d4..38233f9 100644 --- a/Swiften/StringCodecs/UnitTest/HexifyTest.cpp +++ b/Swiften/StringCodecs/UnitTest/HexifyTest.cpp @@ -17,6 +17,7 @@ class HexifyTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(HexifyTest); CPPUNIT_TEST(testHexify); CPPUNIT_TEST(testHexify_Byte); + CPPUNIT_TEST(testUnhexify); CPPUNIT_TEST_SUITE_END(); public: @@ -27,6 +28,11 @@ class HexifyTest : public CppUnit::TestFixture { void testHexify_Byte() { CPPUNIT_ASSERT_EQUAL(std::string("b2"), Hexify::hexify(0xb2)); } + + void testUnhexify() { + CPPUNIT_ASSERT_EQUAL(std::string("ffaf02"), Hexify::hexify(Hexify::unhexify("ffaf02"))); + CPPUNIT_ASSERT(createByteArray("\x01\x23\xf2", 3) == Hexify::unhexify("0123f2")); + } }; CPPUNIT_TEST_SUITE_REGISTRATION(HexifyTest); diff --git a/Swiften/StringCodecs/UnitTest/MD5Test.cpp b/Swiften/StringCodecs/UnitTest/MD5Test.cpp index ce7e422..c62c46a 100644 --- a/Swiften/StringCodecs/UnitTest/MD5Test.cpp +++ b/Swiften/StringCodecs/UnitTest/MD5Test.cpp @@ -19,6 +19,7 @@ class MD5Test : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(MD5Test); CPPUNIT_TEST(testGetHash_Empty); CPPUNIT_TEST(testGetHash_Alphabet); + CPPUNIT_TEST(testIncrementalTest); CPPUNIT_TEST_SUITE_END(); public: @@ -33,6 +34,16 @@ class MD5Test : public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(createByteArray("\xd1\x74\xab\x98\xd2\x77\xd9\xf5\xa5\x61\x1c\x2c\x9f\x41\x9d\x9f", 16), result); } + + void testIncrementalTest() { + MD5 testling; + testling.update(createByteArray("ABCDEFGHIJKLMNOPQRSTUVWXYZ")); + testling.update(createByteArray("abcdefghijklmnopqrstuvwxyz0123456789")); + + ByteArray result = testling.getHash(); + + CPPUNIT_ASSERT_EQUAL(createByteArray("\xd1\x74\xab\x98\xd2\x77\xd9\xf5\xa5\x61\x1c\x2c\x9f\x41\x9d\x9f", 16), result); + } }; CPPUNIT_TEST_SUITE_REGISTRATION(MD5Test); |